Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
4610 lines (3792 sloc) 119 KB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: The TF Game rules
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "cs_gamerules.h"
#include "cs_ammodef.h"
#include "weapon_csbase.h"
#include "cs_shareddefs.h"
#include "KeyValues.h"
#ifdef CLIENT_DLL
#include "networkstringtable_clientdll.h"
#else
#include "bot.h"
#include "utldict.h"
#include "cs_player.h"
#include "cs_team.h"
#include "cs_gamerules.h"
#include "voice_gamemgr.h"
#include "igamesystem.h"
#include "weapon_c4.h"
#include "mapinfo.h"
#include "shake.h"
#include "mapentities.h"
#include "game.h"
#include "cs_simple_hostage.h"
#include "cs_gameinterface.h"
#include "player_resource.h"
#include "info_view_parameters.h"
#include "cs_bot_manager.h"
#include "cs_bot.h"
#include "eventqueue.h"
#include "fmtstr.h"
#include "teamplayroundbased_gamerules.h"
#include "gameweaponmanager.h"
#include "cs_gamestats.h"
#include "cs_urlretrieveprices.h"
#include "networkstringtable_gamedll.h"
#endif
#include "cs_blackmarket.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifndef CLIENT_DLL
#define CS_GAME_STATS_UPDATE 79200 //22 hours
#define CS_GAME_STATS_UPDATE_PERIOD 7200 // 2 hours
extern IUploadGameStats *gamestatsuploader;
#endif
/**
* Player hull & eye position for standing, ducking, etc. This version has a taller
* player height, but goldsrc-compatible collision bounds.
*/
static CViewVectors g_CSViewVectors(
Vector( 0, 0, 64 ), // eye position
Vector(-16, -16, 0 ), // hull min
Vector( 16, 16, 62 ), // hull max
Vector(-16, -16, 0 ), // duck hull min
Vector( 16, 16, 45 ), // duck hull max
Vector( 0, 0, 47 ), // duck view
Vector(-10, -10, -10 ), // observer hull min
Vector( 10, 10, 10 ), // observer hull max
Vector( 0, 0, 14 ) // dead view height
);
#ifndef CLIENT_DLL
LINK_ENTITY_TO_CLASS(info_player_terrorist, CPointEntity);
LINK_ENTITY_TO_CLASS(info_player_counterterrorist,CPointEntity);
LINK_ENTITY_TO_CLASS(info_player_logo,CPointEntity);
#endif
REGISTER_GAMERULES_CLASS( CCSGameRules );
BEGIN_NETWORK_TABLE_NOBASE( CCSGameRules, DT_CSGameRules )
#ifdef CLIENT_DLL
RecvPropBool( RECVINFO( m_bFreezePeriod ) ),
RecvPropInt( RECVINFO( m_iRoundTime ) ),
RecvPropFloat( RECVINFO( m_fRoundStartTime ) ),
RecvPropFloat( RECVINFO( m_flGameStartTime ) ),
RecvPropInt( RECVINFO( m_iHostagesRemaining ) ),
RecvPropBool( RECVINFO( m_bMapHasBombTarget ) ),
RecvPropBool( RECVINFO( m_bMapHasRescueZone ) ),
RecvPropBool( RECVINFO( m_bLogoMap ) ),
RecvPropBool( RECVINFO( m_bBlackMarket ) )
#else
SendPropBool( SENDINFO( m_bFreezePeriod ) ),
SendPropInt( SENDINFO( m_iRoundTime ), 16 ),
SendPropFloat( SENDINFO( m_fRoundStartTime ), 32, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_flGameStartTime ), 32, SPROP_NOSCALE ),
SendPropInt( SENDINFO( m_iHostagesRemaining ), 4 ),
SendPropBool( SENDINFO( m_bMapHasBombTarget ) ),
SendPropBool( SENDINFO( m_bMapHasRescueZone ) ),
SendPropBool( SENDINFO( m_bLogoMap ) ),
SendPropBool( SENDINFO( m_bBlackMarket ) )
#endif
END_NETWORK_TABLE()
LINK_ENTITY_TO_CLASS( cs_gamerules, CCSGameRulesProxy );
IMPLEMENT_NETWORKCLASS_ALIASED( CSGameRulesProxy, DT_CSGameRulesProxy )
#ifdef CLIENT_DLL
void RecvProxy_CSGameRules( const RecvProp *pProp, void **pOut, void *pData, int objectID )
{
CCSGameRules *pRules = CSGameRules();
Assert( pRules );
*pOut = pRules;
}
BEGIN_RECV_TABLE( CCSGameRulesProxy, DT_CSGameRulesProxy )
RecvPropDataTable( "cs_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_CSGameRules ), RecvProxy_CSGameRules )
END_RECV_TABLE()
#else
void* SendProxy_CSGameRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID )
{
CCSGameRules *pRules = CSGameRules();
Assert( pRules );
return pRules;
}
BEGIN_SEND_TABLE( CCSGameRulesProxy, DT_CSGameRulesProxy )
SendPropDataTable( "cs_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_CSGameRules ), SendProxy_CSGameRules )
END_SEND_TABLE()
#endif
ConVar ammo_50AE_max( "ammo_50AE_max", "35", FCVAR_REPLICATED );
ConVar ammo_762mm_max( "ammo_762mm_max", "90", FCVAR_REPLICATED );
ConVar ammo_556mm_max( "ammo_556mm_max", "90", FCVAR_REPLICATED );
ConVar ammo_556mm_box_max( "ammo_556mm_box_max", "200", FCVAR_REPLICATED );
ConVar ammo_338mag_max( "ammo_338mag_max", "30", FCVAR_REPLICATED );
ConVar ammo_9mm_max( "ammo_9mm_max", "120", FCVAR_REPLICATED );
ConVar ammo_buckshot_max( "ammo_buckshot_max", "32", FCVAR_REPLICATED );
ConVar ammo_45acp_max( "ammo_45acp_max", "100", FCVAR_REPLICATED );
ConVar ammo_357sig_max( "ammo_357sig_max", "52", FCVAR_REPLICATED );
ConVar ammo_57mm_max( "ammo_57mm_max", "100", FCVAR_REPLICATED );
ConVar ammo_hegrenade_max( "ammo_hegrenade_max", "1", FCVAR_REPLICATED );
ConVar ammo_flashbang_max( "ammo_flashbang_max", "2", FCVAR_REPLICATED );
ConVar ammo_smokegrenade_max( "ammo_smokegrenade_max", "1", FCVAR_REPLICATED );
//ConVar mp_dynamicpricing( "mp_dynamicpricing", "0", FCVAR_REPLICATED, "Enables or Disables the dynamic weapon prices" );
extern ConVar sv_stopspeed;
ConVar mp_buytime(
"mp_buytime",
"1.5",
FCVAR_REPLICATED,
"How many minutes after round start players can buy items for.",
true, 0.25,
false, 0 );
ConVar mp_playerid(
"mp_playerid",
"0",
FCVAR_REPLICATED,
"Controls what information player see in the status bar: 0 all names; 1 team names; 2 no names",
true, 0,
true, 2 );
ConVar mp_playerid_delay(
"mp_playerid_delay",
"0.5",
FCVAR_REPLICATED,
"Number of seconds to delay showing information in the status bar",
true, 0,
true, 1 );
ConVar mp_playerid_hold(
"mp_playerid_hold",
"0.25",
FCVAR_REPLICATED,
"Number of seconds to keep showing old information in the status bar",
true, 0,
true, 1 );
#ifdef CLIENT_DLL
ConVar cl_autowepswitch(
"cl_autowepswitch",
"1",
FCVAR_ARCHIVE | FCVAR_USERINFO,
"Automatically switch to picked up weapons (if more powerful)" );
ConVar cl_autohelp(
"cl_autohelp",
"1",
FCVAR_ARCHIVE | FCVAR_USERINFO,
"Auto-help" );
#else
// longest the intermission can last, in seconds
#define MAX_INTERMISSION_TIME 120
// Falling damage stuff.
#define CS_PLAYER_FATAL_FALL_SPEED 1100 // approx 60 feet
#define CS_PLAYER_MAX_SAFE_FALL_SPEED 580 // approx 20 feet
#define CS_DAMAGE_FOR_FALL_SPEED ((float)100 / ( CS_PLAYER_FATAL_FALL_SPEED - CS_PLAYER_MAX_SAFE_FALL_SPEED )) // damage per unit per second.
// These entities are preserved each round restart. The rest are removed and recreated.
static const char *s_PreserveEnts[] =
{
"ai_network",
"ai_hint",
"cs_gamerules",
"cs_team_manager",
"cs_player_manager",
"env_soundscape",
"env_soundscape_proxy",
"env_soundscape_triggerable",
"env_sun",
"env_wind",
"env_fog_controller",
"func_brush",
"func_wall",
"func_buyzone",
"func_illusionary",
"func_hostage_rescue",
"func_bomb_target",
"infodecal",
"info_projecteddecal",
"info_node",
"info_target",
"info_node_hint",
"info_player_counterterrorist",
"info_player_terrorist",
"info_map_parameters",
"keyframe_rope",
"move_rope",
"info_ladder",
"player",
"point_viewcontrol",
"scene_manager",
"shadow_control",
"sky_camera",
"soundent",
"trigger_soundscape",
"viewmodel",
"predicted_viewmodel",
"worldspawn",
"point_devshot_camera",
"", // END Marker
};
// --------------------------------------------------------------------------------------------------- //
// Voice helper
// --------------------------------------------------------------------------------------------------- //
class CVoiceGameMgrHelper : public IVoiceGameMgrHelper
{
public:
virtual bool CanPlayerHearPlayer( CBasePlayer *pListener, CBasePlayer *pTalker, bool &bProximity )
{
// Dead players can only be heard by other dead team mates
if ( pTalker->IsAlive() == false )
{
if ( pListener->IsAlive() == false )
return ( pListener->InSameTeam( pTalker ) );
return false;
}
return ( pListener->InSameTeam( pTalker ) );
}
};
CVoiceGameMgrHelper g_VoiceGameMgrHelper;
IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper;
// --------------------------------------------------------------------------------------------------- //
// Globals.
// --------------------------------------------------------------------------------------------------- //
// NOTE: the indices here must match TEAM_TERRORIST, TEAM_CT, TEAM_SPECTATOR, etc.
char *sTeamNames[] =
{
"Unassigned",
"Spectator",
"TERRORIST",
"CT"
};
extern ConVar mp_maxrounds;
ConVar mp_startmoney(
"mp_startmoney",
"800",
FCVAR_REPLICATED,
"amount of money each player gets when they reset",
true, 800,
true, 16000 );
ConVar mp_roundtime(
"mp_roundtime",
"5",
FCVAR_REPLICATED | FCVAR_NOTIFY,
"How many minutes each round takes.",
true, 1, // min value
true, 9 // max value
);
ConVar mp_freezetime(
"mp_freezetime",
"6",
FCVAR_REPLICATED | FCVAR_NOTIFY,
"how many seconds to keep players frozen when the round starts",
true, 0, // min value
true, 60 // max value
);
ConVar mp_c4timer(
"mp_c4timer",
"45",
FCVAR_REPLICATED | FCVAR_NOTIFY,
"how long from when the C4 is armed until it blows",
true, 10, // min value
true, 90 // max value
);
ConVar mp_limitteams(
"mp_limitteams",
"2",
FCVAR_REPLICATED | FCVAR_NOTIFY,
"Max # of players 1 team can have over another (0 disables check)",
true, 0, // min value
true, 30 // max value
);
ConVar mp_tkpunish(
"mp_tkpunish",
"0",
FCVAR_REPLICATED,
"Will a TK'er be punished in the next round? {0=no, 1=yes}" );
ConVar mp_autokick(
"mp_autokick",
"1",
FCVAR_REPLICATED,
"Kick idle/team-killing players" );
ConVar mp_spawnprotectiontime(
"mp_spawnprotectiontime",
"5",
FCVAR_REPLICATED,
"Kick players who team-kill within this many seconds of a round restart." );
ConVar mp_humanteam(
"mp_humanteam",
"any",
FCVAR_REPLICATED,
"Restricts human players to a single team {any, CT, T}" );
ConCommand EndRound( "endround", &CCSGameRules::EndRound, "End the current round.", FCVAR_CHEAT );
// --------------------------------------------------------------------------------------------------- //
// Global helper functions.
// --------------------------------------------------------------------------------------------------- //
void InitBodyQue(void)
{
// FIXME: Make this work
}
Vector DropToGround(
CBaseEntity *pMainEnt,
const Vector &vPos,
const Vector &vMins,
const Vector &vMaxs )
{
trace_t trace;
UTIL_TraceHull( vPos, vPos + Vector( 0, 0, -500 ), vMins, vMaxs, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &trace );
return trace.endpos;
}
//-----------------------------------------------------------------------------
// Purpose: This function can be used to find a valid placement location for an entity.
// Given an origin to start looking from and a minimum radius to place the entity at,
// it will sweep out a circle around vOrigin and try to find a valid spot (on the ground)
// where mins and maxs will fit.
// Input : *pMainEnt - Entity to place
// &vOrigin - Point to search around
// fRadius - Radius to search within
// nTries - Number of tries to attempt
// &mins - mins of the Entity
// &maxs - maxs of the Entity
// &outPos - Return point
// Output : Returns true and fills in outPos if it found a spot.
//-----------------------------------------------------------------------------
bool EntityPlacementTest( CBaseEntity *pMainEnt, const Vector &vOrigin, Vector &outPos, bool bDropToGround )
{
// This function moves the box out in each dimension in each step trying to find empty space like this:
//
// X
// X X
// Step 1: X Step 2: XXX Step 3: XXXXX
// X X
// X
//
Vector mins, maxs;
pMainEnt->CollisionProp()->WorldSpaceAABB( &mins, &maxs );
mins -= pMainEnt->GetAbsOrigin();
maxs -= pMainEnt->GetAbsOrigin();
// Put some padding on their bbox.
float flPadSize = 5;
Vector vTestMins = mins - Vector( flPadSize, flPadSize, flPadSize );
Vector vTestMaxs = maxs + Vector( flPadSize, flPadSize, flPadSize );
// First test the starting origin.
if ( UTIL_IsSpaceEmpty( pMainEnt, vOrigin + vTestMins, vOrigin + vTestMaxs ) )
{
if ( bDropToGround )
{
outPos = DropToGround( pMainEnt, vOrigin, vTestMins, vTestMaxs );
}
else
{
outPos = vOrigin;
}
return true;
}
Vector vDims = vTestMaxs - vTestMins;
// Keep branching out until we get too far.
int iCurIteration = 0;
int nMaxIterations = 15;
int offset = 0;
do
{
for ( int iDim=0; iDim < 3; iDim++ )
{
float flCurOffset = offset * vDims[iDim];
for ( int iSign=0; iSign < 2; iSign++ )
{
Vector vBase = vOrigin;
vBase[iDim] += (iSign*2-1) * flCurOffset;
if ( UTIL_IsSpaceEmpty( pMainEnt, vBase + vTestMins, vBase + vTestMaxs ) )
{
// Ensure that there is a clear line of sight from the spawnpoint entity to the actual spawn point.
// (Useful for keeping things from spawning behind walls near a spawn point)
trace_t tr;
UTIL_TraceLine( vOrigin, vBase, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction != 1.0 )
{
continue;
}
if ( bDropToGround )
outPos = DropToGround( pMainEnt, vBase, vTestMins, vTestMaxs );
else
outPos = vBase;
return true;
}
}
}
++offset;
} while ( iCurIteration++ < nMaxIterations );
// Warning( "EntityPlacementTest for ent %d:%s failed!\n", pMainEnt->entindex(), pMainEnt->GetClassname() );
return false;
}
int UTIL_HumansInGame( bool ignoreSpectators )
{
int iCount = 0;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *entity = CCSPlayer::Instance( i );
if ( entity && !FNullEnt( entity->edict() ) )
{
if ( FStrEq( entity->GetPlayerName(), "" ) )
continue;
if ( FBitSet( entity->GetFlags(), FL_FAKECLIENT ) )
continue;
if ( ignoreSpectators && entity->GetTeamNumber() != TEAM_TERRORIST && entity->GetTeamNumber() != TEAM_CT )
continue;
if ( ignoreSpectators && entity->State_Get() == STATE_PICKINGCLASS )
continue;
iCount++;
}
}
return iCount;
}
// --------------------------------------------------------------------------------------------------- //
// CCSGameRules implementation.
// --------------------------------------------------------------------------------------------------- //
CCSGameRules::CCSGameRules()
{
m_iRoundTime = 0;
m_iRoundWinStatus = WINNER_NONE;
m_iFreezeTime = 0;
m_fRoundStartTime = 0;
m_bAllowWeaponSwitch = true;
m_bFreezePeriod = true;
m_iNumTerrorist = m_iNumCT = 0; // number of players per team
m_flRestartRoundTime = 0.1f; // restart first round as soon as possible
m_iNumSpawnableTerrorist = m_iNumSpawnableCT = 0;
m_bFirstConnected = false;
m_bCompleteReset = false;
m_iAccountTerrorist = m_iAccountCT = 0;
m_iNumCTWins = 0;
m_iNumTerroristWins = 0;
m_iNumConsecutiveCTLoses = 0;
m_iNumConsecutiveTerroristLoses = 0;
m_bTargetBombed = false;
m_bBombDefused = false;
m_iTotalRoundsPlayed = -1;
m_iUnBalancedRounds = 0;
m_flGameStartTime = 0;
m_iHostagesRemaining = 0;
m_bLevelInitialized = false;
m_bLogoMap = false;
m_tmNextPeriodicThink = 0;
m_bMapHasBombTarget = false;
m_bMapHasRescueZone = false;
m_iSpawnPointCount_Terrorist = 0;
m_iSpawnPointCount_CT = 0;
m_bTCantBuy = false;
m_bCTCantBuy = false;
m_bMapHasBuyZone = false;
m_iLoserBonus = 0;
m_iHostagesRescued = 0;
m_iHostagesTouched = 0;
m_flNextHostageAnnouncement = 0.0f;
m_iHaveEscaped = 0;
m_bMapHasEscapeZone = false;
m_iNumEscapers = 0;
m_iNumEscapeRounds = 0;
m_iMapHasVIPSafetyZone = 0;
m_pVIP = NULL;
m_iConsecutiveVIP = 0;
m_bMapHasBombZone = false;
m_bBombDropped = false;
m_bBombPlanted = false;
m_pLastBombGuy = NULL;
m_bAllowWeaponSwitch = true;
m_flNextHostageAnnouncement = gpGlobals->curtime; // asap.
ReadMultiplayCvars();
m_pPrices = NULL;
m_bBlackMarket = false;
m_bDontUploadStats = false;
// Create the team managers
for ( int i = 0; i < ARRAYSIZE( sTeamNames ); i++ )
{
CTeam *pTeam = static_cast<CTeam*>(CreateEntityByName( "cs_team_manager" ));
pTeam->Init( sTeamNames[i], i );
g_Teams.AddToTail( pTeam );
}
if ( filesystem->FileExists( UTIL_VarArgs( "maps/cfg/%s.cfg", STRING(gpGlobals->mapname) ) ) )
{
// Execute a map specific cfg file - as in Day of Defeat
engine->ServerCommand( UTIL_VarArgs( "exec %s.cfg */maps\n", STRING(gpGlobals->mapname) ) );
engine->ServerExecute();
}
#ifndef CLIENT_DLL
// stats
if ( g_flGameStatsUpdateTime == 0.0f )
{
memset( g_iWeaponPurchases, 0, sizeof( g_iWeaponPurchases) );
memset( g_iTerroristVictories, 0, sizeof( g_iTerroristVictories) );
memset( g_iCounterTVictories, 0, sizeof( g_iTerroristVictories) );
g_flGameStatsUpdateTime = CS_GAME_STATS_UPDATE; //Next update is between 22 and 24 hours.
}
#endif
}
void CCSGameRules::AddPricesToTable( weeklyprice_t prices )
{
int iIndex = m_StringTableBlackMarket->FindStringIndex( "blackmarket_prices" );
if ( iIndex == INVALID_STRING_INDEX )
{
m_StringTableBlackMarket->AddString( CBaseEntity::IsServer(), "blackmarket_prices", sizeof( weeklyprice_t), &prices );
}
else
{
m_StringTableBlackMarket->SetStringUserData( iIndex, sizeof( weeklyprice_t), &prices );
}
SetBlackMarketPrices( false );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CCSGameRules::~CCSGameRules()
{
// Note, don't delete each team since they are in the gEntList and will
// automatically be deleted from there, instead.
g_Teams.Purge();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CCSGameRules::UpdateClientData( CBasePlayer *player )
{
}
//-----------------------------------------------------------------------------
// Purpose: TF2 Specific Client Commands
// Input :
// Output :
//-----------------------------------------------------------------------------
bool CCSGameRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args )
{
CCSPlayer *pPlayer = ToCSPlayer( pEdict );
if ( FStrEq( args[0], "changeteam" ) )
{
return true;
}
else if ( FStrEq( args[0], "nextmap" ) )
{
if ( pPlayer->m_iNextTimeCheck < gpGlobals->curtime )
{
char szNextMap[32];
if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) )
{
Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) );
}
else
{
GetNextLevelName( szNextMap, sizeof( szNextMap ) );
}
ClientPrint( pPlayer, HUD_PRINTTALK, "#game_nextmap", szNextMap);
pPlayer->m_iNextTimeCheck = gpGlobals->curtime + 1;
}
return true;
}
else if( pPlayer->ClientCommand( args ) )
{
return true;
}
else if( BaseClass::ClientCommand( pEdict, args ) )
{
return true;
}
else if ( TheBots->ServerCommand( args.GetCommandString() ) )
{
return true;
}
else
{
return TheBots->ClientCommand( pPlayer, args );
}
}
//-----------------------------------------------------------------------------
// Purpose: Player has just spawned. Equip them.
//-----------------------------------------------------------------------------
void CCSGameRules::PlayerSpawn( CBasePlayer *pBasePlayer )
{
CCSPlayer *pPlayer = ToCSPlayer( pBasePlayer );
if ( !pPlayer )
Error( "PlayerSpawn" );
if ( pPlayer->State_Get() != STATE_ACTIVE )
return;
pPlayer->EquipSuit();
bool addDefault = true;
CBaseEntity *pWeaponEntity = NULL;
while ( ( pWeaponEntity = gEntList.FindEntityByClassname( pWeaponEntity, "game_player_equip" )) != NULL )
{
if ( addDefault )
{
// remove all our weapons and armor before touching the first game_player_equip
pPlayer->RemoveAllItems( true );
}
pWeaponEntity->Touch( pPlayer );
addDefault = false;
}
if ( addDefault || pPlayer->m_bIsVIP )
pPlayer->GiveDefaultItems();
}
void CCSGameRules::BroadcastSound( const char *sound, int team )
{
CBroadcastRecipientFilter filter;
filter.MakeReliable();
if( team != -1 )
{
filter.RemoveAllRecipients();
filter.AddRecipientsByTeam( GetGlobalTeam(team) );
}
UserMessageBegin ( filter, "SendAudio" );
WRITE_STRING( sound );
MessageEnd();
}
//-----------------------------------------------------------------------------
// Purpose: Player has just spawned. Equip them.
//-----------------------------------------------------------------------------
// return a multiplier that should adjust the damage done by a blast at position vecSrc to something at the position
// vecEnd. This will take into account the density of an entity that blocks the line of sight from one position to
// the other.
//
// this algorithm was taken from the HL2 version of RadiusDamage.
float CCSGameRules::GetExplosionDamageAdjustment(Vector & vecSrc, Vector & vecEnd, CBaseEntity *pEntityToIgnore)
{
float retval = 0.0;
trace_t tr;
UTIL_TraceLine(vecSrc, vecEnd, MASK_SHOT, pEntityToIgnore, COLLISION_GROUP_NONE, &tr);
if (tr.fraction == 1.0)
{
retval = 1.0;
}
else if (!(tr.DidHitWorld()) && (tr.m_pEnt != NULL) && (tr.m_pEnt != pEntityToIgnore) && (tr.m_pEnt->GetOwnerEntity() != pEntityToIgnore))
{
// if we didn't hit world geometry perhaps there's still damage to be done here.
CBaseEntity *blockingEntity = tr.m_pEnt;
// check to see if this part of the player is visible if entities are ignored.
UTIL_TraceLine(vecSrc, vecEnd, CONTENTS_SOLID, NULL, COLLISION_GROUP_NONE, &tr);
if (tr.fraction == 1.0)
{
if ((blockingEntity != NULL) && (blockingEntity->VPhysicsGetObject() != NULL))
{
int nMaterialIndex = blockingEntity->VPhysicsGetObject()->GetMaterialIndex();
float flDensity;
float flThickness;
float flFriction;
float flElasticity;
physprops->GetPhysicsProperties( nMaterialIndex, &flDensity,
&flThickness, &flFriction, &flElasticity );
const float DENSITY_ABSORB_ALL_DAMAGE = 3000.0;
float scale = flDensity / DENSITY_ABSORB_ALL_DAMAGE;
if ((scale >= 0.0) && (scale < 1.0))
{
retval = 1.0 - scale;
}
else if (scale < 0.0)
{
// should never happen, but just in case.
retval = 1.0;
}
}
else
{
retval = 0.75; // we're blocked by something that isn't an entity with a physics module or world geometry, just cut damage in half for now.
}
}
}
return retval;
}
// returns the percentage of the player that is visible from the given point in the world.
// return value is between 0 and 1.
float CCSGameRules::GetAmountOfEntityVisible(Vector & vecSrc, CBaseEntity *entity)
{
float retval = 0.0;
const float damagePercentageChest = 0.40;
const float damagePercentageHead = 0.20;
const float damagePercentageFeet = 0.20;
const float damagePercentageRightSide = 0.10;
const float damagePercentageLeftSide = 0.10;
if (!(entity->IsPlayer()))
{
// the entity is not a player, so the damage is all or nothing.
Vector vecTarget;
vecTarget = entity->BodyTarget(vecSrc, false);
return GetExplosionDamageAdjustment(vecSrc, vecTarget, entity);
}
CCSPlayer *player = (CCSPlayer *)entity;
// check what parts of the player we can see from this point and modify the return value accordingly.
float chestHeightFromFeet;
float armDistanceFromChest = HalfHumanWidth;
// calculate positions of various points on the target player's body
Vector vecFeet = player->GetAbsOrigin();
Vector vecChest = player->BodyTarget(vecSrc, false);
chestHeightFromFeet = vecChest.z - vecFeet.z; // compute the distance from the chest to the feet. (this accounts for ducking and the like)
Vector vecHead = player->GetAbsOrigin();
vecHead.z += HumanHeight;
Vector vecRightFacing;
AngleVectors(player->GetAbsAngles(), NULL, &vecRightFacing, NULL);
vecRightFacing.NormalizeInPlace();
vecRightFacing = vecRightFacing * armDistanceFromChest;
Vector vecLeftSide = player->GetAbsOrigin();
vecLeftSide.x -= vecRightFacing.x;
vecLeftSide.y -= vecRightFacing.y;
vecLeftSide.z += chestHeightFromFeet;
Vector vecRightSide = player->GetAbsOrigin();
vecRightSide.x += vecRightFacing.x;
vecRightSide.y += vecRightFacing.y;
vecRightSide.z += chestHeightFromFeet;
// check chest
float damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecChest, entity);
retval += (damagePercentageChest * damageAdjustment);
// check top of head
damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecHead, entity);
retval += (damagePercentageHead * damageAdjustment);
// check feet
damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecFeet, entity);
retval += (damagePercentageFeet * damageAdjustment);
// check left "edge"
damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecLeftSide, entity);
retval += (damagePercentageLeftSide * damageAdjustment);
// check right "edge"
damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecRightSide, entity);
retval += (damagePercentageRightSide * damageAdjustment);
return retval;
}
void CCSGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, CBaseEntity * pEntityIgnore )
{
RadiusDamage( info, vecSrcIn, flRadius, iClassIgnore, false );
}
// Add the ability to ignore the world trace
void CCSGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, bool bIgnoreWorld )
{
CBaseEntity *pEntity = NULL;
trace_t tr;
float flAdjustedDamage, falloff, damagePercentage;
Vector vecSpot;
Vector vecToTarget;
Vector vecEndPos;
vecEndPos.Init();
Vector vecSrc = vecSrcIn;
damagePercentage = 1.0;
if ( flRadius )
falloff = info.GetDamage() / flRadius;
else
falloff = 1.0;
int bInWater = (UTIL_PointContents ( vecSrc ) & MASK_WATER) ? true : false;
vecSrc.z += 1;// in case grenade is lying on the ground
// iterate on all entities in the vicinity.
for ( CEntitySphereQuery sphere( vecSrc, flRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
{
if ( pEntity->m_takedamage != DAMAGE_NO )
{
// UNDONE: this should check a damage mask, not an ignore
if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore )
{// houndeyes don't hurt other houndeyes with their attack
continue;
}
// blasts don't travel into or out of water
if ( !bIgnoreWorld )
{
if (bInWater && pEntity->GetWaterLevel() == 0)
continue;
if (!bInWater && pEntity->GetWaterLevel() == 3)
continue;
}
// radius damage can only be blocked by the world
vecSpot = pEntity->BodyTarget( vecSrc );
bool bHit = false;
if( bIgnoreWorld )
{
vecEndPos = vecSpot;
bHit = true;
}
else
{
// get the percentage of the target entity that is visible from the
// explosion position.
damagePercentage = GetAmountOfEntityVisible(vecSrc, pEntity);
if (damagePercentage > 0.0)
{
vecEndPos = vecSpot;
bHit = true;
}
}
if ( bHit )
{
// the explosion can 'see' this entity, so hurt them!
//vecToTarget = ( vecSrc - vecEndPos );
vecToTarget = ( vecEndPos - vecSrc );
// decrease damage for an ent that's farther from the bomb.
flAdjustedDamage = vecToTarget.Length() * falloff;
flAdjustedDamage = info.GetDamage() - flAdjustedDamage;
flAdjustedDamage = flAdjustedDamage * damagePercentage;
if ( flAdjustedDamage > 0 )
{
CTakeDamageInfo adjustedInfo = info;
adjustedInfo.SetDamage( flAdjustedDamage );
Vector dir = vecToTarget;
VectorNormalize( dir );
// If we don't have a damage force, manufacture one
if ( adjustedInfo.GetDamagePosition() == vec3_origin || adjustedInfo.GetDamageForce() == vec3_origin )
{
CalculateExplosiveDamageForce( &adjustedInfo, dir, vecSrc, 1.5 /* explosion scale! */ );
}
else
{
// Assume the force passed in is the maximum force. Decay it based on falloff.
float flForce = adjustedInfo.GetDamageForce().Length() * falloff;
adjustedInfo.SetDamageForce( dir * flForce );
adjustedInfo.SetDamagePosition( vecSrc );
}
Vector vecTarget;
vecTarget = pEntity->BodyTarget(vecSrc, false);
UTIL_TraceLine(vecSrc, vecTarget, MASK_SHOT, NULL, COLLISION_GROUP_NONE, &tr);
if (tr.fraction != 1.0)
{
// this has to be done to make breakable glass work.
ClearMultiDamage( );
pEntity->DispatchTraceAttack( adjustedInfo, dir, &tr );
ApplyMultiDamage();
}
else
{
pEntity->TakeDamage( adjustedInfo );
}
// Now hit all triggers along the way that respond to damage...
pEntity->TraceAttackToTriggers( adjustedInfo, vecSrc, vecEndPos, dir );
}
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pVictim -
// *pKiller -
// *pInflictor -
//-----------------------------------------------------------------------------
void CCSGameRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info )
{
// Work out what killed the player, and send a message to all clients about it
const char *killer_weapon_name = "world"; // by default, the player is killed by the world
int killer_ID = 0;
// Find the killer & the scorer
CBaseEntity *pInflictor = info.GetInflictor();
CBaseEntity *pKiller = info.GetAttacker();
CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor );
bool bHeadshot = false;
if ( pScorer ) // Is the killer a client?
{
killer_ID = pScorer->GetUserID();
if( info.GetDamageType() & DMG_HEADSHOT )
{
//to enable drawing the headshot icon as well as the weapon icon,
bHeadshot = true;
}
if ( pInflictor )
{
if ( pInflictor == pScorer )
{
// If the inflictor is the killer, then it must be their current weapon doing the damage
if ( pScorer->GetActiveWeapon() )
{
killer_weapon_name = pScorer->GetActiveWeapon()->GetClassname(); //GetDeathNoticeName();
}
}
else
{
killer_weapon_name = STRING( pInflictor->m_iClassname ); // it's just that easy
}
}
}
else
{
killer_weapon_name = STRING( pInflictor->m_iClassname );
}
// strip the NPC_* or weapon_* from the inflictor's classname
if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 )
{
killer_weapon_name += 7;
}
else if ( strncmp( killer_weapon_name, "NPC_", 8 ) == 0 )
{
killer_weapon_name += 8;
}
else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 )
{
killer_weapon_name += 5;
}
else if( strncmp( killer_weapon_name, "hegrenade", 9 ) == 0 ) //"hegrenade_projectile"
{
killer_weapon_name = "hegrenade";
}
else if( strncmp( killer_weapon_name, "flashbang", 9 ) == 0 ) //"flashbang_projectile"
{
killer_weapon_name = "flashbang";
}
IGameEvent * event = gameeventmanager->CreateEvent( "player_death" );
if ( event )
{
event->SetInt("userid", pVictim->GetUserID() );
event->SetInt("attacker", killer_ID );
event->SetString("weapon", killer_weapon_name );
event->SetInt("headshot", bHeadshot ? 1 : 0 );
event->SetInt("priority", bHeadshot ? 8 : 7 ); // HLTV event priority, not transmitted
gameeventmanager->FireEvent( event );
}
}
//=========================================================
//=========================================================
void CCSGameRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info )
{
CBaseEntity *pInflictor = info.GetInflictor();
CBaseEntity *pKiller = info.GetAttacker();
CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor );
// If we're killed by the C4, we do a subset of BaseClass::PlayerKilled()
// Specifically, we shouldn't lose any points or show death notices, to match goldsrc
if ( Q_strcmp(pKiller->GetClassname(), "planted_c4" ) == 0 )
{
// dvsents2: uncomment when removing all FireTargets
// variant_t value;
// g_EventQueue.AddEvent( "game_playerdie", "Use", value, 0, pVictim, pVictim );
FireTargets( "game_playerdie", pVictim, pVictim, USE_TOGGLE, 0 );
}
else
{
BaseClass::PlayerKilled( pVictim, info );
}
// check for team-killing, and give monetary rewards/penalties
// Find the killer & the scorer
if ( !pScorer )
return;
CCSPlayer *pCSVictim = (CCSPlayer *)pVictim;
CCSPlayer *pCSScorer = (CCSPlayer *)pScorer;
if ( IPointsForKill( pScorer, pVictim ) < 0 )
{
// team-killer!
pCSScorer->AddAccount( -3300 );
++pCSScorer->m_iTeamKills;
pCSScorer->m_bJustKilledTeammate = true;
ClientPrint( pCSScorer, HUD_PRINTCENTER, "#Killed_Teammate" );
if ( mp_autokick.GetBool() )
{
char strTeamKills[64];
Q_snprintf( strTeamKills, sizeof( strTeamKills ), "%d", pCSScorer->m_iTeamKills );
ClientPrint( pCSScorer, HUD_PRINTCONSOLE, "#Game_teammate_kills", strTeamKills ); // this includes a " of 3" in it
}
if ( pCSScorer->m_iTeamKills >= 3 && mp_autokick.GetBool() )
{
ClientPrint( pCSScorer, HUD_PRINTCONSOLE, "#Banned_For_Killing_Teammates" );
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pCSScorer->GetUserID() ) );
}
else if ( mp_spawnprotectiontime.GetInt() > 0 && GetRoundElapsedTime() < mp_spawnprotectiontime.GetInt() )
{
ClientPrint( pCSScorer, HUD_PRINTCONSOLE, "#Banned_For_Killing_Teammates" );
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pCSScorer->GetUserID() ) );
}
if ( !(pCSScorer->m_iDisplayHistoryBits & DHF_FRIEND_KILLED) )
{
pCSScorer->m_iDisplayHistoryBits |= DHF_FRIEND_KILLED;
pCSScorer->HintMessage( "#Hint_careful_around_teammates", false );
}
}
else
{
if ( pCSVictim->IsVIP() )
{
pCSScorer->HintMessage( "#Hint_reward_for_killing_vip", true );
pCSScorer->AddAccount( 2500 );
}
else
{
pCSScorer->AddAccount( 300 );
}
if ( !(pCSScorer->m_iDisplayHistoryBits & DHF_ENEMY_KILLED) )
{
pCSScorer->m_iDisplayHistoryBits |= DHF_ENEMY_KILLED;
pCSScorer->HintMessage( "#Hint_win_round_by_killing_enemy", false );
}
}
}
void CCSGameRules::InitDefaultAIRelationships()
{
// Allocate memory for default relationships
CBaseCombatCharacter::AllocateDefaultRelationships();
// --------------------------------------------------------------
// First initialize table so we can report missing relationships
// --------------------------------------------------------------
int i, j;
for (i=0;i<NUM_AI_CLASSES;i++)
{
for (j=0;j<NUM_AI_CLASSES;j++)
{
// By default all relationships are neutral of priority zero
CBaseCombatCharacter::SetDefaultRelationship( (Class_T)i, (Class_T)j, D_NU, 0 );
}
}
}
//------------------------------------------------------------------------------
// Purpose : Return classify text for classify type
//------------------------------------------------------------------------------
const char *CCSGameRules::AIClassText(int classType)
{
switch (classType)
{
case CLASS_NONE: return "CLASS_NONE";
case CLASS_PLAYER: return "CLASS_PLAYER";
default: return "MISSING CLASS in ClassifyText()";
}
}
//-----------------------------------------------------------------------------
// Purpose: When gaining new technologies in TF, prevent auto switching if we
// receive a weapon during the switch
// Input : *pPlayer -
// *pWeapon -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CCSGameRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon )
{
bool bIsBeingGivenItem = false;
CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
if ( pCSPlayer && pCSPlayer->IsBeingGivenItem() )
bIsBeingGivenItem = true;
if ( pPlayer->GetActiveWeapon() && pPlayer->IsNetClient() && !bIsBeingGivenItem )
{
// Player has an active item, so let's check cl_autowepswitch.
const char *cl_autowepswitch = engine->GetClientConVarValue( engine->IndexOfEdict( pPlayer->edict() ), "cl_autowepswitch" );
if ( cl_autowepswitch && atoi( cl_autowepswitch ) <= 0 )
{
return false;
}
}
if ( pPlayer->IsBot() && !bIsBeingGivenItem )
{
return false;
}
if ( !GetAllowWeaponSwitch() )
{
return false;
}
return BaseClass::FShouldSwitchWeapon( pPlayer, pWeapon );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : allow -
//-----------------------------------------------------------------------------
void CCSGameRules::SetAllowWeaponSwitch( bool allow )
{
m_bAllowWeaponSwitch = allow;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CCSGameRules::GetAllowWeaponSwitch()
{
return m_bAllowWeaponSwitch;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPlayer -
// Output : const char
//-----------------------------------------------------------------------------
const char *CCSGameRules::SetDefaultPlayerTeam( CBasePlayer *pPlayer )
{
Assert( pPlayer );
return BaseClass::SetDefaultPlayerTeam( pPlayer );
}
void CCSGameRules::LevelInitPreEntity()
{
BaseClass::LevelInitPreEntity();
// TODO for CZ-style hostages: TheHostageChatter->Precache();
}
void CCSGameRules::LevelInitPostEntity()
{
BaseClass::LevelInitPostEntity();
m_bLevelInitialized = false; // re-count CT and T start spots now that they exist
// Figure out from the entities in the map what kind of map this is (bomb run, prison escape, etc).
CheckMapConditions();
}
INetworkStringTable *g_StringTableBlackMarket = NULL;
void CCSGameRules::CreateCustomNetworkStringTables( void )
{
m_StringTableBlackMarket = g_StringTableBlackMarket;
if ( 0 )//mp_dynamicpricing.GetBool() )
{
m_bBlackMarket = BlackMarket_DownloadPrices();
if ( m_bBlackMarket == false )
{
Msg( "ERROR: mp_dynamicpricing set to 1 but couldn't download the price list!\n" );
}
}
else
{
m_bBlackMarket = false;
SetBlackMarketPrices( true );
}
}
float CCSGameRules::FlPlayerFallDamage( CBasePlayer *pPlayer )
{
pPlayer->m_Local.m_flFallVelocity -= CS_PLAYER_MAX_SAFE_FALL_SPEED;
float fallDamage = pPlayer->m_Local.m_flFallVelocity * CS_DAMAGE_FOR_FALL_SPEED * 1.25;
if ( fallDamage > 0.0f )
{
// let the bots know
IGameEvent * event = gameeventmanager->CreateEvent( "player_falldamage" );
if ( event )
{
event->SetInt( "userid", pPlayer->GetUserID() );
event->SetFloat( "damage", fallDamage );
event->SetInt( "priority", 4 ); // HLTV event priority, not transmitted
gameeventmanager->FireEvent( event );
}
}
return fallDamage;
}
void CCSGameRules::ClientDisconnected( edict_t *pClient )
{
BaseClass::ClientDisconnected( pClient );
CheckWinConditions();
}
// Called when game rules are destroyed by CWorld
void CCSGameRules::LevelShutdown()
{
int iLevelIndex = GetCSLevelIndex( STRING( gpGlobals->mapname ) );
if ( iLevelIndex != -1 )
{
g_iTerroristVictories[iLevelIndex] += m_iNumTerroristWins;
g_iCounterTVictories[iLevelIndex] += m_iNumCTWins;
}
BaseClass::LevelShutdown();
}
//---------------------------------------------------------------------------------------------------
/**
* Check if the scenario has been won/lost.
* Return true if the scenario is over, false if the scenario is still in progress
*/
bool CCSGameRules::CheckWinConditions( void )
{
// If a winner has already been determined.. then get the heck out of here
if (m_iRoundWinStatus != WINNER_NONE)
{
// still check if we lost players to where we need to do a full reset next round...
int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT;
InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT );
bool bNeededPlayers = false;
NeededPlayersCheck( bNeededPlayers );
return true;
}
// Initialize the player counts..
int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT;
InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT );
/***************************** OTHER PLAYER's CHECK *********************************************************/
bool bNeededPlayers = false;
if ( NeededPlayersCheck( bNeededPlayers ) )
return false;
/****************************** ASSASINATION/VIP SCENARIO CHECK *******************************************************/
if ( VIPRoundEndCheck( bNeededPlayers ) )
return true;
/****************************** PRISON ESCAPE CHECK *******************************************************/
if ( PrisonRoundEndCheck() )
return true;
/****************************** BOMB CHECK ********************************************************/
if ( BombRoundEndCheck( bNeededPlayers ) )
return true;
/***************************** TEAM EXTERMINATION CHECK!! *********************************************************/
// CounterTerrorists won by virture of elimination
if ( TeamExterminationCheck( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT, bNeededPlayers ) )
return true;
/******************************** HOSTAGE RESCUE CHECK ******************************************************/
if ( HostageRescueRoundEndCheck( bNeededPlayers ) )
return true;
// scenario not won - still in progress
return false;
}
bool CCSGameRules::NeededPlayersCheck( bool &bNeededPlayers )
{
// We needed players to start scoring
// Do we have them now?
if( !m_iNumSpawnableTerrorist || !m_iNumSpawnableCT )
{
Msg( "Game will not start until both teams have players.\n" );
UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#Game_scoring" );
bNeededPlayers = true;
m_bFirstConnected = false;
}
if ( !m_bFirstConnected && m_iNumSpawnableTerrorist && m_iNumSpawnableCT )
{
// Start the round immediately when the first person joins
// UTIL_LogPrintf( "World triggered \"Game_Commencing\"\n" );
m_bFreezePeriod = false; //Make sure we are not on the FreezePeriod.
m_bCompleteReset = true;
TerminateRound( 3, Game_Commencing );
m_bFirstConnected = true;
return true;
}
return false;
}
void CCSGameRules::InitializePlayerCounts(
int &NumAliveTerrorist,
int &NumAliveCT,
int &NumDeadTerrorist,
int &NumDeadCT
)
{
NumAliveTerrorist = NumAliveCT = NumDeadCT = NumDeadTerrorist = 0;
m_iNumTerrorist = m_iNumCT = m_iNumSpawnableTerrorist = m_iNumSpawnableCT = 0;
m_iHaveEscaped = 0;
// Count how many dead players there are on each team.
for ( int iTeam=0; iTeam < GetNumberOfTeams(); iTeam++ )
{
CTeam *pTeam = GetGlobalTeam( iTeam );
for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ )
{
CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) );
Assert( pPlayer );
if ( !pPlayer )
continue;
Assert( pPlayer->GetTeamNumber() == pTeam->GetTeamNumber() );
switch ( pTeam->GetTeamNumber() )
{
case TEAM_CT:
m_iNumCT++;
if ( pPlayer->State_Get() != STATE_PICKINGCLASS )
m_iNumSpawnableCT++;
if ( pPlayer->m_lifeState != LIFE_ALIVE )
NumDeadCT++;
else
NumAliveCT++;
break;
case TEAM_TERRORIST:
m_iNumTerrorist++;
if ( pPlayer->State_Get() != STATE_PICKINGCLASS )
m_iNumSpawnableTerrorist++;
if ( pPlayer->m_lifeState != LIFE_ALIVE )
NumDeadTerrorist++;
else
NumAliveTerrorist++;
// Check to see if this guy escaped.
if ( pPlayer->m_bEscaped == true )
m_iHaveEscaped++;
break;
}
}
}
}
bool CCSGameRules::HostageRescueRoundEndCheck( bool bNeededPlayers )
{
// Check to see if 50% of the hostages have been rescued.
CHostage* hostage = NULL;
int iNumHostages = g_Hostages.Count();
int iNumLeftToRescue = 0;
int i;
for ( i=0; i<iNumHostages; i++ )
{
hostage = g_Hostages[i];
if ( hostage->m_iHealth > 0 && !hostage->IsRescued() ) // We've found a live hostage. don't end the round
iNumLeftToRescue++;
}
m_iHostagesRemaining = iNumLeftToRescue;
if ( (iNumLeftToRescue == 0) && (iNumHostages > 0) )
{
if ( m_iHostagesRescued >= (iNumHostages * 0.5) )
{
m_iAccountCT += 2500;
if ( !bNeededPlayers )
{
m_iNumCTWins ++;
// Update the clients team score
UpdateTeamScores();
}
// tell the bots all the hostages have been rescued
IGameEvent * event = gameeventmanager->CreateEvent( "hostage_rescued_all" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
TerminateRound( 5, All_Hostages_Rescued );
return true;
}
}
return false;
}
bool CCSGameRules::PrisonRoundEndCheck()
{
//MIKETODO: get this working when working on prison escape
/*
if (m_bMapHasEscapeZone == true)
{
float flEscapeRatio;
flEscapeRatio = (float) m_iHaveEscaped / (float) m_iNumEscapers;
if (flEscapeRatio >= m_flRequiredEscapeRatio)
{
BroadcastSound( "Event.TERWin" );
m_iAccountTerrorist += 3150;
if ( !bNeededPlayers )
{
m_iNumTerroristWins ++;
// Update the clients team score
UpdateTeamScores();
}
EndRoundMessage( "#Terrorists_Escaped", Terrorists_Escaped );
TerminateRound( 5, WINNER_TER );
return;
}
else if ( NumAliveTerrorist == 0 && flEscapeRatio < m_flRequiredEscapeRatio)
{
BroadcastSound( "Event.CTWin" );
m_iAccountCT += (1 - flEscapeRatio) * 3500; // CTs are rewarded based on how many terrorists have escaped...
if ( !bNeededPlayers )
{
m_iNumCTWins++;
// Update the clients team score
UpdateTeamScores();
}
EndRoundMessage( "#CTs_PreventEscape", CTs_PreventEscape );
TerminateRound( 5, WINNER_CT );
return;
}
else if ( NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && m_iNumSpawnableCT > 0 )
{
BroadcastSound( "Event.CTWin" );
m_iAccountCT += (1 - flEscapeRatio) * 3250; // CTs are rewarded based on how many terrorists have escaped...
if ( !bNeededPlayers )
{
m_iNumCTWins++;
// Update the clients team score
UpdateTeamScores();
}
EndRoundMessage( "#Escaping_Terrorists_Neutralized", Escaping_Terrorists_Neutralized );
TerminateRound( 5, WINNER_CT );
return;
}
// else return;
}
*/
return false;
}
bool CCSGameRules::VIPRoundEndCheck( bool bNeededPlayers )
{
if (m_iMapHasVIPSafetyZone != 1)
return false;
if (m_pVIP == NULL)
return false;
if (m_pVIP->m_bEscaped == true)
{
m_iAccountCT += 3500;
if ( !bNeededPlayers )
{
m_iNumCTWins ++;
// Update the clients team score
UpdateTeamScores();
}
//MIKETODO: get this working when working on VIP scenarios
/*
MessageBegin( MSG_SPEC, SVC_DIRECTOR );
WRITE_BYTE ( 9 ); // command length in bytes
WRITE_BYTE ( DRC_CMD_EVENT ); // VIP rescued
WRITE_SHORT( ENTINDEX(m_pVIP->edict()) ); // index number of primary entity
WRITE_SHORT( 0 ); // index number of secondary entity
WRITE_LONG( 15 | DRC_FLAG_FINAL); // eventflags (priority and flags)
MessageEnd();
*/
// tell the bots the VIP got out
IGameEvent * event = gameeventmanager->CreateEvent( "vip_escaped" );
if ( event )
{
event->SetInt( "userid", m_pVIP->GetUserID() );
event->SetInt( "priority", 9 );
gameeventmanager->FireEvent( event );
}
TerminateRound( 5, VIP_Escaped );
return true;
}
else if ( m_pVIP->m_lifeState == LIFE_DEAD ) // The VIP is dead
{
m_iAccountTerrorist += 3250;
if ( !bNeededPlayers )
{
m_iNumTerroristWins ++;
// Update the clients team score
UpdateTeamScores();
}
// tell the bots the VIP was killed
IGameEvent * event = gameeventmanager->CreateEvent( "vip_killed" );
if ( event )
{
event->SetInt( "userid", m_pVIP->GetUserID() );
event->SetInt( "priority", 9 );
gameeventmanager->FireEvent( event );
}
TerminateRound( 5, VIP_Assassinated );
return true;
}
return false;
}
bool CCSGameRules::BombRoundEndCheck( bool bNeededPlayers )
{
// Check to see if the bomb target was hit or the bomb defused.. if so, then let's end the round!
if ( ( m_bTargetBombed == true ) && ( m_bMapHasBombTarget == true ) )
{
m_iAccountTerrorist += 3500;
if ( !bNeededPlayers )
{
m_iNumTerroristWins ++;
// Update the clients team score
UpdateTeamScores();
}
TerminateRound( 5, Target_Bombed );
return true;
}
else
if ( ( m_bBombDefused == true ) && ( m_bMapHasBombTarget == true ) )
{
m_iAccountCT += 3250;
m_iAccountTerrorist += 800; // give the T's a little bonus for planting the bomb even though it was defused.
if ( !bNeededPlayers )
{
m_iNumCTWins++;
// Update the clients team score
UpdateTeamScores();
}
TerminateRound( 5, Bomb_Defused );
return true;
}
return false;
}
bool CCSGameRules::TeamExterminationCheck(
int NumAliveTerrorist,
int NumAliveCT,
int NumDeadTerrorist,
int NumDeadCT,
bool bNeededPlayers
)
{
if ( ( m_iNumCT > 0 && m_iNumSpawnableCT > 0 ) && ( m_iNumTerrorist > 0 && m_iNumSpawnableTerrorist > 0 ) )
{
if ( NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && m_iNumSpawnableCT > 0 )
{
bool nowin = false;
for ( int iGrenade=0; iGrenade < g_PlantedC4s.Count(); iGrenade++ )
{
CPlantedC4 *pC4 = g_PlantedC4s[iGrenade];
if ( pC4->IsBombActive() )
nowin = true;
}
if ( !nowin )
{
if ( m_bMapHasBombTarget )
m_iAccountCT += 3250;
else
m_iAccountCT += 3000;
if ( !bNeededPlayers )
{
m_iNumCTWins++;
// Update the clients team score
UpdateTeamScores();
}
TerminateRound( 5, CTs_Win );
return true;
}
}
// Terrorists WON
if ( NumAliveCT == 0 && NumDeadCT != 0 && m_iNumSpawnableTerrorist > 0 )
{
if ( m_bMapHasBombTarget )
m_iAccountTerrorist += 3250;
else
m_iAccountTerrorist += 3000;
if ( !bNeededPlayers )
{
m_iNumTerroristWins++;
// Update the clients team score
UpdateTeamScores();
}
TerminateRound( 5, Terrorists_Win );
return true;
}
}
else if ( NumAliveCT == 0 && NumAliveTerrorist == 0 )
{
TerminateRound( 5, Round_Draw );
return true;
}
return false;
}
void CCSGameRules::PickNextVIP()
{
// MIKETODO: work on this when getting VIP maps running.
/*
if (IsVIPQueueEmpty() != true)
{
// Remove the current VIP from his VIP status and make him a regular CT.
if (m_pVIP != NULL)
ResetCurrentVIP();
for (int i = 0; i <= 4; i++)
{
if (VIPQueue[i] != NULL)
{
m_pVIP = VIPQueue[i];
m_pVIP->MakeVIP();
VIPQueue[i] = NULL; // remove this player from the VIP queue
StackVIPQueue(); // and re-organize the queue
m_iConsecutiveVIP = 0;
return;
}
}
}
else if (m_iConsecutiveVIP >= 3) // If it's been the same VIP for 3 rounds already.. then randomly pick a new one
{
m_iLastPick++;
if (m_iLastPick > m_iNumCT)
m_iLastPick = 1;
int iCount = 1;
CBaseEntity* pPlayer = NULL;
CBasePlayer* player = NULL;
CBasePlayer* pLastPlayer = NULL;
pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" );
while ( (pPlayer != NULL) && (!FNullEnt(pPlayer->edict())) )
{
if ( !(pPlayer->pev->flags & FL_DORMANT) )
{
player = GetClassPtr((CBasePlayer *)pPlayer->pev);
if ( (player->m_iTeam == CT) && (iCount == m_iLastPick) )
{
if ( (player == m_pVIP) && (pLastPlayer != NULL) )
player = pLastPlayer;
// Remove the current VIP from his VIP status and make him a regular CT.
if (m_pVIP != NULL)
ResetCurrentVIP();
player->MakeVIP();
m_iConsecutiveVIP = 0;
return;
}
else if ( player->m_iTeam == CT )
iCount++;
if ( player->m_iTeam != SPECTATOR )
pLastPlayer = player;
}
pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" );
}
}
else if (m_pVIP == NULL) // There is no VIP and there is no one waiting to be the VIP.. therefore just pick the first CT player we can find.
{
CBaseEntity* pPlayer = NULL;
CBasePlayer* player = NULL;
pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" );
while ( (pPlayer != NULL) && (!FNullEnt(pPlayer->edict())) )
{
if ( pPlayer->pev->flags != FL_DORMANT )
{
player = GetClassPtr((CBasePlayer *)pPlayer->pev);
if ( player->m_iTeam == CT )
{
player->MakeVIP();
m_iConsecutiveVIP = 0;
return;
}
}
pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" );
}
}
*/
}
void CCSGameRules::ReadMultiplayCvars()
{
m_iRoundTime = (int)(mp_roundtime.GetFloat() * 60);
m_iFreezeTime = mp_freezetime.GetInt();
}
void CCSGameRules::RestartRound()
{
if ( !IsFinite( gpGlobals->curtime ) )
{
Warning( "NaN curtime in RestartRound\n" );
gpGlobals->curtime = 0.0f;
}
int i;
m_iTotalRoundsPlayed++;
//ClearBodyQue();
// Hardlock the player accelaration to 5.0
//CVAR_SET_FLOAT( "sv_accelerate", 5.0 );
//CVAR_SET_FLOAT( "sv_friction", 4.0 );
//CVAR_SET_FLOAT( "sv_stopspeed", 75 );
sv_stopspeed.SetValue( 75.0f );
// Tabulate the number of players on each team.
int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT;
InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT );
m_bBombDropped = false;
m_bBombPlanted = false;
if ( GetHumanTeam() != TEAM_UNASSIGNED )
{
MoveHumansToHumanTeam();
}
/*************** AUTO-BALANCE CODE *************/
if ( mp_autoteambalance.GetInt() != 0 &&
(m_iUnBalancedRounds >= 1) )
{
if ( GetHumanTeam() == TEAM_UNASSIGNED )
{
BalanceTeams();
}
}
if ( ((m_iNumSpawnableCT - m_iNumSpawnableTerrorist) >= 2) ||
((m_iNumSpawnableTerrorist - m_iNumSpawnableCT) >= 2) )
{
m_iUnBalancedRounds++;
}
else
{
m_iUnBalancedRounds = 0;
}
// Warn the players of an impending auto-balance next round...
if ( mp_autoteambalance.GetInt() != 0 &&
(m_iUnBalancedRounds == 1) )
{
if ( GetHumanTeam() == TEAM_UNASSIGNED )
{
UTIL_ClientPrintAll( HUD_PRINTCENTER,"#Auto_Team_Balance_Next_Round");
}
}
/*************** AUTO-BALANCE CODE *************/
if ( m_bCompleteReset )
{
// bounds check
if ( mp_timelimit.GetInt() < 0 )
{
mp_timelimit.SetValue( 0 );
}
m_flGameStartTime = gpGlobals->curtime;
if ( !IsFinite( m_flGameStartTime.Get() ) )
{
Warning( "Trying to set a NaN game start time\n" );
m_flGameStartTime.GetForModify() = 0.0f;
}
// Reset total # of rounds played
m_iTotalRoundsPlayed = 0;
// Reset score info
m_iNumTerroristWins = 0;
m_iNumCTWins = 0;
m_iNumConsecutiveTerroristLoses = 0;
m_iNumConsecutiveCTLoses = 0;
// Reset team scores
UpdateTeamScores();
// Reset the player stats
for ( i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pPlayer = CCSPlayer::Instance( i );
if ( pPlayer && !FNullEnt( pPlayer->edict() ) )
pPlayer->Reset();
}
}
m_bFreezePeriod = true;
ReadMultiplayCvars();
// Check to see if there's a mapping info paramater entity
if ( g_pMapInfo )
{
switch ( g_pMapInfo->m_iBuyingStatus )
{
case 0:
m_bCTCantBuy = false;
m_bTCantBuy = false;
Msg( "EVERYONE CAN BUY!\n" );
break;
case 1:
m_bCTCantBuy = false;
m_bTCantBuy = true;
Msg( "Only CT's can buy!!\n" );
break;
case 2:
m_bCTCantBuy = true;
m_bTCantBuy = false;
Msg( "Only T's can buy!!\n" );
break;
case 3:
m_bCTCantBuy = true;
m_bTCantBuy = true;
Msg( "No one can buy!!\n" );
break;
default:
m_bCTCantBuy = false;
m_bTCantBuy = false;
break;
}
}
else
{
// by default everyone can buy
m_bCTCantBuy = false;
m_bTCantBuy = false;
}
// Check to see if this map has a bomb target in it
if ( gEntList.FindEntityByClassname( NULL, "func_bomb_target" ) )
{
m_bMapHasBombTarget = true;
m_bMapHasBombZone = true;
}
else if ( gEntList.FindEntityByClassname( NULL, "info_bomb_target" ) )
{
m_bMapHasBombTarget = true;
m_bMapHasBombZone = false;
}
else
{
m_bMapHasBombTarget = false;
m_bMapHasBombZone = false;
}
// Check to see if this map has hostage rescue zones
if ( gEntList.FindEntityByClassname( NULL, "func_hostage_rescue" ) )
m_bMapHasRescueZone = true;
else
m_bMapHasRescueZone = false;
// See if the map has func_buyzone entities
// Used by CBasePlayer::HandleSignals() to support maps without these entities
if ( gEntList.FindEntityByClassname( NULL, "func_buyzone" ) )
m_bMapHasBuyZone = true;
else
m_bMapHasBuyZone = false;
// GOOSEMAN : See if this map has func_escapezone entities
if ( gEntList.FindEntityByClassname( NULL, "func_escapezone" ) )
{
m_bMapHasEscapeZone = true;
m_iHaveEscaped = 0;
m_iNumEscapers = 0; // Will increase this later when we count how many Ts are starting
if (m_iNumEscapeRounds >= 3)
{
SwapAllPlayers();
m_iNumEscapeRounds = 0;
}
m_iNumEscapeRounds++; // Increment the number of rounds played... After 8 rounds, the players will do a whole sale switch..
}
else
m_bMapHasEscapeZone = false;
// Check to see if this map has VIP safety zones
if ( gEntList.FindEntityByClassname( NULL, "func_vip_safetyzone" ) )
{
PickNextVIP();
m_iConsecutiveVIP++;
m_iMapHasVIPSafetyZone = 1;
}
else
m_iMapHasVIPSafetyZone = 2;
// Update accounts based on number of hostages remaining..
int iRescuedHostageBonus = 0;
for ( int iHostage=0; iHostage < g_Hostages.Count(); iHostage++ )
{
CHostage *pHostage = g_Hostages[iHostage];
if( pHostage->IsRescuable() ) //Alive and not rescued
{
iRescuedHostageBonus += 150;
}
if ( iRescuedHostageBonus >= 2000 )
break;
}
//*******Catch up code by SupraFiend. Scale up the loser bonus when teams fall into losing streaks
if (m_iRoundWinStatus == WINNER_TER) // terrorists won
{
//check to see if they just broke a losing streak
if(m_iNumConsecutiveTerroristLoses > 1)
m_iLoserBonus = 1500;//this is the default losing bonus
m_iNumConsecutiveTerroristLoses = 0;//starting fresh
m_iNumConsecutiveCTLoses++;//increment the number of wins the CTs have had
}
else if (m_iRoundWinStatus == WINNER_CT) // CT Won
{
//check to see if they just broke a losing streak
if(m_iNumConsecutiveCTLoses > 1)
m_iLoserBonus = 1500;//this is the default losing bonus
m_iNumConsecutiveCTLoses = 0;//starting fresh
m_iNumConsecutiveTerroristLoses++;//increment the number of wins the Terrorists have had
}
//check if the losing team is in a losing streak & that the loser bonus hasen't maxed out.
if((m_iNumConsecutiveTerroristLoses > 1) && (m_iLoserBonus < 3000))
m_iLoserBonus += 500;//help out the team in the losing streak
else
if((m_iNumConsecutiveCTLoses > 1) && (m_iLoserBonus < 3000))
m_iLoserBonus += 500;//help out the team in the losing streak
// assign the wining and losing bonuses
if (m_iRoundWinStatus == WINNER_TER) // terrorists won
{
m_iAccountTerrorist += iRescuedHostageBonus;
m_iAccountCT += m_iLoserBonus;
}
else if (m_iRoundWinStatus == WINNER_CT) // CT Won
{
m_iAccountCT += iRescuedHostageBonus;
if (m_bMapHasEscapeZone == false) // only give them the bonus if this isn't an escape map
m_iAccountTerrorist += m_iLoserBonus;
}
//Update CT account based on number of hostages rescued
m_iAccountCT += m_iHostagesRescued * 750;
// Update individual players accounts and respawn players
//**********new code by SupraFiend
//##########code changed by MartinO
//the round time stamp must be set before players are spawned
m_fRoundStartTime = gpGlobals->curtime + m_iFreezeTime;
if ( !IsFinite( m_fRoundStartTime.Get() ) )
{
Warning( "Trying to set a NaN round start time\n" );
m_fRoundStartTime.GetForModify() = 0.0f;
}
//Adrian - No cash for anyone at first rounds! ( well, only the default. )
if ( m_bCompleteReset )
{
m_iAccountTerrorist = m_iAccountCT = 0; //No extra cash!.
//We are starting fresh. So it's like no one has ever won or lost.
m_iNumTerroristWins = 0;
m_iNumCTWins = 0;
m_iNumConsecutiveTerroristLoses = 0;
m_iNumConsecutiveCTLoses = 0;
m_iLoserBonus = 1400;
}
for ( i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
pPlayer->m_iNumSpawns = 0;
pPlayer->m_bTeamChanged = false;
if ( pPlayer->GetTeamNumber() == TEAM_CT )
{
if (pPlayer->DoesPlayerGetRoundStartMoney())
{
pPlayer->AddAccount( m_iAccountCT );
}
}
else if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST )
{
m_iNumEscapers++; // Add another potential escaper to the mix!
if (pPlayer->DoesPlayerGetRoundStartMoney())
{
pPlayer->AddAccount( m_iAccountTerrorist );
}
}
// tricky, make players non solid while moving to their spawn points
if ( (pPlayer->GetTeamNumber() == TEAM_CT) || (pPlayer->GetTeamNumber() == TEAM_TERRORIST) )
{
pPlayer->AddSolidFlags( FSOLID_NOT_SOLID );
}
}
// know respawn all players
for ( i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
if ( pPlayer->GetTeamNumber() == TEAM_CT && pPlayer->PlayerClass() >= FIRST_CT_CLASS && pPlayer->PlayerClass() <= LAST_CT_CLASS )
{
pPlayer->RoundRespawn();
}
if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST && pPlayer->PlayerClass() >= FIRST_T_CLASS && pPlayer->PlayerClass() <= LAST_T_CLASS )
{
pPlayer->RoundRespawn();
}
else
{
pPlayer->ObserverRoundRespawn();
}
if ( pPlayer->m_iAccount > pPlayer->m_iShouldHaveCash )
{
m_bDontUploadStats = true;
}
}
// Respawn entities (glass, doors, etc..)
CleanUpMap();
// now run a tkpunish check, after the map has been cleaned up
for ( i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
if ( pPlayer->GetTeamNumber() == TEAM_CT && pPlayer->PlayerClass() >= FIRST_CT_CLASS && pPlayer->PlayerClass() <= LAST_CT_CLASS )
{
pPlayer->CheckTKPunishment();
}
if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST && pPlayer->PlayerClass() >= FIRST_T_CLASS && pPlayer->PlayerClass() <= LAST_T_CLASS )
{
pPlayer->CheckTKPunishment();
}
}
// Give C4 to the terrorists
if (m_bMapHasBombTarget == true )
GiveC4();
// Reset game variables
m_flIntermissionEndTime = 0;
m_flRestartRoundTime = 0.0;
m_iAccountTerrorist = m_iAccountCT = 0;
m_iHostagesRescued = 0;
m_iHostagesTouched = 0;
m_iHostagesRemaining = 0;
m_iRoundWinStatus = WINNER_NONE;
m_bTargetBombed = m_bBombDefused = false;
m_bCompleteReset = false;
m_flNextHostageAnnouncement = gpGlobals->curtime;
m_iHostagesRemaining = g_Hostages.Count();
// fire global game event
IGameEvent * event = gameeventmanager->CreateEvent( "round_start" );
if ( event )
{
event->SetInt("timelimit", m_iRoundTime );
event->SetInt("fraglimit", 0 );
event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted
if ( m_bMapHasRescueZone )
{
event->SetString("objective","HOSTAGE RESCUE");
}
else if ( m_bMapHasEscapeZone )
{
event->SetString("objective","PRISON ESCAPE");
}
else if ( m_iMapHasVIPSafetyZone == 1 )
{
event->SetString("objective","VIP RESCUE");
}
else if ( m_bMapHasBombTarget || m_bMapHasBombZone )
{
event->SetString("objective","BOMB TARGET");
}
else
{
event->SetString("objective","DEATHMATCH");
}
gameeventmanager->FireEvent( event );
}
UploadGameStats();
CreateWeaponManager( "weapon_*", gpGlobals->maxClients * 2 );
}
void CCSGameRules::GiveC4()
{
enum {
ALL_TERRORISTS = 0,
HUMAN_TERRORISTS,
};
int iTerrorists[2][ABSOLUTE_PLAYER_LIMIT];
int numAliveTs[2] = { 0, 0 };
int lastBombGuyIndex[2] = { -1, -1 };
//Create an array of the indeces of bomb carrier candidates
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
if( pPlayer && pPlayer->IsAlive() && pPlayer->GetTeamNumber() == TEAM_TERRORIST && numAliveTs[ALL_TERRORISTS] < ABSOLUTE_PLAYER_LIMIT )
{
if ( pPlayer == m_pLastBombGuy )
{
lastBombGuyIndex[ALL_TERRORISTS] = numAliveTs[ALL_TERRORISTS];
lastBombGuyIndex[HUMAN_TERRORISTS] = numAliveTs[HUMAN_TERRORISTS];
}
iTerrorists[ALL_TERRORISTS][numAliveTs[ALL_TERRORISTS]] = i;
numAliveTs[ALL_TERRORISTS]++;
if ( !pPlayer->IsBot() )
{
iTerrorists[HUMAN_TERRORISTS][numAliveTs[HUMAN_TERRORISTS]] = i;
numAliveTs[HUMAN_TERRORISTS]++;
}
}
}
int which = cv_bot_defer_to_human.GetBool();
if ( numAliveTs[HUMAN_TERRORISTS] == 0 )
{
which = ALL_TERRORISTS;
}
//pick one of the candidates randomly
if( numAliveTs[which] > 0 )
{
int index = random->RandomInt(0,numAliveTs[which]-1);
if ( lastBombGuyIndex[which] >= 0 )
{
// give the C4 sequentially
index = (lastBombGuyIndex[which] + 1) % numAliveTs[which];
}
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iTerrorists[which][index] ) );
Assert( pPlayer && pPlayer->GetTeamNumber() == TEAM_TERRORIST && pPlayer->IsAlive() );
pPlayer->GiveNamedItem( WEAPON_C4_CLASSNAME );
m_pLastBombGuy = pPlayer;
//pPlayer->SetBombIcon();
//pPlayer->pev->body = 1;
pPlayer->m_iDisplayHistoryBits |= DHF_BOMB_RETRIEVED;
pPlayer->HintMessage( "#Hint_you_have_the_bomb", false, true );
// Log this information
//UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Spawned_With_The_Bomb\"\n",
// STRING( pPlayer->GetPlayerName() ),
// GETPLAYERUSERID( pPlayer->edict() ),
// GETPLAYERAUTHID( pPlayer->edict() ) );
}
m_bBombDropped = false;
}
void CCSGameRules::Think()
{
CGameRules::Think();
for ( int i = 0; i < GetNumberOfTeams(); i++ )
{
GetGlobalTeam( i )->Think();
}
///// Check game rules /////
if ( CheckGameOver() )
{
return;
}
// have we hit the max rounds?
if ( CheckMaxRounds() )
{
return;
}
// did somebaody hit the fraglimit ?
if ( CheckFragLimit() )
{
return;
}
if ( CheckWinLimit() )
{
return;
}
// Check for the end of the round.
if ( IsFreezePeriod() )
{
CheckFreezePeriodExpired();
}
else
{
CheckRoundTimeExpired();
}
CheckLevelInitialized();
if ( m_flRestartRoundTime > 0.0f && m_flRestartRoundTime <= gpGlobals->curtime )
{
bool botSpeaking = false;
for ( int i=1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = UTIL_PlayerByIndex( i );
if (player == NULL)
continue;
if (!player->IsBot())
continue;
CCSBot *bot = dynamic_cast< CCSBot * >(player);
if ( !bot )
continue;
if ( bot->IsUsingVoice() )
{
if ( gpGlobals->curtime > m_flRestartRoundTime + 10.0f )
{
Msg( "Ignoring speaking bot %s at round end\n", bot->GetPlayerName() );
}
else
{
botSpeaking = true;
break;
}
}
}
if ( !botSpeaking )
{
RestartRound();
}
}
if ( gpGlobals->curtime > m_tmNextPeriodicThink )
{
CheckRestartRound();
m_tmNextPeriodicThink = gpGlobals->curtime + 1.0;
}
}
// The bots do their processing after physics simulation etc so their visibility checks don't recompute
// bone positions multiple times a frame.
void CCSGameRules::EndGameFrame( void )
{
TheBots->StartFrame();
BaseClass::EndGameFrame();
}
bool CCSGameRules::CheckGameOver()
{
if ( g_fGameOver ) // someone else quit the game already
{
// check to see if we should change levels now
if ( m_flIntermissionEndTime < gpGlobals->curtime )
{
ChangeLevel(); // intermission is over
}
return true;
}
return false;
}
bool CCSGameRules::CheckFragLimit()
{
if ( fraglimit.GetInt() <= 0 )
return false;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( pPlayer && pPlayer->FragCount() >= fraglimit.GetInt() )
{
const char *teamName = "UNKNOWN";
if ( pPlayer->GetTeam() )
{
teamName = pPlayer->GetTeam()->GetName();
}
UTIL_LogPrintf("\"%s<%i><%s><%s>\" triggered \"Intermission_Kill_Limit\"\n",
pPlayer->GetPlayerName(),
pPlayer->GetUserID(),
pPlayer->GetNetworkIDString(),
teamName
);
GoToIntermission();
return true;
}
}
return false;
}
bool CCSGameRules::CheckMaxRounds()
{
if ( mp_maxrounds.GetInt() != 0 )
{
if ( m_iTotalRoundsPlayed >= mp_maxrounds.GetInt() )
{
UTIL_LogPrintf("World triggered \"Intermission_Round_Limit\"\n");
GoToIntermission();
return true;
}
}
return false;
}
bool CCSGameRules::CheckWinLimit()
{
// has one team won the specified number of rounds?
if ( mp_winlimit.GetInt() != 0 )
{
if ( m_iNumCTWins >= mp_winlimit.GetInt() )
{
UTIL_LogPrintf("Team \"CT\" triggered \"Intermission_Win_Limit\"\n");
GoToIntermission();
return true;
}
if ( m_iNumTerroristWins >= mp_winlimit.GetInt() )
{
UTIL_LogPrintf("Team \"TERRORIST\" triggered \"Intermission_Win_Limit\"\n");
GoToIntermission();
return true;
}
}
return false;
}
void CCSGameRules::CheckFreezePeriodExpired()
{
float startTime = m_fRoundStartTime;
if ( !IsFinite( startTime ) )
{
Warning( "Infinite round start time!\n" );
m_fRoundStartTime.GetForModify() = gpGlobals->curtime;
}
if ( IsFinite( startTime ) && gpGlobals->curtime < startTime )
{
return; // not time yet to start round
}
// Log this information
UTIL_LogPrintf("World triggered \"Round_Start\"\n");
char CT_sentence[40];
char T_sentence[40];
switch ( random->RandomInt( 0, 3 ) )
{
case 0:
Q_strncpy(CT_sentence,"radio.moveout", sizeof( CT_sentence ) );
Q_strncpy(T_sentence ,"radio.moveout", sizeof( T_sentence ) );
break;
case 1:
Q_strncpy(CT_sentence, "radio.letsgo", sizeof( CT_sentence ) );
Q_strncpy(T_sentence , "radio.letsgo", sizeof( T_sentence ) );
break;
case 2:
Q_strncpy(CT_sentence , "radio.locknload", sizeof( CT_sentence ) );
Q_strncpy(T_sentence , "radio.locknload", sizeof( T_sentence ) );
break;
default:
Q_strncpy(CT_sentence , "radio.go", sizeof( CT_sentence ) );
Q_strncpy(T_sentence , "radio.go", sizeof( T_sentence ) );
break;
}
// More specific radio commands for the new scenarios : Prison & Assasination
if (m_bMapHasEscapeZone == TRUE)
{
Q_strncpy(CT_sentence , "radio.elim", sizeof( CT_sentence ) );
Q_strncpy(T_sentence , "radio.getout", sizeof( T_sentence ) );
}
else if (m_iMapHasVIPSafetyZone == 1)
{
Q_strncpy(CT_sentence , "radio.vip", sizeof( CT_sentence ) );
Q_strncpy(T_sentence , "radio.locknload", sizeof( T_sentence ) );
}
// Freeze period expired: kill the flag
m_bFreezePeriod = false;
IGameEvent * event = gameeventmanager->CreateEvent( "round_freeze_end" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
// Update the timers for all clients and play a sound
bool bCTPlayed = false;
bool bTPlayed = false;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pPlayer = CCSPlayer::Instance( i );
if ( pPlayer && !FNullEnt( pPlayer->edict() ) )
{
if ( pPlayer->State_Get() == STATE_ACTIVE )
{
if ( (pPlayer->GetTeamNumber() == TEAM_CT) && !bCTPlayed )
{
pPlayer->Radio( CT_sentence );
bCTPlayed = true;
}
else if ( (pPlayer->GetTeamNumber() == TEAM_TERRORIST) && !bTPlayed )
{
pPlayer->Radio( T_sentence );
bTPlayed = true;
}
if ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
{
pPlayer->ResetMaxSpeed();
}
}
//pPlayer->SyncRoundTimer();
}
}
}
void CCSGameRules::CheckRoundTimeExpired()
{
if ( GetRoundRemainingTime() > 0 || m_iRoundWinStatus != WINNER_NONE )
return; //We haven't completed other objectives, so go for this!.
if( !m_bFirstConnected )
return;
// New code to get rid of round draws!!
if ( m_bMapHasBombTarget )
{
//If the bomb is planted, don't let the round timer end the round.
//keep going until the bomb explodes or is defused
if( !m_bBombPlanted )
{
m_iAccountCT += 3250;
m_iNumCTWins++;
TerminateRound( 5, Target_Saved );
UpdateTeamScores();
MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(TEAM_TERRORIST);
}
}
else if ( m_bMapHasRescueZone )
{
m_iAccountTerrorist += 3250;
m_iNumTerroristWins++;
TerminateRound( 5, Hostages_Not_Rescued );
UpdateTeamScores();
MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(TEAM_CT);
}
else if ( m_bMapHasEscapeZone )
{
m_iNumCTWins++;
TerminateRound( 5, Terrorists_Not_Escaped );
UpdateTeamScores();
}
else if ( m_iMapHasVIPSafetyZone == 1 )
{
m_iAccountTerrorist += 3250;
m_iNumTerroristWins++;
TerminateRound( 5, VIP_Not_Escaped );
UpdateTeamScores();
}
}
void CCSGameRules::GoToIntermission( void )
{
Msg( "Going to intermission...\n" );
BaseClass::GoToIntermission();
// set all players to FL_FROZEN
for ( int i = 1; i <= MAX_PLAYERS; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( pPlayer )
{
pPlayer->AddFlag( FL_FROZEN );
}
}
// freeze players while in intermission
m_bFreezePeriod = true;
}
static void PrintToConsole( CBasePlayer *player, const char *text )
{
if ( player )
{
ClientPrint( player, HUD_PRINTCONSOLE, text );
}
else
{
Msg( "%s", text );
}
}
void CCSGameRules::DumpTimers( void ) const
{
extern ConVar bot_join_delay;
CBasePlayer *player = UTIL_GetCommandClient();
CFmtStr str;
PrintToConsole( player, str.sprintf( "Timers and related info at %f:\n", gpGlobals->curtime ) );
PrintToConsole( player, str.sprintf( "m_bCompleteReset: %d\n", m_bCompleteReset ) );
PrintToConsole( player, str.sprintf( "m_iTotalRoundsPlayed: %d\n", m_iTotalRoundsPlayed ) );
PrintToConsole( player, str.sprintf( "m_iRoundTime: %d\n", m_iRoundTime.Get() ) );
PrintToConsole( player, str.sprintf( "m_iRoundWinStatus: %d\n", m_iRoundWinStatus ) );
PrintToConsole( player, str.sprintf( "first connected: %d\n", m_bFirstConnected ) );
PrintToConsole( player, str.sprintf( "intermission end time: %f\n", m_flIntermissionEndTime ) );
PrintToConsole( player, str.sprintf( "freeze period: %d\n", m_bFreezePeriod.Get() ) );
PrintToConsole( player, str.sprintf( "round restart time: %f\n", m_flRestartRoundTime ) );
PrintToConsole( player, str.sprintf( "game start time: %f\n", m_flGameStartTime.Get() ) );
PrintToConsole( player, str.sprintf( "m_fRoundStartTime: %f\n", m_fRoundStartTime.Get() ) );
PrintToConsole( player, str.sprintf( "freeze time: %d\n", m_iFreezeTime ) );
PrintToConsole( player, str.sprintf( "next think: %f\n", m_tmNextPeriodicThink ) );
PrintToConsole( player, str.sprintf( "fraglimit: %d\n", fraglimit.GetInt() ) );
PrintToConsole( player, str.sprintf( "mp_maxrounds: %d\n", mp_maxrounds.GetInt() ) );
PrintToConsole( player, str.sprintf( "mp_winlimit: %d\n", mp_winlimit.GetInt() ) );
PrintToConsole( player, str.sprintf( "bot_quota: %d\n", cv_bot_quota.GetInt() ) );
PrintToConsole( player, str.sprintf( "bot_quota_mode: %s\n", cv_bot_quota_mode.GetString() ) );
PrintToConsole( player, str.sprintf( "bot_join_after_player: %d\n", cv_bot_join_after_player.GetInt() ) );
PrintToConsole( player, str.sprintf( "bot_join_delay: %d\n", bot_join_delay.GetInt() ) );
PrintToConsole( player, str.sprintf( "nextlevel: %s\n", nextlevel.GetString() ) );
int humansInGame = UTIL_HumansInGame( true );
int botsInGame = UTIL_BotsInGame();
PrintToConsole( player, str.sprintf( "%d humans and %d bots in game\n", humansInGame, botsInGame ) );
PrintToConsole( player, str.sprintf( "num CTs (spawnable): %d (%d)\n", m_iNumCT, m_iNumSpawnableCT ) );
PrintToConsole( player, str.sprintf( "num Ts (spawnable): %d (%d)\n", m_iNumTerrorist, m_iNumSpawnableTerrorist ) );
if ( g_fGameOver )
{
PrintToConsole( player, str.sprintf( "Game is over!\n" ) );
}
PrintToConsole( player, str.sprintf( "\n" ) );
}
CON_COMMAND( mp_dump_timers, "Prints round timers to the console for debugging" )
{
if ( CSGameRules() )
{
CSGameRules()->DumpTimers();
}
}
// living players on the given team need to be marked as not receiving any money
// next round.
void CCSGameRules::MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(int team)
{
int playerNum;
for (playerNum = 1; playerNum <= gpGlobals->maxClients; ++playerNum)
{
CCSPlayer *player = (CCSPlayer *)UTIL_PlayerByIndex(playerNum);
if (player == NULL)
{
continue;
}
if ((player->GetTeamNumber() == team) && (player->IsAlive()))
{
player->MarkAsNotReceivingMoneyNextRound();
}
}
}
void CCSGameRules::CheckLevelInitialized( void )
{
if ( !m_bLevelInitialized )
{
// Count the number of spawn points for each team
// This determines the maximum number of players allowed on each
CBaseEntity* ent = NULL;
m_iSpawnPointCount_Terrorist = 0;
m_iSpawnPointCount_CT = 0;
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_terrorist" ) ) != NULL )
{
if ( IsSpawnPointValid( ent, NULL ) )
{
m_iSpawnPointCount_Terrorist++;
}
else
{
Warning("Invalid terrorist spawnpoint at (%.1f,%.1f,%.1f)\n",
ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[2],ent->GetAbsOrigin()[2] );
}
}
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_counterterrorist" ) ) != NULL )
{
if ( IsSpawnPointValid( ent, NULL ) )
{
m_iSpawnPointCount_CT++;
}
else
{
Warning("Invalid counterterrorist spawnpoint at (%.1f,%.1f,%.1f)\n",
ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[2],ent->GetAbsOrigin()[2] );
}
}
// Is this a logo map?
if ( gEntList.FindEntityByClassname( NULL, "info_player_logo" ) )
m_bLogoMap = true;
m_bLevelInitialized = true;
}
}
void CCSGameRules::ShowSpawnPoints( void )
{
CBaseEntity* ent = NULL;
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_terrorist" ) ) != NULL )
{
if ( IsSpawnPointValid( ent, NULL ) )
{
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 200, 600 );
}
else
{
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, 600);
}
}
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_counterterrorist" ) ) != NULL )
{
if ( IsSpawnPointValid( ent, NULL ) )
{
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 200, 600 );
}
else
{
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, 600 );
}
}
}
void CCSGameRules::CheckRestartRound( void )
{
// Restart the game if specified by the server
int iRestartDelay = mp_restartgame.GetInt();
if ( iRestartDelay > 0 )
{
if ( iRestartDelay > 60 )
iRestartDelay = 60;
// log the restart
UTIL_LogPrintf( "World triggered \"Restart_Round_(%i_%s)\"\n", iRestartDelay, iRestartDelay == 1 ? "second" : "seconds" );
UTIL_LogPrintf( "Team \"CT\" scored \"%i\" with \"%i\" players\n", m_iNumCTWins, m_iNumCT );
UTIL_LogPrintf( "Team \"TERRORIST\" scored \"%i\" with \"%i\" players\n", m_iNumTerroristWins, m_iNumTerrorist );
// let the players know
char strRestartDelay[64];
Q_snprintf( strRestartDelay, sizeof( strRestartDelay ), "%d", iRestartDelay );
UTIL_ClientPrintAll( HUD_PRINTCENTER, "#Game_will_restart_in", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" );
UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#Game_will_restart_in", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" );
m_flRestartRoundTime = gpGlobals->curtime + iRestartDelay;
m_bCompleteReset = true;
mp_restartgame.SetValue( 0 );
}
}
class SetHumanTeamFunctor
{
public:
SetHumanTeamFunctor( int targetTeam )
{
m_targetTeam = targetTeam;
m_sourceTeam = ( m_targetTeam == TEAM_CT ) ? TEAM_TERRORIST : TEAM_CT;
m_traitors.MakeReliable();
m_loyalists.MakeReliable();
m_loyalists.AddAllPlayers();
}
bool operator()( CBasePlayer *basePlayer )
{
CCSPlayer *player = ToCSPlayer( basePlayer );
if ( !player )
return true;
if ( player->IsBot() )
return true;
if ( player->GetTeamNumber() != m_sourceTeam )
return true;
if ( player->State_Get() == STATE_PICKINGCLASS )
return true;
if ( CSGameRules()->TeamFull( m_targetTeam ) )
return false;
if ( CSGameRules()->TeamStacked( m_targetTeam, m_sourceTeam ) )
return false;
player->SwitchTeam( m_targetTeam );
m_traitors.AddRecipient( player );
m_loyalists.RemoveRecipient( player );
return true;
}
void SendNotice( void )
{
if ( m_traitors.GetRecipientCount() > 0 )
{
UTIL_ClientPrintFilter( m_traitors, HUD_PRINTCENTER, "#Player_Balanced" );
UTIL_ClientPrintFilter( m_loyalists, HUD_PRINTCENTER, "#Teams_Balanced" );
}
}
private:
int m_targetTeam;
int m_sourceTeam;
CRecipientFilter m_traitors;
CRecipientFilter m_loyalists;
};
void CCSGameRules::MoveHumansToHumanTeam( void )
{
int targetTeam = GetHumanTeam();
if ( targetTeam != TEAM_TERRORIST && targetTeam != TEAM_CT )
return;
SetHumanTeamFunctor setTeam( targetTeam );
ForEachPlayer( setTeam );
setTeam.SendNotice();
}
void CCSGameRules::BalanceTeams( void )
{
int iTeamToSwap = TEAM_UNASSIGNED;
int iNumToSwap;
if (m_iMapHasVIPSafetyZone == 1) // The ratio for teams is different for Assasination maps
{
int iDesiredNumCT, iDesiredNumTerrorist;
if ( (m_iNumCT + m_iNumTerrorist)%2 != 0) // uneven number of players
iDesiredNumCT = (int)((m_iNumCT + m_iNumTerrorist) * 0.55) + 1;
else
iDesiredNumCT = (int)((m_iNumCT + m_iNumTerrorist)/2);
iDesiredNumTerrorist = (m_iNumCT + m_iNumTerrorist) - iDesiredNumCT;
if ( m_iNumCT < iDesiredNumCT )
{
iTeamToSwap = TEAM_TERRORIST;
iNumToSwap = iDesiredNumCT - m_iNumCT;
}
else if ( m_iNumTerrorist < iDesiredNumTerrorist )
{
iTeamToSwap = TEAM_CT;
iNumToSwap = iDesiredNumTerrorist - m_iNumTerrorist;
}
else
return;
}
else
{
if (m_iNumCT > m_iNumTerrorist)
{
iTeamToSwap = TEAM_CT;
iNumToSwap = (m_iNumCT - m_iNumTerrorist)/2;
}
else if (m_iNumTerrorist > m_iNumCT)
{
iTeamToSwap = TEAM_TERRORIST;
iNumToSwap = (m_iNumTerrorist - m_iNumCT)/2;
}
else
{
return; // Teams are even.. Get out of here.
}
}
if (iNumToSwap > 3) // Don't swap more than 3 players at a time.. This is a naive method of avoiding infinite loops.
iNumToSwap = 3;
int iTragetTeam = TEAM_UNASSIGNED;
if ( iTeamToSwap == TEAM_CT )
{
iTragetTeam = TEAM_TERRORIST;
}
else if ( iTeamToSwap == TEAM_TERRORIST )
{
iTragetTeam = TEAM_CT;
}
else
{
// no valid team to swap
return;
}
CRecipientFilter traitors;
CRecipientFilter loyalists;
traitors.MakeReliable();
loyalists.MakeReliable();
loyalists.AddAllPlayers();
for (int i = 0; i < iNumToSwap; i++)
{
// last person to join the server
int iHighestUserID = -1;
CCSPlayer *pPlayerToSwap = NULL;
// check if target team is full, exit if so
if ( TeamFull(iTragetTeam) )
break;
// search for player with highest UserID = most recently joined to switch over
for ( int j = 1; j <= gpGlobals->maxClients; j++ )
{
CCSPlayer *pPlayer = (CCSPlayer *)UTIL_PlayerByIndex( j );
if ( !pPlayer )
continue;
CCSBot *bot = dynamic_cast< CCSBot * >(pPlayer);
if ( bot )
continue; // don't swap bots - the bot system will handle that
if ( pPlayer &&
( m_pVIP != pPlayer ) &&
( pPlayer->GetTeamNumber() == iTeamToSwap ) &&
( engine->GetPlayerUserId( pPlayer->edict() ) > iHighestUserID ) &&
( pPlayer->State_Get() != STATE_PICKINGCLASS ) )
{
iHighestUserID = engine->GetPlayerUserId( pPlayer->edict() );
pPlayerToSwap = pPlayer;
}
}
if ( pPlayerToSwap != NULL )
{
traitors.AddRecipient( pPlayerToSwap );
loyalists.RemoveRecipient( pPlayerToSwap );
pPlayerToSwap->SwitchTeam( iTragetTeam );
}
}
if ( traitors.GetRecipientCount() > 0 )
{
UTIL_ClientPrintFilter( traitors, HUD_PRINTCENTER, "#Player_Balanced" );
UTIL_ClientPrintFilter( loyalists, HUD_PRINTCENTER, "#Teams_Balanced" );
}
}
bool CCSGameRules::TeamFull( int team_id )
{
CheckLevelInitialized();
switch ( team_id )
{
case TEAM_TERRORIST:
return m_iNumTerrorist >= m_iSpawnPointCount_Terrorist;
case TEAM_CT:
return m_iNumCT >= m_iSpawnPointCount_CT;
}
return false;
}
int CCSGameRules::GetHumanTeam()
{
if ( FStrEq( "CT", mp_humanteam.GetString() ) )
{
return TEAM_CT;
}
else if ( FStrEq( "T", mp_humanteam.GetString() ) )
{
return TEAM_TERRORIST;
}
return TEAM_UNASSIGNED;
}
int CCSGameRules::SelectDefaultTeam( bool ignoreBots /*= false*/ )
{
if ( ignoreBots && ( FStrEq( cv_bot_join_team.GetString(), "T" ) || FStrEq( cv_bot_join_team.GetString(), "CT" ) ) )
{
ignoreBots = false; // don't ignore bots when they can't switch teams
}
if ( ignoreBots && !mp_autoteambalance.GetBool() )
{
ignoreBots = false; // don't ignore bots when they can't switch teams
}
int team = TEAM_UNASSIGNED;
int numTerrorists = m_iNumTerrorist;
int numCTs = m_iNumCT;
if ( ignoreBots )
{
numTerrorists = UTIL_HumansOnTeam( TEAM_TERRORIST );
numCTs = UTIL_HumansOnTeam( TEAM_CT );
}
// Choose the team that's lacking players
if ( numTerrorists < numCTs )
{
team = TEAM_TERRORIST;
}
else if ( numTerrorists > numCTs )
{
team = TEAM_CT;
}
// Choose the team that's losing
else if ( m_iNumTerroristWins < m_iNumCTWins )
{
team = TEAM_TERRORIST;
}
else if ( m_iNumCTWins < m_iNumTerroristWins )
{
team = TEAM_CT;
}
else
{
// Teams and scores are equal, pick a random team
if ( random->RandomInt( 0, 1 ) == 0 )
{
team = TEAM_CT;
}
else
{
team = TEAM_TERRORIST;
}
}
if ( TeamFull( team ) )
{
// Pick the opposite team
if ( team == TEAM_TERRORIST )
{
team = TEAM_CT;
}
else
{
team = TEAM_TERRORIST;
}
// No choices left
if ( TeamFull( team ) )
return TEAM_UNASSIGNED;
}
return team;
}
//checks to see if the desired team is stacked, returns true if it is
bool CCSGameRules::TeamStacked( int newTeam_id, int curTeam_id )
{
//players are allowed to change to their own team
if(newTeam_id == curTeam_id)
return false;
// if mp_limitteams is 0, don't check
if ( mp_limitteams.GetInt() == 0 )
return false;
switch ( newTeam_id )
{
case TEAM_TERRORIST:
if(curTeam_id != TEAM_UNASSIGNED && curTeam_id != TEAM_SPECTATOR)
{
if((m_iNumTerrorist + 1) > (m_iNumCT + mp_limitteams.GetInt() - 1))
return true;
else
return false;
}
else
{
if((m_iNumTerrorist + 1) > (m_iNumCT + mp_limitteams.GetInt()))
return true;
else
return false;
}
break;
case TEAM_CT:
if(curTeam_id != TEAM_UNASSIGNED && curTeam_id != TEAM_SPECTATOR)
{
if((m_iNumCT + 1) > (m_iNumTerrorist + mp_limitteams.GetInt() - 1))
return true;
else
return false;
}
else
{
if((m_iNumCT + 1) > (m_iNumTerrorist + mp_limitteams.GetInt()))
return true;
else
return false;
}
break;
}
return false;
}
//=========================================================
//=========================================================
bool CCSGameRules::FPlayerCanRespawn( CBasePlayer *pBasePlayer )
{
CCSPlayer *pPlayer = ToCSPlayer( pBasePlayer );
if ( !pPlayer )
Error( "FPlayerCanRespawn: pPlayer=0" );
// Player cannot respawn twice in a round
if ( pPlayer->m_iNumSpawns > 0 && m_bFirstConnected )
return false;
// If they're dead after the map has ended, and it's about to start the next round,
// wait for the round restart to respawn them.
if ( gpGlobals->curtime < m_flRestartRoundTime )
return false;
// Only valid team members can spawn
if ( pPlayer->GetTeamNumber() != TEAM_CT && pPlayer->GetTeamNumber() != TEAM_TERRORIST )
return false;
// Only players with a valid class can spawn
if ( pPlayer->GetClass() == CS_CLASS_NONE )
return false;
// Player cannot respawn until next round if more than 20 seconds in
// Tabulate the number of players on each team.
m_iNumCT = GetGlobalTeam( TEAM_CT )->GetNumPlayers();
m_iNumTerrorist = GetGlobalTeam( TEAM_TERRORIST )->GetNumPlayers();
if ( m_iNumTerrorist > 0 && m_iNumCT > 0 )
{
if ( gpGlobals->curtime > (m_fRoundStartTime + 20) )
{
//If this player just connected and fadetoblack is on, then maybe
//the server admin doesn't want him peeking around.
color32_s clr = {0,0,0,255};
if ( mp_fadetoblack.GetBool() )
{
UTIL_ScreenFade( pPlayer, clr, 3, 3, FFADE_OUT | FFADE_STAYOUT );
}
return false;
}
}
// Player cannot respawn while in the Choose Appearance menu
//if ( pPlayer->m_iMenu == Menu_ChooseAppearance )
// return false;
return true;
}
void CCSGameRules::TerminateRound(float tmDelay, int iReason )
{
variant_t emptyVariant;
int iWinnerTeam = WINNER_NONE;
const char *text = "UNKNOWN";
// UTIL_ClientPrintAll( HUD_PRINTCENTER, sentence );
switch ( iReason )
{
// Terror wins:
case Target_Bombed:
text = "#Target_Bombed";
iWinnerTeam = WINNER_TER;
break;
case VIP_Assassinated:
text = "#VIP_Assassinated";
iWinnerTeam = WINNER_TER;
break;
case Terrorists_Escaped:
text = "#Terrorists_Escaped";
iWinnerTeam = WINNER_TER;
break;
case Terrorists_Win:
text = "#Terrorists_Win";
iWinnerTeam = WINNER_TER;
break;
case Hostages_Not_Rescued:
text = "#Hostages_Not_Rescued";
iWinnerTeam = WINNER_TER;
break;
case VIP_Not_Escaped:
text = "#VIP_Not_Escaped";
iWinnerTeam = WINNER_TER;
break;
// CT wins:
case VIP_Escaped:
text = "#VIP_Escaped";
iWinnerTeam = WINNER_CT;
break;
case CTs_PreventEscape:
text = "#CTs_PreventEscape";
iWinnerTeam = WINNER_CT;
break;
case Escaping_Terrorists_Neutralized:
text = "#Escaping_Terrorists_Neutralized";
iWinnerTeam = WINNER_CT;
break;
case Bomb_Defused:
text = "#Bomb_Defused";
iWinnerTeam = WINNER_CT;
break;
case CTs_Win:
text = "#CTs_Win";
iWinnerTeam = WINNER_CT;
break;
case All_Hostages_Rescued:
text = "#All_Hostages_Rescued";
iWinnerTeam = WINNER_CT;
break;
case Target_Saved:
text = "#Target_Saved";
iWinnerTeam = WINNER_CT;
break;
case Terrorists_Not_Escaped:
text = "#Terrorists_Not_Escaped";
iWinnerTeam = WINNER_CT;
break;
// no winners:
case Game_Commencing:
text = "#Game_Commencing";
iWinnerTeam = WINNER_DRAW;
break;
case Round_Draw:
text = "#Round_Draw";
iWinnerTeam = WINNER_DRAW;
break;
default:
DevMsg("TerminateRound: unknown round end ID %i\n", iReason );
break;
}
m_iRoundWinStatus = iWinnerTeam;
m_flRestartRoundTime = gpGlobals->curtime + tmDelay;
if ( iWinnerTeam == WINNER_CT )
{
for( int i=0;i<g_Hostages.Count();i++ )
g_Hostages[i]->AcceptInput( "CTsWin", NULL, NULL, emptyVariant, 0 );
}
else if ( iWinnerTeam == WINNER_TER )
{
for( int i=0;i<g_Hostages.Count();i++ )
g_Hostages[i]->AcceptInput( "TerroristsWin", NULL, NULL, emptyVariant, 0 );
}
else
{
Assert( iWinnerTeam == WINNER_NONE || iWinnerTeam == WINNER_DRAW );
}
IGameEvent * event = gameeventmanager->CreateEvent( "round_end" );
if ( event )
{
event->SetInt( "winner", iWinnerTeam );
event->SetInt( "reason", iReason );
event->SetString( "message", text );
event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted
gameeventmanager->FireEvent( event );
}
if ( GetMapRemainingTime() == 0.0f )
{
UTIL_LogPrintf("World triggered \"Intermission_Time_Limit\"\n");
GoToIntermission();
}
}
void CCSGameRules::UpdateTeamScores()
{
CTeam *pTerrorists = GetGlobalTeam( TEAM_TERRORIST );
CTeam *pCTs = GetGlobalTeam( TEAM_CT );
Assert( pTerrorists && pCTs );
if( pTerrorists )
pTerrorists->SetScore( m_iNumTerroristWins );
if( pCTs )
pCTs->SetScore( m_iNumCTWins );
}
void CCSGameRules::CheckMapConditions()
{
// Check to see if this map has a bomb target in it
if ( gEntList.FindEntityByClassname( NULL, "func_bomb_target" ) )
{
m_bMapHasBombTarget = true;
m_bMapHasBombZone = true;
}
else if ( gEntList.FindEntityByClassname( NULL, "info_bomb_target" ) )
{
m_bMapHasBombTarget = true;
m_bMapHasBombZone = false;
}
else
{
m_bMapHasBombTarget = false;
m_bMapHasBombZone = false;
}
// See if the map has func_buyzone entities
// Used by CBasePlayer::HandleSignals() to support maps without these entities
if ( gEntList.FindEntityByClassname( NULL, "func_buyzone" ) )
{
m_bMapHasBuyZone = true;
}
else
{
m_bMapHasBuyZone = false;
}
// Check to see if this map has hostage rescue zones
if ( gEntList.FindEntityByClassname( NULL, "func_hostage_rescue" ) )
{
m_bMapHasRescueZone = true;
}
else
{
m_bMapHasRescueZone = false;
}
// GOOSEMAN : See if this map has func_escapezone entities
if ( gEntList.FindEntityByClassname( NULL, "func_escapezone" ) )
{
m_bMapHasEscapeZone = true;
}
else
{
m_bMapHasEscapeZone = false;
}
// Check to see if this map has VIP safety zones
if ( gEntList.FindEntityByClassname( NULL, "func_vip_safetyzone" ) )
{
m_iMapHasVIPSafetyZone = 1;
}
else
{
m_iMapHasVIPSafetyZone = 2;
}
}
void CCSGameRules::SwapAllPlayers()
{
// MOTODO we have to make sure that enought spaning points exits
Assert ( 0 );
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
/* CCSPlayer *pPlayer = CCSPlayer::Instance( i );
if ( pPlayer && !FNullEnt( pPlayer->edict() ) )
pPlayer->SwitchTeam(); */
}
// Swap Team victories
int iTemp;
iTemp = m_iNumCTWins;
m_iNumCTWins = m_iNumTerroristWins;
m_iNumTerroristWins = iTemp;
// Update the clients team score
UpdateTeamScores();
}
bool CS_FindInList( const char **pStrings, const char *pToFind )
{
return FindInList( pStrings, pToFind );
}
void CCSGameRules::CleanUpMap()
{
if (IsLogoMap())
return;
// Recreate all the map entities from the map data (preserving their indices),
// then remove everything else except the players.
// Get rid of all entities except players.
CBaseEntity *pCur = gEntList.FirstEnt();
while ( pCur )
{
CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* >( pCur );
// Weapons with owners don't want to be removed..
if ( pWeapon )
{
if ( pWeapon->ShouldRemoveOnRoundRestart() )
{
UTIL_Remove( pCur );
}
}
// remove entities that has to be restored on roundrestart (breakables etc)
else if ( !CS_FindInList( s_PreserveEnts, pCur->GetClassname() ) )
{
UTIL_Remove( pCur );
}
pCur = gEntList.NextEnt( pCur );
}
// Really remove the entities so we can have access to their slots below.
gEntList.CleanupDeleteList();
// Cancel all queued events, in case a func_bomb_target fired some delayed outputs that
// could kill respawning CTs
g_EventQueue.Clear();
// Now reload the map entities.
class CCSMapEntityFilter : public IMapEntityFilter
{
public:
virtual bool ShouldCreateEntity( const char *pClassname )
{
// Don't recreate the preserved entities.
if ( !CS_FindInList( s_PreserveEnts, pClassname ) )
{
return true;
}
else
{
// Increment our iterator since it's not going to call CreateNextEntity for this ent.
if ( m_iIterator != g_MapEntityRefs.InvalidIndex() )
m_iIterator = g_MapEntityRefs.Next( m_iIterator );
return false;
}
}
virtual CBaseEntity* CreateNextEntity( const char *pClassname )
{
if ( m_iIterator == g_MapEntityRefs.InvalidIndex() )
{
// This shouldn't be possible. When we loaded the map, it should have used
// CCSMapLoadEntityFilter, which should have built the g_MapEntityRefs list
// with the same list of entities we're referring to here.
Assert( false );
return NULL;
}
else
{
CMapEntityRef &ref = g_MapEntityRefs[m_iIterator];
m_iIterator = g_MapEntityRefs.Next( m_iIterator ); // Seek to the next entity.
if ( ref.m_iEdict == -1 || engine->PEntityOfEntIndex( ref.m_iEdict ) )
{
// Doh! The entity was delete and its slot was reused.
// Just use any old edict slot. This case sucks because we lose the baseline.
return CreateEntityByName( pClassname );
}
else
{
// Cool, the slot where this entity was is free again (most likely, the entity was
// freed above). Now create an entity with this specific index.
return CreateEntityByName( pClassname, ref.m_iEdict );
}
}
}
public:
int m_iIterator; // Iterator into g_MapEntityRefs.
};
CCSMapEntityFilter filter;
filter.m_iIterator = g_MapEntityRefs.Head();
// DO NOT CALL SPAWN ON info_node ENTITIES!
MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true );
}
bool CCSGameRules::IsThereABomber()
{
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pPlayer = CCSPlayer::Instance( i );
if ( pPlayer && !FNullEnt( pPlayer->edict() ) )
{
if ( pPlayer->GetTeamNumber() == TEAM_CT )
continue;
if ( pPlayer->HasC4() )
return true; //There you are.
}
}
//Didn't find a bomber.
return false;
}
void CCSGameRules::EndRound()
{
// fake a round end
CSGameRules()->TerminateRound( 0.0f, Round_Draw );
}
CBaseEntity *CCSGameRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer )
{
// gat valid spwan point
CBaseEntity *pSpawnSpot = pPlayer->EntSelectSpawnPoint();
// drop down to ground
Vector GroundPos = DropToGround( pPlayer, pSpawnSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX );
// Move the player to the place it said.
pPlayer->Teleport( &pSpawnSpot->GetAbsOrigin(), &pSpawnSpot->GetLocalAngles(), &vec3_origin );
pPlayer->m_Local.m_vecPunchAngle = vec3_angle;
return pSpawnSpot;
}
// checks if the spot is clear of players
bool CCSGameRules::IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer )
{
if ( !pSpot->IsTriggered( pPlayer ) )
{
return false;
}
Vector mins = GetViewVectors()->m_vHullMin;
Vector maxs = GetViewVectors()->m_vHullMax;
Vector vTestMins = pSpot->GetAbsOrigin() + mins;
Vector vTestMaxs = pSpot->GetAbsOrigin() + maxs;
// First test the starting origin.
return UTIL_IsSpaceEmpty( pPlayer, vTestMins, vTestMaxs );
}
bool CCSGameRules::IsThereABomb()
{
bool bBombFound = false;
/* are there any bombs, either laying around, or in someone's inventory? */
if( gEntList.FindEntityByClassname( NULL, WEAPON_C4_CLASSNAME ) != 0 )
{
bBombFound = true;
}
/* what about planted bombs!? */
else if( gEntList.FindEntityByClassname( NULL, PLANTED_C4_CLASSNAME ) != 0 )
{
bBombFound = true;
}
return bBombFound;
}
void CCSGameRules::HostageTouched()
{
if( gpGlobals->curtime > m_flNextHostageAnnouncement && m_iRoundWinStatus == WINNER_NONE )
{
//BroadcastSound( "Event.HostageTouched" );
m_flNextHostageAnnouncement = gpGlobals->curtime + 60.0;
}
}
void CCSGameRules::CreateStandardEntities()
{
// Create the player resource
g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "cs_player_manager", vec3_origin, vec3_angle );
// Create the entity that will send our data to the client.
#ifdef _DEBUG
CBaseEntity *pEnt =
#endif
CBaseEntity::Create( "cs_gamerules", vec3_origin, vec3_angle );
Assert( pEnt );
}
#define MY_USHRT_MAX 0xffff
#define MY_UCHAR_MAX 0xff
bool DataHasChanged( void )
{
for ( int i = 0; i < CS_NUM_LEVELS; i++ )
{
if ( g_iTerroristVictories[i] != 0 || g_iCounterTVictories[i] != 0 )
return true;
}
for ( int i = 0; i < WEAPON_MAX; i++ )
{
if ( g_iWeaponPurchases[i] != 0 )
return true;
}
return false;
}
void CCSGameRules::UploadGameStats( void )
{
g_flGameStatsUpdateTime -= gpGlobals->curtime;
if ( g_flGameStatsUpdateTime > 0 )
return;
if ( IsBlackMarket() == false )
return;
if ( m_bDontUploadStats == true )
return;
if ( DataHasChanged() == true )
{
cs_gamestats_t stats;
memset( &stats, 0, sizeof(stats) );
// Header
stats.header.iVersion = CS_STATS_BLOB_VERSION;
Q_strncpy( stats.header.szGameName, "cstrike", sizeof(stats.header.szGameName) );
Q_strncpy( stats.header.szMapName, STRING( gpGlobals->mapname ), sizeof( stats.header.szMapName ) );
ConVar *hostip = cvar->FindVar( "hostip" );
if ( hostip )
{
int ip = hostip->GetInt();
stats.header.ipAddr[0] = ip >> 24;
stats.header.ipAddr[1] = ( ip >> 16 ) & MY_UCHAR_MAX;
stats.header.ipAddr[2] = ( ip >> 8 ) & MY_UCHAR_MAX;
stats.header.ipAddr[3] = ( ip ) & MY_UCHAR_MAX;
}
ConVar *hostport = cvar->FindVar( "hostip" );
if ( hostport )
{
stats.header.port = hostport->GetInt();
}
stats.header.serverid = 0;
stats.iMinutesPlayed = clamp( (short)( gpGlobals->curtime / 60 ), 0, MY_USHRT_MAX );
memcpy( stats.iTerroristVictories, g_iTerroristVictories, sizeof( g_iTerroristVictories) );
memcpy( stats.iCounterTVictories, g_iCounterTVictories, sizeof( g_iCounterTVictories) );
memcpy( stats.iBlackMarketPurchases, g_iWeaponPurchases, sizeof( g_iWeaponPurchases) );
stats.iAutoBuyPurchases = g_iAutoBuyPurchases;
stats.iReBuyPurchases = g_iReBuyPurchases;
stats.iAutoBuyM4A1Purchases = g_iAutoBuyM4A1Purchases;
stats.iAutoBuyAK47Purchases = g_iAutoBuyAK47Purchases;
stats.iAutoBuyFamasPurchases = g_iAutoBuyFamasPurchases;
stats.iAutoBuyGalilPurchases = g_iAutoBuyGalilPurchases;
stats.iAutoBuyVestHelmPurchases = g_iAutoBuyVestHelmPurchases;
stats.iAutoBuyVestPurchases = g_iAutoBuyVestPurchases;
const void *pvBlobData = ( const void * )( &stats );
unsigned int uBlobSize = sizeof( stats );
if ( gamestatsuploader )
{
gamestatsuploader->UploadGameStats(
STRING( gpGlobals->mapname ),
CS_STATS_BLOB_VERSION,
uBlobSize,
pvBlobData );
}
memset( g_iWeaponPurchases, 0, sizeof( g_iWeaponPurchases) );
memset( g_iTerroristVictories, 0, sizeof( g_iTerroristVictories) );
memset( g_iCounterTVictories, 0, sizeof( g_iTerroristVictories) );
g_iAutoBuyPurchases = 0;
g_iReBuyPurchases = 0;
g_iAutoBuyM4A1Purchases = 0;
g_iAutoBuyAK47Purchases = 0;
g_iAutoBuyFamasPurchases = 0;
g_iAutoBuyGalilPurchases = 0;
g_iAutoBuyVestHelmPurchases = 0;
g_iAutoBuyVestPurchases = 0;
}
g_flGameStatsUpdateTime = CS_GAME_STATS_UPDATE; //Next update is between 22 and 24 hours.
}
#endif // CLIENT_DLL
CBaseCombatWeapon *CCSGameRules::GetNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon )
{
CBaseCombatWeapon *bestWeapon = NULL;
// search all the weapons looking for the closest next
for ( int i = 0; i < MAX_WEAPONS; i++ )
{
CBaseCombatWeapon *weapon = pPlayer->GetWeapon(i);
if ( !weapon )
continue;
if ( !weapon->CanBeSelected() || weapon == pCurrentWeapon )
continue;
#ifndef CLIENT_DLL
CCSPlayer *csPlayer = ToCSPlayer(pPlayer);
CWeaponCSBase *csWeapon = static_cast< CWeaponCSBase * >(weapon);
if ( csPlayer && csPlayer->IsBot() && !TheCSBots()->IsWeaponUseable( csWeapon ) )
continue;
#endif // CLIENT_DLL
if ( bestWeapon )
{
if ( weapon->GetSlot() < bestWeapon->GetSlot() )
{
bestWeapon = weapon;
}
else if ( weapon->GetSlot() == bestWeapon->GetSlot() && weapon->GetPosition() < bestWeapon->GetPosition() )
{
bestWeapon = weapon;
}
}
else
{
bestWeapon = weapon;
}
}
return bestWeapon;
}
float CCSGameRules::GetMapRemainingTime()
{
#ifdef GAME_DLL
if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) )
{
return 0;
}
#endif
// if timelimit is disabled, return -1
if ( mp_timelimit.GetInt() <= 0 )
return -1;
// timelimit is in minutes
float flTimeLeft = ( m_flGameStartTime + mp_timelimit.GetInt() * 60 ) - gpGlobals->curtime;
// never return a negative value
if ( flTimeLeft < 0 )
flTimeLeft = 0;
return flTimeLeft;
}
float CCSGameRules::GetMapElapsedTime( void )
{
return gpGlobals->curtime;
}
float CCSGameRules::GetRoundRemainingTime()
{
return (float) (m_fRoundStartTime + m_iRoundTime) - gpGlobals->curtime;
}
float CCSGameRules::GetRoundStartTime()
{
return m_fRoundStartTime;
}
float CCSGameRules::GetRoundElapsedTime()
{
return gpGlobals->curtime - m_fRoundStartTime;
}
bool CCSGameRules::ShouldCollide( int collisionGroup0, int collisionGroup1 )
{
if ( collisionGroup0 > collisionGroup1 )
{
// swap so that lowest is always first
swap(collisionGroup0,collisionGroup1);
}
//Don't stand on COLLISION_GROUP_WEAPONs
if( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT &&
collisionGroup1 == COLLISION_GROUP_WEAPON )
{
return false;
}
// TODO: make a CS-SPECIFIC COLLISION GROUP FOR PHYSICS PROPS THAT USE THIS COLLISION BEHAVIOR.
if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) &&