summaryrefslogtreecommitdiff
path: root/game/shared/cstrike/cs_gamerules.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/cstrike/cs_gamerules.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/shared/cstrike/cs_gamerules.cpp')
-rw-r--r--game/shared/cstrike/cs_gamerules.cpp5859
1 files changed, 5859 insertions, 0 deletions
diff --git a/game/shared/cstrike/cs_gamerules.cpp b/game/shared/cstrike/cs_gamerules.cpp
new file mode 100644
index 0000000..c688140
--- /dev/null
+++ b/game/shared/cstrike/cs_gamerules.cpp
@@ -0,0 +1,5859 @@
+//========= Copyright 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"
+#include "cs_achievement_constants.h"
+#include "fmtstr.h"
+
+#ifdef CLIENT_DLL
+
+ #include "networkstringtable_clientdll.h"
+ #include "utlvector.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"
+ #include "player_resource.h"
+ #include "cs_player_resource.h"
+
+#if defined( REPLAY_ENABLED )
+ #include "replay/ireplaysystem.h"
+ #include "replay/iserverreplaycontext.h"
+ #include "replay/ireplaysessionrecorder.h"
+#endif // REPLAY_ENABLED
+
+#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;
+
+#if defined( REPLAY_ENABLED )
+extern IReplaySystem *g_pReplay;
+#endif // REPLAY_ENABLED
+
+#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 );
+
+ConVar mp_round_restart_delay(
+ "mp_round_restart_delay",
+ "5.0",
+ FCVAR_REPLICATED,
+ "Number of seconds to delay before restarting a round after a win",
+ true, 0.0f,
+ true, 10.0f );
+
+ConVar sv_allowminmodels(
+ "sv_allowminmodels",
+ "1",
+ FCVAR_REPLICATED | FCVAR_NOTIFY,
+ "Allow or disallow the use of cl_minmodels on this server." );
+
+#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.
+ const char *sTeamNames[] =
+ {
+ "Unassigned",
+ "Spectator",
+ "TERRORIST",
+ "CT"
+ };
+
+ extern ConVar mp_maxrounds;
+
+ ConVar mp_startmoney(
+ "mp_startmoney",
+ "800",
+ FCVAR_REPLICATED | FCVAR_NOTIFY,
+ "amount of money each player gets when they reset",
+ true, 800,
+ true, 16000 );
+
+ ConVar mp_roundtime(
+ "mp_roundtime",
+ "2.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}" );
+
+ ConVar mp_ignore_round_win_conditions(
+ "mp_ignore_round_win_conditions",
+ "0",
+ FCVAR_REPLICATED,
+ "Ignore conditions which would end the current round");
+
+ 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;
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [dwenger] Reset rescue-related achievement values
+ //=============================================================================
+
+ // [tj] reset flawless and lossless round related flags
+ m_bNoTerroristsKilled = true;
+ m_bNoCTsKilled = true;
+ m_bNoTerroristsDamaged = true;
+ m_bNoCTsDamaged = true;
+ m_pFirstKill = NULL;
+ m_firstKillTime = 0;
+
+ // [menglish] Reset fun fact values
+ m_pFirstBlood = NULL;
+ m_firstBloodTime = 0;
+
+ m_bCanDonateWeapons = true;
+
+ // [dwenger] Reset rescue-related achievement values
+ m_pLastRescuer = NULL;
+ m_iNumRescuers = 0;
+
+ m_hostageWasInjured = false;
+ m_hostageWasKilled = false;
+
+ m_pFunFactManager = new CCSFunFactMgr();
+ m_pFunFactManager->Init();
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ 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
+ // Map names cannot contain quotes or control characters so this is safe but silly that we have to do it.
+ 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();
+ if( m_pFunFactManager )
+ {
+ delete m_pFunFactManager;
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // 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() )
+ {
+ 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::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues )
+ {
+ CCSPlayer *pPlayer = dynamic_cast< CCSPlayer * >( CBaseEntity::Instance( pEntity ) );
+ if ( pPlayer )
+ {
+ char const *pszCommand = pKeyValues->GetName();
+ if ( pszCommand && pszCommand[0] )
+ {
+ if ( FStrEq( pszCommand, "ClanTagChanged" ) )
+ {
+ pPlayer->SetClanTag( pKeyValues->GetString( "tag", "" ) );
+
+ const char *teamName = "UNKNOWN";
+ if ( pPlayer->GetTeam() )
+ {
+ teamName = pPlayer->GetTeam()->GetName();
+ }
+ UTIL_LogPrintf("\"%s<%i><%s><%s>\" triggered \"clantag\" (value \"%s\")\n",
+ pPlayer->GetPlayerName(),
+ pPlayer->GetUserID(),
+ pPlayer->GetNetworkIDString(),
+ teamName,
+ pKeyValues->GetString( "tag", "unknown" ) );
+ }
+ }
+ }
+
+ BaseClass::ClientCommandKeyValues( pEntity, pKeyValues );
+ }
+
+ //-----------------------------------------------------------------------------
+ // 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 falloff, damagePercentage;
+ Vector vecSpot;
+ Vector vecToTarget;
+ Vector vecEndPos;
+
+ //=============================================================================
+ // HPE_BEGIN:
+ //=============================================================================
+
+ // [tj] The number of enemy players this explosion killed
+ int numberOfEnemyPlayersKilledByThisExplosion = 0;
+
+ // [tj] who we award the achievement to if enough players are killed
+ CCSPlayer* pCSExplosionAttacker = ToCSPlayer(info.GetAttacker());
+
+ // [tj] used to determine which achievement to award for sufficient kills
+ CBaseEntity* pInflictor = info.GetInflictor();
+ bool isGrenade = pInflictor && V_strcmp(pInflictor->GetClassname(), "hegrenade_projectile") == 0;
+ bool isBomb = pInflictor && V_strcmp(pInflictor->GetClassname(), "planted_c4") == 0;
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+
+ 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() )
+ {
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] We have to save whether or not the player is killed so we don't give credit
+ // for pre-dead players.
+ //=============================================================================
+ bool wasAliveBeforeExplosion = false;
+ CCSPlayer* pCSExplosionVictim = ToCSPlayer(pEntity);
+ if (pCSExplosionVictim)
+ {
+ wasAliveBeforeExplosion = pCSExplosionVictim->IsAlive();
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ 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 );
+
+ // use a Gaussian function to describe the damage falloff over distance, with flRadius equal to 3 * sigma
+ // this results in the following values:
+ //
+ // Range Fraction Damage
+ // 0.0 100%
+ // 0.1 96%
+ // 0.2 84%
+ // 0.3 67%
+ // 0.4 49%
+ // 0.5 32%
+ // 0.6 20%
+ // 0.7 11%
+ // 0.8 6%
+ // 0.9 3%
+ // 1.0 1%
+
+ float fDist = vecToTarget.Length();
+ float fSigma = flRadius / 3.0f; // flRadius specifies 3rd standard deviation (0.0111 damage at this range)
+ float fGaussianFalloff = exp(-fDist * fDist / (2.0f * fSigma * fSigma));
+ float flAdjustedDamage = info.GetDamage() * fGaussianFalloff * 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);
+
+ // blasts always hit chest
+ tr.hitgroup = HITGROUP_GENERIC;
+
+ 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 );
+ //=============================================================================
+ // HPE_BEGIN:
+ // [sbodenbender] Increment grenade damage stat
+ //=============================================================================
+ if (pCSExplosionVictim && pCSExplosionAttacker && isGrenade)
+ {
+ CCS_GameStats.IncrementStat(pCSExplosionAttacker, CSSTAT_GRENADE_DAMAGE, static_cast<int>(adjustedInfo.GetDamage()));
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ }
+ }
+ }
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Count up victims of area of effect damage for achievement purposes
+ //=============================================================================
+
+ if (pCSExplosionVictim)
+ {
+ //If the bomb is exploding, set the attacker to the planter (we can't put this in the CTakeDamageInfo, since
+ //players aren't supposed to get credit for bomb kills)
+ if (isBomb)
+ {
+ CPlantedC4* bomb = static_cast<CPlantedC4*> (pInflictor);
+ if (bomb)
+ {
+ pCSExplosionAttacker = bomb->GetPlanter();
+ }
+ }
+
+ //Count check to make sure we killed an enemy player
+ if( pCSExplosionAttacker &&
+ !pCSExplosionVictim->IsAlive() &&
+ wasAliveBeforeExplosion &&
+ pCSExplosionVictim->GetTeamNumber() != pCSExplosionAttacker->GetTeamNumber())
+ {
+ numberOfEnemyPlayersKilledByThisExplosion++;
+ }
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ }
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] //Depending on which type of explosion it was, award the appropriate achievement.
+ //=============================================================================
+
+ if (pCSExplosionAttacker && isGrenade && numberOfEnemyPlayersKilledByThisExplosion >= AchievementConsts::GrenadeMultiKill_MinKills)
+ {
+ pCSExplosionAttacker->AwardAchievement(CSGrenadeMultikill);
+ pCSExplosionAttacker->CheckMaxGrenadeKills(numberOfEnemyPlayersKilledByThisExplosion);
+
+ }
+ if (pCSExplosionAttacker && isBomb && numberOfEnemyPlayersKilledByThisExplosion >= AchievementConsts::BombMultiKill_MinKills)
+ {
+ pCSExplosionAttacker->AwardAchievement(CSBombMultikill);
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ }
+
+ //-----------------------------------------------------------------------------
+ // 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 );
+ CCSPlayer *pCSVictim = (CCSPlayer*)(pVictim);
+
+ 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
+ if ( pCSVictim->GetDeathFlags() & CS_DEATH_DOMINATION )
+ {
+ event->SetInt( "dominated", 1 );
+ }
+ else if ( pCSVictim->GetDeathFlags() & CS_DEATH_REVENGE )
+ {
+ event->SetInt( "revenge", 1 );
+ }
+
+ gameeventmanager->FireEvent( event );
+ }
+ }
+
+ //=========================================================
+ //=========================================================
+ void CCSGameRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info )
+ {
+ CBaseEntity *pInflictor = info.GetInflictor();
+ CBaseEntity *pKiller = info.GetAttacker();
+ CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor );
+ CCSPlayer *pCSVictim = (CCSPlayer *)pVictim;
+ CCSPlayer *pCSScorer = (CCSPlayer *)pScorer;
+
+ CCS_GameStats.PlayerKilled( pVictim, info );
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Flag the round as non-lossless for the appropriate team.
+ // [menglish] Set the death flags depending on a nemesis system
+ //=============================================================================
+
+ if (pVictim->GetTeamNumber() == TEAM_TERRORIST)
+ {
+ m_bNoTerroristsKilled = false;
+ m_bNoTerroristsDamaged = false;
+ }
+ if (pVictim->GetTeamNumber() == TEAM_CT)
+ {
+ m_bNoCTsKilled = false;
+ m_bNoCTsDamaged = false;
+ }
+
+ m_bCanDonateWeapons = false;
+
+ if ( m_pFirstKill == NULL && pCSScorer != pVictim )
+ {
+ m_pFirstKill = pCSScorer;
+ m_firstKillTime = gpGlobals->curtime - m_fRoundStartTime;
+ }
+
+ // determine if this kill affected a nemesis relationship
+ int iDeathFlags = 0;
+ if ( pScorer )
+ {
+ CCS_GameStats.CalculateOverkill( pCSScorer, pCSVictim);
+ CCS_GameStats.CalcDominationAndRevenge( pCSScorer, pCSVictim, &iDeathFlags );
+ }
+ pCSVictim->SetDeathFlags( iDeathFlags );
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ // 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;
+
+ 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 )
+ {
+ 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
+ {
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Added a check to make sure we don't get money for suicides.
+ //=============================================================================
+ if (pCSScorer != pCSVictim)
+ {
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ 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 )
+ {
+ float fFallVelocity = pPlayer->m_Local.m_flFallVelocity - CS_PLAYER_MAX_SAFE_FALL_SPEED;
+ float fallDamage = fFallVelocity * 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 );
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Clear domination data when a player disconnects
+ //=============================================================================
+
+ CCSPlayer *pPlayer = ToCSPlayer( GetContainingEntity( pClient ) );
+ if ( pPlayer )
+ {
+ pPlayer->RemoveNemesisRelationships();
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+
+ 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 ( mp_ignore_round_win_conditions.GetBool() )
+ {
+ return false;
+ }
+
+ // 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.0f, 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();
+ }
+ CCS_GameStats.Event_AllHostagesRescued();
+ // tell the bots all the hostages have been rescued
+ IGameEvent * event = gameeventmanager->CreateEvent( "hostage_rescued_all" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event );
+ }
+
+ TerminateRound( mp_round_restart_delay.GetFloat(), 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( mp_round_restart_delay.GetFloat(), 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( mp_round_restart_delay.GetFloat(), 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( mp_round_restart_delay.GetFloat(), 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 );
+ }
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [menglish] If the VIP has escaped award him an MVP
+ //=============================================================================
+
+ m_pVIP->IncrementNumMVPs( CSMVP_UNDEFINED );
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ TerminateRound( mp_round_restart_delay.GetFloat(), 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( mp_round_restart_delay.GetFloat(), 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( mp_round_restart_delay.GetFloat(), 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( mp_round_restart_delay.GetFloat(), 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( mp_round_restart_delay.GetFloat(), 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( mp_round_restart_delay.GetFloat(), Terrorists_Win );
+ return true;
+ }
+ }
+ else if ( NumAliveCT == 0 && NumAliveTerrorist == 0 )
+ {
+ TerminateRound( mp_round_restart_delay.GetFloat(), 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 defined( REPLAY_ENABLED )
+ if ( g_pReplay )
+ {
+ // Write replay and stop recording if appropriate
+ if ( g_pReplay->IsRecording() )
+ {
+ g_pReplay->SV_EndRecordingSession();
+ }
+
+ int nActivePlayerCount = m_iNumTerrorist + m_iNumCT;
+ if ( nActivePlayerCount && g_pReplay->SV_ShouldBeginRecording( false ) )
+ {
+ // Tell the replay manager that it should begin recording the new round as soon as possible
+ g_pReplay->SV_GetContext()->GetSessionRecorder()->StartRecording();
+ }
+ }
+#endif
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Notify players that the round is about to be reset
+ //=============================================================================
+ for ( int clientIndex = 1; clientIndex <= gpGlobals->maxClients; clientIndex++ )
+ {
+ CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( clientIndex );
+ if(pPlayer)
+ {
+ pPlayer->OnPreResetRound();
+ }
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ 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 );
+ }
+ }
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Keep track of number of players per side and if they have the same uniform
+ //=============================================================================
+
+ int terroristUniform = -1;
+ bool allTerroristsWearingSameUniform = true;
+ int numberOfTerrorists = 0;
+ int ctUniform = -1;
+ bool allCtsWearingSameUniform = true;
+ int numberOfCts = 0;
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ // 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 )
+ {
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Increment CT count and check CT uniforms.
+ //=============================================================================
+
+ numberOfCts++;
+ if (ctUniform == -1)
+ {
+ ctUniform = pPlayer->PlayerClass();
+ }
+ else if (pPlayer->PlayerClass() != ctUniform)
+ {
+ allCtsWearingSameUniform = false;
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ pPlayer->RoundRespawn();
+ }
+
+ if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST && pPlayer->PlayerClass() >= FIRST_T_CLASS && pPlayer->PlayerClass() <= LAST_T_CLASS )
+ {
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Increment terrorist count and check terrorist uniforms
+ //=============================================================================
+
+ numberOfTerrorists++;
+ if (terroristUniform == -1)
+ {
+ terroristUniform = pPlayer->PlayerClass();
+ }
+ else if (pPlayer->PlayerClass() != terroristUniform)
+ {
+ allTerroristsWearingSameUniform = false;
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ pPlayer->RoundRespawn();
+ }
+ else
+ {
+ pPlayer->ObserverRoundRespawn();
+ }
+
+ if ( pPlayer->m_iAccount > pPlayer->m_iShouldHaveCash )
+ {
+ m_bDontUploadStats = true;
+ }
+ }
+
+ //=============================================================================
+ // HPE_BEGIN:
+ //=============================================================================
+
+ // [tj] Award same uniform achievement for qualifying teams
+ for ( i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
+
+ if ( !pPlayer )
+ continue;
+
+ if ( pPlayer->GetTeamNumber() == TEAM_CT && allCtsWearingSameUniform && numberOfCts >= AchievementConsts::SameUniform_MinPlayers)
+ {
+ pPlayer->AwardAchievement(CSSameUniform);
+ }
+
+ if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST && allTerroristsWearingSameUniform && numberOfTerrorists >= AchievementConsts::SameUniform_MinPlayers)
+ {
+ pPlayer->AwardAchievement(CSSameUniform);
+ }
+ }
+
+ // [menglish] reset per-round achievement variables for each player
+ for ( i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
+ if( pPlayer )
+ {
+ pPlayer->ResetRoundBasedAchievementVariables();
+ }
+ }
+
+ // [pfreese] Reset all round or match stats, depending on type of restart
+ if ( m_bCompleteReset )
+ {
+ CCS_GameStats.ResetAllStats();
+ CCS_GameStats.ResetPlayerClassMatchStats();
+ }
+ else
+ {
+ CCS_GameStats.ResetRoundStats();
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ // 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;
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [dwenger] Reset rescue-related achievement values
+ //=============================================================================
+
+ // [tj] reset flawless and lossless round related flags
+ m_bNoTerroristsKilled = true;
+ m_bNoCTsKilled = true;
+ m_bNoTerroristsDamaged = true;
+ m_bNoCTsDamaged = true;
+ m_pFirstKill = NULL;
+ m_pFirstBlood = NULL;
+
+ m_bCanDonateWeapons = true;
+
+ // [dwenger] Reset rescue-related achievement values
+ m_iHostagesRemaining = 0;
+ m_pLastRescuer = NULL;
+
+ m_hostageWasInjured = false;
+ m_hostageWasKilled = false;
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ m_iNumRescuers = 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();
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [pfreese] I commented out this call to CreateWeaponManager, as the
+ // CGameWeaponManager object doesn't appear to be actually used by the CSS
+ // code, and in any case, the weapon manager does not support wildcards in
+ // entity names (as seemingly indicated) below. When the manager fails to
+ // create its factory, it removes itself in any case.
+ //=============================================================================
+
+ // CreateWeaponManager( "weapon_*", gpGlobals->maxClients * 2 );
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ }
+
+ 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
+ {
+ //=============================================================================
+ // HPE_BEGIN:
+ // [Forrest] Calling ChangeLevel multiple times was causing IncrementMapCycleIndex
+ // to skip over maps in the list. Avoid this using a technique from CTeamplayRoundBasedRules::Think.
+ //=============================================================================
+ // check to see if we should change levels now
+ if ( m_flIntermissionEndTime && ( m_flIntermissionEndTime < gpGlobals->curtime ) )
+ {
+ ChangeLevel(); // intermission is over
+
+ // Don't run this code again
+ m_flIntermissionEndTime = 0.f;
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ 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;
+ }
+
+ }
+
+ //pPlayer->SyncRoundTimer();
+ }
+ }
+ }
+
+
+ void CCSGameRules::CheckRoundTimeExpired()
+ {
+ if ( mp_ignore_round_win_conditions.GetBool() )
+ return;
+
+ 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( mp_round_restart_delay.GetFloat(), Target_Saved );
+ UpdateTeamScores();
+ MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(TEAM_TERRORIST);
+ }
+ }
+ else if ( m_bMapHasRescueZone )
+ {
+ m_iAccountTerrorist += 3250;
+
+ m_iNumTerroristWins++;
+ TerminateRound( mp_round_restart_delay.GetFloat(), Hostages_Not_Rescued );
+ UpdateTeamScores();
+ MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(TEAM_CT);
+ }
+ else if ( m_bMapHasEscapeZone )
+ {
+ m_iNumCTWins++;
+ TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Not_Escaped );
+ UpdateTeamScores();
+ }
+ else if ( m_iMapHasVIPSafetyZone == 1 )
+ {
+ m_iAccountTerrorist += 3250;
+ m_iNumTerroristWins++;
+
+ TerminateRound( mp_round_restart_delay.GetFloat(), VIP_Not_Escaped );
+ UpdateTeamScores();
+ }
+
+#if defined( REPLAY_ENABLED )
+ if ( g_pReplay )
+ {
+ // Write replay and stop recording if appropriate
+ g_pReplay->SV_EndRecordingSession();
+ }
+#endif
+ }
+
+ void CCSGameRules::GoToIntermission( void )
+ {
+ Msg( "Going to intermission...\n" );
+
+ IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_match" );
+
+ if( winEvent )
+ {
+ for ( int teamIndex = TEAM_TERRORIST; teamIndex <= TEAM_CT; teamIndex++ )
+ {
+ CTeam *team = GetGlobalTeam( teamIndex );
+ if ( team )
+ {
+ float kills = CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_KILLS];
+ float deaths = CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_DEATHS];
+ // choose dialog variables to set depending on team
+ switch ( teamIndex )
+ {
+ case TEAM_TERRORIST:
+ winEvent->SetInt( "t_score", team->GetScore() );
+ if(deaths == 0)
+ {
+ winEvent->SetFloat( "t_kd", kills );
+ }
+ else
+ {
+ winEvent->SetFloat( "t_kd", kills / deaths );
+ }
+ winEvent->SetInt( "t_objectives_done", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_OBJECTIVES_COMPLETED] );
+ winEvent->SetInt( "t_money_earned", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_MONEY_EARNED] );
+ break;
+ case TEAM_CT:
+ winEvent->SetInt( "ct_score", team->GetScore() );
+ if(deaths == 0)
+ {
+ winEvent->SetFloat( "ct_kd", kills );
+ }
+ else
+ {
+ winEvent->SetFloat( "ct_kd", kills / deaths );
+ }
+ winEvent->SetInt( "ct_objectives_done", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_OBJECTIVES_COMPLETED] );
+ winEvent->SetInt( "ct_money_earned", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_MONEY_EARNED] );
+ break;
+ default:
+ Assert( false );
+ break;
+ }
+ }
+ }
+
+ gameeventmanager->FireEvent( winEvent );
+ }
+
+ 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;
+ }
+
+ int PlayerScoreInfoSort( const playerscore_t *p1, const playerscore_t *p2 )
+ {
+ // check frags
+ if ( p1->iScore > p2->iScore )
+ return -1;
+ if ( p2->iScore > p1->iScore )
+ return 1;
+
+ // check index
+ if ( p1->iPlayerIndex < p2->iPlayerIndex )
+ return -1;
+
+ return 1;
+ }
+
+#if defined (_DEBUG)
+ void TestRoundWinpanel( void )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "round_end" );
+ event->SetInt( "winner", TEAM_TERRORIST );
+
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event );
+ }
+
+
+ IGameEvent *event2 = gameeventmanager->CreateEvent( "player_death" );
+ if ( event2 )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex(1) );
+
+ // pCappingPlayers is a null terminated list of player indeces
+ event2->SetInt("userid", pPlayer->GetUserID() );
+ event2->SetInt("attacker", pPlayer->GetUserID() );
+ event2->SetString("weapon", "Bare Hands" );
+ event2->SetInt("headshot", 1 );
+ event2->SetInt( "revenge", 1 );
+
+ gameeventmanager->FireEvent( event2 );
+ }
+
+ IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_round" );
+
+ if ( winEvent )
+ {
+ if ( 1 )
+ {
+ if ( 0 /*team == m_iTimerWinTeam */)
+ {
+ // timer expired, defenders win
+ // show total time that was defended
+ winEvent->SetBool( "show_timer_defend", true );
+ winEvent->SetInt( "timer_time", 0 /*m_pRoundTimer->GetTimerMaxLength() */);
+ }
+ else
+ {
+ // attackers win
+ // show time it took for them to win
+ winEvent->SetBool( "show_timer_attack", true );
+
+ int iTimeElapsed = 90; //m_pRoundTimer->GetTimerMaxLength() - (int)m_pRoundTimer->GetTimeRemaining();
+ winEvent->SetInt( "timer_time", iTimeElapsed );
+ }
+ }
+ else
+ {
+ winEvent->SetBool( "show_timer_attack", false );
+ winEvent->SetBool( "show_timer_defend", false );
+ }
+
+ int iLastEvent = Terrorists_Win;
+
+ winEvent->SetInt( "final_event", iLastEvent );
+
+ // Set the fun fact data in the event
+ winEvent->SetString( "funfact_token", "#funfact_first_blood" );
+ winEvent->SetInt( "funfact_player", 1 );
+ winEvent->SetInt( "funfact_data1", 20 );
+ winEvent->SetInt( "funfact_data2", 31 );
+ winEvent->SetInt( "funfact_data3", 45 );
+
+ gameeventmanager->FireEvent( winEvent );
+ }
+ }
+ ConCommand test_round_winpanel( "test_round_winpanel", TestRoundWinpanel, "", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT );
+
+ void TestMatchWinpanel( void )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "round_end" );
+ event->SetInt( "winner", TEAM_TERRORIST );
+
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event );
+ }
+
+ IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_match" );
+
+ if ( winEvent )
+ {
+ winEvent->SetInt( "t_score", 4 );
+ winEvent->SetInt( "ct_score", 1 );
+
+ winEvent->SetFloat( "t_kd", 1.8f );
+ winEvent->SetFloat( "ct_kd", 0.4f );
+
+ winEvent->SetInt( "t_objectives_done", 5 );
+ winEvent->SetInt( "ct_objectives_done", 2 );
+
+ winEvent->SetInt( "t_money_earned", 30000 );
+ winEvent->SetInt( "ct_money_earned", 19999 );
+
+ gameeventmanager->FireEvent( winEvent );
+ }
+ }
+ ConCommand test_match_winpanel( "test_match_winpanel", TestMatchWinpanel, "", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT );
+
+ void TestFreezePanel( void )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "freezecam_started" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event );
+ }
+
+ IGameEvent *winEvent = gameeventmanager->CreateEvent( "show_freezepanel" );
+
+ if ( winEvent )
+ {
+ winEvent->SetInt( "killer", 1 );
+ gameeventmanager->FireEvent( winEvent );
+ }
+ }
+ ConCommand test_freezepanel( "test_freezepanel", TestFreezePanel, "", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT );
+#endif // _DEBUG
+
+ 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 ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ 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 );
+ }
+
+ //=============================================================================
+ // HPE_BEGIN:
+ //=============================================================================
+
+ // [tj] Check for any non-player-specific achievements.
+ ProcessEndOfRoundAchievements(iWinnerTeam, iReason);
+
+ if( iReason != Game_Commencing )
+ {
+ // [pfreese] Setup and send win panel event (primarily funfact data)
+
+ FunFact funfact;
+ funfact.szLocalizationToken = "";
+ funfact.iPlayer = 0;
+ funfact.iData1 = 0;
+ funfact.iData2 = 0;
+ funfact.iData3 = 0;
+
+ m_pFunFactManager->GetRoundEndFunFact( iWinnerTeam, iReason, funfact);
+
+ //Send all the info needed for the win panel
+ IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_round" );
+
+ if ( winEvent )
+ {
+ // determine what categories to send
+ if ( GetRoundRemainingTime() <= 0 )
+ {
+ // timer expired, defenders win
+ // show total time that was defended
+ winEvent->SetBool( "show_timer_defend", true );
+ winEvent->SetInt( "timer_time", m_iRoundTime );
+ }
+ else
+ {
+ // attackers win
+ // show time it took for them to win
+ winEvent->SetBool( "show_timer_attack", true );
+
+ int iTimeElapsed = m_iRoundTime - GetRoundRemainingTime();
+ winEvent->SetInt( "timer_time", iTimeElapsed );
+ }
+
+ winEvent->SetInt( "final_event", iReason );
+
+ // Set the fun fact data in the event
+ winEvent->SetString( "funfact_token", funfact.szLocalizationToken);
+ winEvent->SetInt( "funfact_player", funfact.iPlayer );
+ winEvent->SetInt( "funfact_data1", funfact.iData1 );
+ winEvent->SetInt( "funfact_data2", funfact.iData2 );
+ winEvent->SetInt( "funfact_data3", funfact.iData3 );
+ gameeventmanager->FireEvent( winEvent );
+ }
+ }
+
+ // [tj] Inform players that the round is over
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
+ if(pPlayer)
+ {
+ pPlayer->OnRoundEnd(iWinnerTeam, iReason);
+ }
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ 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();
+ }
+ }
+
+ //=============================================================================
+ // HPE_BEGIN:
+ //=============================================================================
+
+ // Helper to determine if all players on a team are playing for the same clan
+ static bool IsClanTeam( CTeam *pTeam )
+ {
+ uint32 iTeamClan = 0;
+ for ( int iPlayer = 0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ )
+ {
+ CBasePlayer *pPlayer = pTeam->GetPlayer( iPlayer );
+ if ( !pPlayer )
+ return false;
+
+ const char *pClanID = engine->GetClientConVarValue( pPlayer->entindex(), "cl_clanid" );
+ uint32 iPlayerClan = atoi( pClanID );
+ if ( iPlayer == 0 )
+ {
+ // Initialize the team clan
+ iTeamClan = iPlayerClan;
+ }
+ else
+ {
+ if ( iPlayerClan != iTeamClan || iPlayerClan == 0 )
+ return false;
+ }
+ }
+ return iTeamClan != 0;
+ }
+
+ // [tj] This is where we check non-player-specific that occur at the end of the round
+ void CCSGameRules::ProcessEndOfRoundAchievements(int iWinnerTeam, int iReason)
+ {
+ if (iWinnerTeam == WINNER_CT || iWinnerTeam == WINNER_TER)
+ {
+ int losingTeamId = (iWinnerTeam == TEAM_CT) ? TEAM_TERRORIST : TEAM_CT;
+ CTeam* losingTeam = GetGlobalTeam(losingTeamId);
+
+
+ //Check for players we should ignore when checking team size.
+ int ignoreCount = 0;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
+ if (pPlayer)
+ {
+ int teamNum = pPlayer->GetTeamNumber();
+ if ( teamNum == losingTeamId )
+ {
+ if (pPlayer->WasNotKilledNaturally())
+ {
+ ignoreCount++;
+ }
+ }
+ }
+ }
+
+
+ // [tj] Check extermination with no losses achievement
+ if ( ( ( iReason == CTs_Win && m_bNoCTsKilled ) || ( iReason == Terrorists_Win && m_bNoTerroristsKilled ) )
+ && losingTeam && losingTeam->GetNumPlayers() - ignoreCount >= AchievementConsts::DefaultMinOpponentsForAchievement)
+ {
+ CTeam *pTeam = GetGlobalTeam( iWinnerTeam );
+
+ for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) );
+ Assert( pPlayer );
+ if ( !pPlayer )
+ continue;
+
+ pPlayer->AwardAchievement(CSLosslessExtermination);
+ }
+ }
+
+ // [tj] Check flawless victory achievement - currently requiring extermination
+ if (((iReason == CTs_Win && m_bNoCTsDamaged) || (iReason == Terrorists_Win && m_bNoTerroristsDamaged))
+ && losingTeam && losingTeam->GetNumPlayers() - ignoreCount >= AchievementConsts::DefaultMinOpponentsForAchievement)
+ {
+ CTeam *pTeam = GetGlobalTeam( iWinnerTeam );
+
+ for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) );
+ Assert( pPlayer );
+ if ( !pPlayer )
+ continue;
+
+ pPlayer->AwardAchievement(CSFlawlessVictory);
+ }
+ }
+
+ // [tj] Check bloodless victory achievement
+ if (((iWinnerTeam == TEAM_TERRORIST && m_bNoCTsKilled) || (iWinnerTeam == Terrorists_Win && m_bNoTerroristsKilled))
+ && losingTeam && losingTeam->GetNumPlayers() >= AchievementConsts::DefaultMinOpponentsForAchievement)
+ {
+ CTeam *pTeam = GetGlobalTeam( iWinnerTeam );
+
+ for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) );
+ Assert( pPlayer );
+ if ( !pPlayer )
+ continue;
+
+ pPlayer->AwardAchievement(CSBloodlessVictory);
+ }
+ }
+
+ // Check the clan match achievement
+ CTeam *pWinningTeam = GetGlobalTeam( iWinnerTeam );
+ if ( pWinningTeam && pWinningTeam->GetNumPlayers() >= AchievementConsts::DefaultMinOpponentsForAchievement &&
+ losingTeam && losingTeam->GetNumPlayers() - ignoreCount >= AchievementConsts::DefaultMinOpponentsForAchievement &&
+ IsClanTeam( pWinningTeam ) && IsClanTeam( losingTeam ) )
+ {
+ for ( int iPlayer=0; iPlayer < pWinningTeam->GetNumPlayers(); iPlayer++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( pWinningTeam->GetPlayer( iPlayer ) );
+ if ( !pPlayer )
+ continue;
+
+ pPlayer->AwardAchievement( CSWinClanMatch );
+ }
+ }
+ }
+ }
+
+ //[tj] Counts the number of players in each category in the struct (dead, alive, etc...)
+ void CCSGameRules::GetPlayerCounts(TeamPlayerCounts teamCounts[TEAM_MAXCOUNT])
+ {
+ memset(teamCounts, 0, sizeof(TeamPlayerCounts) * TEAM_MAXCOUNT);
+
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
+ if (pPlayer)
+ {
+ int iTeam = pPlayer->GetTeamNumber();
+
+ if (iTeam >= 0 && iTeam < TEAM_MAXCOUNT)
+ {
+ ++teamCounts[iTeam].totalPlayers;
+ if (pPlayer->IsAlive())
+ {
+ ++teamCounts[iTeam].totalAlivePlayers;
+ }
+ else
+ {
+ ++teamCounts[iTeam].totalDeadPlayers;
+
+ //If the player has joined a team bit isn't in the game yet
+ if (pPlayer->State_Get() == STATE_PICKINGCLASS)
+ {
+ ++teamCounts[iTeam].unenteredPlayers;
+ }
+ else if (pPlayer->WasNotKilledNaturally())
+ {
+ ++teamCounts[iTeam].suicidedPlayers;
+ }
+ else
+ {
+ ++teamCounts[iTeam].killedPlayers;
+ }
+ }
+ }
+ }
+ }
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ 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 )
+ {
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Handle round restart processing for the weapon.
+ //=============================================================================
+
+ pWeapon->OnRoundRestart();
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ 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 DBGFLAG_ASSERT
+ 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() )
+ {
+ 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
+ ::V_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) &&
+ collisionGroup1 == COLLISION_GROUP_PUSHAWAY )
+ {
+ return false;
+ }
+
+ if ( collisionGroup0 == COLLISION_GROUP_DEBRIS && collisionGroup1 == COLLISION_GROUP_PUSHAWAY )
+ {
+ // let debris and multiplayer objects collide
+ return true;
+ }
+
+ return BaseClass::ShouldCollide( collisionGroup0, collisionGroup1 );
+}
+
+
+bool CCSGameRules::IsFreezePeriod()
+{
+ return m_bFreezePeriod;
+}
+
+
+bool CCSGameRules::IsVIPMap() const
+{
+ //MIKETODO: VIP mode
+ return false;
+}
+
+
+bool CCSGameRules::IsBombDefuseMap() const
+{
+ return m_bMapHasBombTarget;
+}
+
+bool CCSGameRules::IsHostageRescueMap() const
+{
+ return m_bMapHasRescueZone;
+}
+
+bool CCSGameRules::IsLogoMap() const
+{
+ return m_bLogoMap;
+}
+
+float CCSGameRules::GetBuyTimeLength() const
+{
+ return ( mp_buytime.GetFloat() * 60 );
+}
+
+bool CCSGameRules::IsBuyTimeElapsed()
+{
+ return ( GetRoundElapsedTime() > GetBuyTimeLength() );
+}
+
+int CCSGameRules::DefaultFOV()
+{
+ return 90;
+}
+
+const CViewVectors* CCSGameRules::GetViewVectors() const
+{
+ return &g_CSViewVectors;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Init CS ammo definitions
+//-----------------------------------------------------------------------------
+
+// shared ammo definition
+// JAY: Trying to make a more physical bullet response
+#define BULLET_MASS_GRAINS_TO_LB(grains) (0.002285*(grains)/16.0f)
+#define BULLET_MASS_GRAINS_TO_KG(grains) lbs2kg(BULLET_MASS_GRAINS_TO_LB(grains))
+
+// exaggerate all of the forces, but use real numbers to keep them consistent
+#define BULLET_IMPULSE_EXAGGERATION 1
+
+// convert a velocity in ft/sec and a mass in grains to an impulse in kg in/s
+#define BULLET_IMPULSE(grains, ftpersec) ((ftpersec)*12*BULLET_MASS_GRAINS_TO_KG(grains)*BULLET_IMPULSE_EXAGGERATION)
+
+
+static CCSAmmoDef ammoDef;
+CCSAmmoDef* GetCSAmmoDef()
+{
+ GetAmmoDef(); // to initialize the ammo info
+ return &ammoDef;
+}
+
+CAmmoDef* GetAmmoDef()
+{
+ static bool bInitted = false;
+
+ if ( !bInitted )
+ {
+ bInitted = true;
+
+ ammoDef.AddAmmoType( BULLET_PLAYER_50AE, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_50AE_max", 2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 );
+ ammoDef.AddAmmoType( BULLET_PLAYER_762MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_762mm_max", 2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 );
+ ammoDef.AddAmmoType( BULLET_PLAYER_556MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_556mm_max", 2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 );
+ ammoDef.AddAmmoType( BULLET_PLAYER_556MM_BOX, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_556mm_box_max",2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 );
+ ammoDef.AddAmmoType( BULLET_PLAYER_338MAG, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_338mag_max", 2800 * BULLET_IMPULSE_EXAGGERATION, 0, 12, 16 );
+ ammoDef.AddAmmoType( BULLET_PLAYER_9MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_9mm_max", 2000 * BULLET_IMPULSE_EXAGGERATION, 0, 5, 10 );
+ ammoDef.AddAmmoType( BULLET_PLAYER_BUCKSHOT, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_buckshot_max", 600 * BULLET_IMPULSE_EXAGGERATION, 0, 3, 6 );
+ ammoDef.AddAmmoType( BULLET_PLAYER_45ACP, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_45acp_max", 2100 * BULLET_IMPULSE_EXAGGERATION, 0, 6, 10 );
+ ammoDef.AddAmmoType( BULLET_PLAYER_357SIG, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_357sig_max", 2000 * BULLET_IMPULSE_EXAGGERATION, 0, 4, 8 );
+ ammoDef.AddAmmoType( BULLET_PLAYER_57MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_57mm_max", 2000 * BULLET_IMPULSE_EXAGGERATION, 0, 4, 8 );
+ ammoDef.AddAmmoType( AMMO_TYPE_HEGRENADE, DMG_BLAST, TRACER_LINE, 0, 0, "ammo_hegrenade_max", 1, 0 );
+ ammoDef.AddAmmoType( AMMO_TYPE_FLASHBANG, 0, TRACER_LINE, 0, 0, "ammo_flashbang_max", 1, 0 );
+ ammoDef.AddAmmoType( AMMO_TYPE_SMOKEGRENADE, 0, TRACER_LINE, 0, 0, "ammo_smokegrenade_max", 1, 0 );
+
+ //Adrian: I set all the prices to 0 just so the rest of the buy code works
+ //This should be revisited.
+ ammoDef.AddAmmoCost( BULLET_PLAYER_50AE, 0, 7 );
+ ammoDef.AddAmmoCost( BULLET_PLAYER_762MM, 0, 30 );
+ ammoDef.AddAmmoCost( BULLET_PLAYER_556MM, 0, 30 );
+ ammoDef.AddAmmoCost( BULLET_PLAYER_556MM_BOX, 0, 30 );
+ ammoDef.AddAmmoCost( BULLET_PLAYER_338MAG, 0, 10 );
+ ammoDef.AddAmmoCost( BULLET_PLAYER_9MM, 0, 30 );
+ ammoDef.AddAmmoCost( BULLET_PLAYER_BUCKSHOT, 0, 8 );
+ ammoDef.AddAmmoCost( BULLET_PLAYER_45ACP, 0, 25 );
+ ammoDef.AddAmmoCost( BULLET_PLAYER_357SIG, 0, 13 );
+ ammoDef.AddAmmoCost( BULLET_PLAYER_57MM, 0, 50 );
+ }
+
+ return &ammoDef;
+}
+
+#ifndef CLIENT_DLL
+const char *CCSGameRules::GetChatPrefix( bool bTeamOnly, CBasePlayer *pPlayer )
+{
+ const char *pszPrefix = NULL;
+
+ if ( !pPlayer ) // dedicated server output
+ {
+ pszPrefix = "";
+ }
+ else
+ {
+ // team only
+ if ( bTeamOnly == TRUE )
+ {
+ if ( pPlayer->GetTeamNumber() == TEAM_CT )
+ {
+ if ( pPlayer->m_lifeState == LIFE_ALIVE )
+ {
+ pszPrefix = "(Counter-Terrorist)";
+ }
+ else
+ {
+ pszPrefix = "*DEAD*(Counter-Terrorist)";
+ }
+ }
+ else if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST )
+ {
+ if ( pPlayer->m_lifeState == LIFE_ALIVE )
+ {
+ pszPrefix = "(Terrorist)";
+ }
+ else
+ {
+ pszPrefix = "*DEAD*(Terrorist)";
+ }
+ }
+ else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR )
+ {
+ pszPrefix = "(Spectator)";
+ }
+ }
+ // everyone
+ else
+ {
+ if ( pPlayer->m_lifeState == LIFE_ALIVE )
+ {
+ pszPrefix = "";
+ }
+ else
+ {
+ if ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
+ {
+ pszPrefix = "*DEAD*";
+ }
+ else
+ {
+ pszPrefix = "*SPEC*";
+ }
+ }
+ }
+ }
+
+ return pszPrefix;
+}
+
+const char *CCSGameRules::GetChatLocation( bool bTeamOnly, CBasePlayer *pPlayer )
+{
+ if ( !pPlayer ) // dedicated server output
+ {
+ return NULL;
+ }
+
+ // only teammates see locations
+ if ( !bTeamOnly )
+ return NULL;
+
+ // only living players have locations
+ if ( pPlayer->GetTeamNumber() != TEAM_CT && pPlayer->GetTeamNumber() != TEAM_TERRORIST )
+ return NULL;
+
+ if ( !pPlayer->IsAlive() )
+ return NULL;
+
+ return pPlayer->GetLastKnownPlaceName();
+}
+
+const char *CCSGameRules::GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer )
+{
+ if ( !pPlayer ) // dedicated server output
+ {
+ return NULL;
+ }
+
+ const char *pszFormat = NULL;
+
+ // team only
+ if ( bTeamOnly == TRUE )
+ {
+ if ( pPlayer->GetTeamNumber() == TEAM_CT )
+ {
+ if ( pPlayer->m_lifeState == LIFE_ALIVE )
+ {
+ const char *chatLocation = GetChatLocation( bTeamOnly, pPlayer );
+ if ( chatLocation && *chatLocation )
+ {
+ pszFormat = "Cstrike_Chat_CT_Loc";
+ }
+ else
+ {
+ pszFormat = "Cstrike_Chat_CT";
+ }
+ }
+ else
+ {
+ pszFormat = "Cstrike_Chat_CT_Dead";
+ }
+ }
+ else if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST )
+ {
+ if ( pPlayer->m_lifeState == LIFE_ALIVE )
+ {
+ const char *chatLocation = GetChatLocation( bTeamOnly, pPlayer );
+ if ( chatLocation && *chatLocation )
+ {
+ pszFormat = "Cstrike_Chat_T_Loc";
+ }
+ else
+ {
+ pszFormat = "Cstrike_Chat_T";
+ }
+ }
+ else
+ {
+ pszFormat = "Cstrike_Chat_T_Dead";
+ }
+ }
+ else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR )
+ {
+ pszFormat = "Cstrike_Chat_Spec";
+ }
+ }
+ // everyone
+ else
+ {
+ if ( pPlayer->m_lifeState == LIFE_ALIVE )
+ {
+ pszFormat = "Cstrike_Chat_All";
+ }
+ else
+ {
+ if ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
+ {
+ pszFormat = "Cstrike_Chat_AllDead";
+ }
+ else
+ {
+ pszFormat = "Cstrike_Chat_AllSpec";
+ }
+ }
+ }
+
+ return pszFormat;
+}
+
+void CCSGameRules::ClientSettingsChanged( CBasePlayer *pPlayer )
+{
+ const char *pszNewName = engine->GetClientConVarValue( pPlayer->entindex(), "name" );
+ const char *pszOldName = pPlayer->GetPlayerName();
+ CCSPlayer *pCSPlayer = (CCSPlayer*)pPlayer;
+ if ( pszOldName[0] != 0 && Q_strncmp( pszOldName, pszNewName, MAX_PLAYER_NAME_LENGTH-1 ) )
+ {
+ pCSPlayer->ChangeName( pszNewName );
+ }
+
+ pCSPlayer->m_bShowHints = true;
+ if ( pCSPlayer->IsNetClient() )
+ {
+ const char *pShowHints = engine->GetClientConVarValue( engine->IndexOfEdict( pCSPlayer->edict() ), "cl_autohelp" );
+ if ( pShowHints && atoi( pShowHints ) <= 0 )
+ {
+ pCSPlayer->m_bShowHints = false;
+ }
+ }
+}
+
+bool CCSGameRules::FAllowNPCs( void )
+{
+ return false;
+}
+
+bool CCSGameRules::IsFriendlyFireOn( void )
+{
+ return friendlyfire.GetBool();
+}
+
+
+CON_COMMAND( map_showspawnpoints, "Shows player spawn points (red=invalid)" )
+{
+ CSGameRules()->ShowSpawnPoints();
+}
+
+void DrawSphere( const Vector& pos, float radius, int r, int g, int b, float lifetime )
+{
+ Vector edge, lastEdge;
+ NDebugOverlay::Line( pos, pos + Vector( 0, 0, 50 ), r, g, b, true, lifetime );
+
+ lastEdge = Vector( radius + pos.x, pos.y, pos.z );
+ float angle;
+ for( angle=0.0f; angle <= 360.0f; angle += 22.5f )
+ {
+ edge.x = radius * BotCOS( angle ) + pos.x;
+ edge.y = pos.y;
+ edge.z = radius * BotSIN( angle ) + pos.z;
+
+ NDebugOverlay::Line( edge, lastEdge, r, g, b, true, lifetime );
+
+ lastEdge = edge;
+ }
+
+ lastEdge = Vector( pos.x, radius + pos.y, pos.z );
+ for( angle=0.0f; angle <= 360.0f; angle += 22.5f )
+ {
+ edge.x = pos.x;
+ edge.y = radius * BotCOS( angle ) + pos.y;
+ edge.z = radius * BotSIN( angle ) + pos.z;
+
+ NDebugOverlay::Line( edge, lastEdge, r, g, b, true, lifetime );
+
+ lastEdge = edge;
+ }
+
+ lastEdge = Vector( pos.x, radius + pos.y, pos.z );
+ for( angle=0.0f; angle <= 360.0f; angle += 22.5f )
+ {
+ edge.x = radius * BotCOS( angle ) + pos.x;
+ edge.y = radius * BotSIN( angle ) + pos.y;
+ edge.z = pos.z;
+
+ NDebugOverlay::Line( edge, lastEdge, r, g, b, true, lifetime );
+
+ lastEdge = edge;
+ }
+}
+
+CON_COMMAND_F( map_showbombradius, "Shows bomb radius from the center of each bomb site and planted bomb.", FCVAR_CHEAT )
+{
+ float flBombDamage = 500.0f;
+ if ( g_pMapInfo )
+ flBombDamage = g_pMapInfo->m_flBombRadius;
+ float flBombRadius = flBombDamage * 3.5f;
+ Msg( "Bomb Damage is %.0f, Radius is %.0f\n", flBombDamage, flBombRadius );
+
+ CBaseEntity* ent = NULL;
+ while ( ( ent = gEntList.FindEntityByClassname( ent, "func_bomb_target" ) ) != NULL )
+ {
+ const Vector &pos = ent->WorldSpaceCenter();
+ DrawSphere( pos, flBombRadius, 255, 255, 0, 10 );
+ }
+
+ ent = NULL;
+ while ( ( ent = gEntList.FindEntityByClassname( ent, "planted_c4" ) ) != NULL )
+ {
+ const Vector &pos = ent->WorldSpaceCenter();
+ DrawSphere( pos, flBombRadius, 255, 0, 0, 10 );
+ }
+}
+
+CON_COMMAND_F( map_setbombradius, "Sets the bomb radius for the map.", FCVAR_CHEAT )
+{
+ if ( args.ArgC() != 2 )
+ return;
+
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ if ( !g_pMapInfo )
+ CBaseEntity::Create( "info_map_parameters", vec3_origin, vec3_angle );
+
+ if ( !g_pMapInfo )
+ return;
+
+ g_pMapInfo->m_flBombRadius = atof( args[1] );
+ map_showbombradius( args );
+}
+
+void CreateBlackMarketString( void )
+{
+ g_StringTableBlackMarket = networkstringtable->CreateStringTable( "BlackMarketTable" , 1 );
+}
+
+int CCSGameRules::GetStartMoney( void )
+{
+ if ( IsBlackMarket() )
+ {
+ return atoi( mp_startmoney.GetDefault() );
+ }
+
+ return mp_startmoney.GetInt();
+}
+
+
+
+//=============================================================================
+// HPE_BEGIN:
+// [menglish] Set up anything for all players that changes based on new players spawning mid-game
+// Find and return fun fact data
+//=============================================================================
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when a player joins the game after it's started yet can still spawn in
+//-----------------------------------------------------------------------------
+void CCSGameRules::SpawningLatePlayer( CCSPlayer* pLatePlayer )
+{
+ //Reset the round kills number of enemies for the opposite team
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
+ if(pPlayer)
+ {
+ if(pPlayer->GetTeamNumber() == pLatePlayer->GetTeamNumber())
+ {
+ continue;
+ }
+ pPlayer->m_NumEnemiesAtRoundStart++;
+ }
+ }
+}
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+//=============================================================================
+// HPE_BEGIN:
+// [pfreese] Test for "pistol" round, defined as the default starting round
+// when players cannot purchase anything primary weapons
+//=============================================================================
+
+bool CCSGameRules::IsPistolRound()
+{
+ return m_iTotalRoundsPlayed == 0 && GetStartMoney() <= 800;
+}
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+//=============================================================================
+// HPE_BEGIN:
+// [tj] So game rules can react to damage taken
+// [menglish]
+//=============================================================================
+
+void CCSGameRules::PlayerTookDamage(CCSPlayer* player, const CTakeDamageInfo &damageInfo)
+{
+ CBaseEntity *pInflictor = damageInfo.GetInflictor();
+ CBaseEntity *pAttacker = damageInfo.GetAttacker();
+ CCSPlayer *pCSScorer = (CCSPlayer *)(GetDeathScorer( pAttacker, pInflictor ));
+
+ if ( player && pCSScorer )
+ {
+ if (player->GetTeamNumber() == TEAM_CT)
+ {
+ m_bNoCTsDamaged = false;
+ }
+
+ if (player->GetTeamNumber() == TEAM_TERRORIST)
+ {
+ m_bNoTerroristsDamaged = false;
+ }
+ // set the first blood if this is the first and the victim is on a different team then the player
+ if ( m_pFirstBlood == NULL && pCSScorer != player && pCSScorer->GetTeamNumber() != player ->GetTeamNumber() )
+ {
+ m_pFirstBlood = pCSScorer;
+ m_firstBloodTime = gpGlobals->curtime - m_fRoundStartTime;
+ }
+ }
+}
+
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+#endif
+
+bool CCSGameRules::IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer )
+{
+#ifdef GAME_DLL
+ if( pPlayer )
+ {
+ int iPlayerTeam = pPlayer->GetTeamNumber();
+ if( ( iPlayerTeam == TEAM_CT ) || ( iPlayerTeam == TEAM_TERRORIST ) )
+ return false;
+ }
+#else
+ int iLocalPlayerTeam = GetLocalPlayerTeam();
+ if( ( iLocalPlayerTeam == TEAM_CT ) || ( iLocalPlayerTeam == TEAM_TERRORIST ) )
+ return false;
+#endif
+
+ return true;
+}
+
+#ifdef GAME_DLL
+
+struct convar_tags_t
+{
+ const char *pszConVar;
+ const char *pszTag;
+};
+
+// The list of convars that automatically turn on tags when they're changed.
+// Convars in this list need to have the FCVAR_NOTIFY flag set on them, so the
+// tags are recalculated and uploaded to the master server when the convar is changed.
+convar_tags_t convars_to_check_for_tags[] =
+{
+ { "mp_friendlyfire", "friendlyfire" },
+ { "bot_quota", "bots" },
+ { "sv_nostats", "nostats" },
+ { "mp_startmoney", "startmoney" },
+ { "sv_allowminmodels", "nominmodels" },
+ { "sv_enablebunnyhopping", "bunnyhopping" },
+ { "sv_competitive_minspec", "compspec" },
+ { "mp_holiday_nogifts", "nogifts" },
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Engine asks for the list of convars that should tag the server
+//-----------------------------------------------------------------------------
+void CCSGameRules::GetTaggedConVarList( KeyValues *pCvarTagList )
+{
+ BaseClass::GetTaggedConVarList( pCvarTagList );
+
+ for ( int i = 0; i < ARRAYSIZE(convars_to_check_for_tags); i++ )
+ {
+ KeyValues *pKV = new KeyValues( "tag" );
+ pKV->SetString( "convar", convars_to_check_for_tags[i].pszConVar );
+ pKV->SetString( "tag", convars_to_check_for_tags[i].pszTag );
+
+ pCvarTagList->AddSubKey( pKV );
+ }
+}
+
+#endif
+
+
+int CCSGameRules::GetBlackMarketPriceForWeapon( int iWeaponID )
+{
+ if ( m_pPrices == NULL )
+ {
+ GetBlackMarketPriceList();
+ }
+
+ if ( m_pPrices )
+ return m_pPrices->iCurrentPrice[iWeaponID];
+ else
+ return 0;
+}
+
+int CCSGameRules::GetBlackMarketPreviousPriceForWeapon( int iWeaponID )
+{
+ if ( m_pPrices == NULL )
+ {
+ GetBlackMarketPriceList();
+ }
+
+ if ( m_pPrices )
+ return m_pPrices->iPreviousPrice[iWeaponID];
+ else
+ return 0;
+}
+
+const weeklyprice_t *CCSGameRules::GetBlackMarketPriceList( void )
+{
+ if ( m_StringTableBlackMarket == NULL )
+ {
+ m_StringTableBlackMarket = networkstringtable->FindTable( CS_GAMERULES_BLACKMARKET_TABLE_NAME);
+ }
+
+ if ( m_pPrices == NULL )
+ {
+ int iSize = 0;
+ INetworkStringTable *pTable = m_StringTableBlackMarket;
+ if ( pTable && pTable->GetNumStrings() > 0 )
+ {
+ m_pPrices = (const weeklyprice_t *)pTable->GetStringUserData( 0, &iSize );
+ }
+ }
+
+ if ( m_pPrices )
+ {
+ PrepareEquipmentInfo();
+ }
+
+ return m_pPrices;
+}
+
+void CCSGameRules::SetBlackMarketPrices( bool bSetDefaults )
+{
+ for ( int i = 1; i < WEAPON_MAX; i++ )
+ {
+ if ( i == WEAPON_SHIELDGUN )
+ continue;
+
+ CCSWeaponInfo *info = GetWeaponInfo( (CSWeaponID)i );
+
+ if ( info == NULL )
+ continue;
+
+ if ( bSetDefaults == false )
+ {
+ info->SetWeaponPrice( GetBlackMarketPriceForWeapon( i ) );
+ info->SetPreviousPrice( GetBlackMarketPreviousPriceForWeapon( i ) );
+ }
+ else
+ {
+ info->SetWeaponPrice( info->GetDefaultPrice() );
+ }
+ }
+}
+
+#ifdef CLIENT_DLL
+
+CCSGameRules::CCSGameRules()
+{
+ CSGameRules()->m_StringTableBlackMarket = NULL;
+ m_pPrices = NULL;
+ m_bBlackMarket = false;
+}
+
+void TestTable( void )
+{
+ CSGameRules()->m_StringTableBlackMarket = networkstringtable->FindTable( CS_GAMERULES_BLACKMARKET_TABLE_NAME);
+
+ if ( CSGameRules()->m_StringTableBlackMarket == NULL )
+ return;
+
+ int iIndex = CSGameRules()->m_StringTableBlackMarket->FindStringIndex( "blackmarket_prices" );
+ int iSize = 0;
+
+ const weeklyprice_t *pPrices = NULL;
+
+ pPrices = (const weeklyprice_t *)(CSGameRules()->m_StringTableBlackMarket)->GetStringUserData( iIndex, &iSize );
+}
+
+#ifdef DEBUG
+ConCommand cs_testtable( "cs_testtable", TestTable );
+#endif
+
+//-----------------------------------------------------------------------------
+// Enforce certain values on the specified convar.
+//-----------------------------------------------------------------------------
+void EnforceCompetitiveCVar( const char *szCvarName, float fMinValue, float fMaxValue = FLT_MAX, int iArgs = 0, ... )
+{
+ // Doing this check first because OK values might be outside the min/max range
+ ConVarRef competitiveConvar(szCvarName);
+ float fValue = competitiveConvar.GetFloat();
+ va_list vl;
+ va_start(vl, iArgs);
+ for( int i=0; i< iArgs; ++i )
+ {
+ if( (int)fValue == (int)va_arg(vl,double) )
+ return;
+ }
+ va_end(vl);
+
+ if( fValue < fMinValue || fValue > fMaxValue )
+ {
+ float fNewValue = MAX( MIN( fValue, fMaxValue ), fMinValue );
+ competitiveConvar.SetValue( fNewValue );
+ DevMsg( "Convar %s enforced by server (see sv_competitive_minspec.) Set to %2f.\n", szCvarName, fNewValue );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// An interface used by ENABLE_COMPETITIVE_CONVAR macro that lets the classes
+// defined in the macro to be stored and acted on.
+//-----------------------------------------------------------------------------
+class ICompetitiveConvar
+{
+public:
+ // It is a best practice to always have a virtual destructor in an interface
+ // class. Otherwise if the derived classes have destructors they will not be
+ // called.
+ virtual ~ICompetitiveConvar() {}
+ virtual void BackupConvar() = 0;
+ virtual void EnforceRestrictions() = 0;
+ virtual void RestoreOriginalValue() = 0;
+ virtual void InstallChangeCallback() = 0;
+};
+
+//-----------------------------------------------------------------------------
+// A manager for all enforced competitive convars.
+//-----------------------------------------------------------------------------
+class CCompetitiveCvarManager : public CAutoGameSystem
+{
+public:
+ typedef CUtlVector<ICompetitiveConvar*> CompetitiveConvarList_t;
+ static void AddConvarToList( ICompetitiveConvar* pCVar )
+ {
+ GetConvarList()->AddToTail( pCVar );
+ }
+
+ static void BackupAllConvars()
+ {
+ FOR_EACH_VEC( *GetConvarList(), i )
+ {
+ (*GetConvarList())[i]->BackupConvar();
+ }
+ }
+
+ static void EnforceRestrictionsOnAllConvars()
+ {
+ FOR_EACH_VEC( *GetConvarList(), i )
+ {
+ (*GetConvarList())[i]->EnforceRestrictions();
+ }
+ }
+
+ static void RestoreAllOriginalValues()
+ {
+ FOR_EACH_VEC( *GetConvarList(), i )
+ {
+ (*GetConvarList())[i]->RestoreOriginalValue();
+ }
+ }
+
+ static CompetitiveConvarList_t* GetConvarList()
+ {
+ if( !s_pCompetitiveConvars )
+ {
+ s_pCompetitiveConvars = new CompetitiveConvarList_t();
+ }
+ return s_pCompetitiveConvars;
+ }
+
+ static KeyValues* GetConVarBackupKV()
+ {
+ if( !s_pConVarBackups )
+ {
+ s_pConVarBackups = new KeyValues("ConVarBackups");
+ }
+ return s_pConVarBackups;
+ }
+
+ virtual bool Init()
+ {
+ FOR_EACH_VEC( *GetConvarList(), i )
+ {
+ (*GetConvarList())[i]->InstallChangeCallback();
+ }
+ return true;
+ }
+
+ virtual void Shutdown()
+ {
+ FOR_EACH_VEC( *GetConvarList(), i )
+ {
+ delete (*GetConvarList())[i];
+ }
+ delete s_pCompetitiveConvars;
+ s_pCompetitiveConvars = null;
+ s_pConVarBackups->deleteThis();
+ s_pConVarBackups = null;
+ }
+private:
+ static CompetitiveConvarList_t* s_pCompetitiveConvars;
+ static KeyValues* s_pConVarBackups;
+};
+static CCompetitiveCvarManager *s_pCompetitiveCvarManager = new CCompetitiveCvarManager();
+CCompetitiveCvarManager::CompetitiveConvarList_t* CCompetitiveCvarManager::s_pCompetitiveConvars = null;
+KeyValues* CCompetitiveCvarManager::s_pConVarBackups = null;
+
+//-----------------------------------------------------------------------------
+// Macro to define restrictions on convars with "sv_competitive_minspec 1"
+// Usage: ENABLE_COMPETITIVE_CONVAR( convarName, minValue, maxValue, optionalValues, opVal1, opVal2, ...
+//-----------------------------------------------------------------------------
+#define ENABLE_COMPETITIVE_CONVAR( convarName, ... ) \
+class CCompetitiveMinspecConvar##convarName : public ICompetitiveConvar { \
+public: \
+ CCompetitiveMinspecConvar##convarName(){ CCompetitiveCvarManager::AddConvarToList(this);} \
+ static void on_changed_##convarName( IConVar *var, const char *pOldValue, float flOldValue ){ \
+ if( sv_competitive_minspec.GetBool() ) { \
+ EnforceCompetitiveCVar( #convarName , __VA_ARGS__ ); }\
+ else {\
+ CCompetitiveCvarManager::GetConVarBackupKV()->SetFloat( #convarName, ConVarRef( #convarName ).GetFloat() ); } } \
+ virtual void BackupConvar() { CCompetitiveCvarManager::GetConVarBackupKV()->SetFloat( #convarName, ConVarRef( #convarName ).GetFloat() ); } \
+ virtual void EnforceRestrictions() { EnforceCompetitiveCVar( #convarName , __VA_ARGS__ ); } \
+ virtual void RestoreOriginalValue() { ConVarRef(#convarName).SetValue(CCompetitiveCvarManager::GetConVarBackupKV()->GetFloat( #convarName ) ); } \
+ virtual void InstallChangeCallback() { static_cast<ConVar*>(ConVarRef( #convarName ).GetLinkedConVar())->InstallChangeCallback( CCompetitiveMinspecConvar##convarName::on_changed_##convarName); } \
+}; \
+static CCompetitiveMinspecConvar##convarName *s_pCompetitiveConvar##convarName = new CCompetitiveMinspecConvar##convarName();
+
+//-----------------------------------------------------------------------------
+// Callback function for sv_competitive_minspec convar value change.
+//-----------------------------------------------------------------------------
+void sv_competitive_minspec_changed_f( IConVar *var, const char *pOldValue, float flOldValue )
+{
+ ConVar *pCvar = static_cast<ConVar*>(var);
+
+ if( pCvar->GetBool() == true && (bool)flOldValue == false )
+ {
+ // Backup the values of each cvar and enforce new ones
+ CCompetitiveCvarManager::BackupAllConvars();
+ CCompetitiveCvarManager::EnforceRestrictionsOnAllConvars();
+ }
+ else if( pCvar->GetBool() == false && (bool)flOldValue == true )
+ {
+ // If sv_competitive_minspec is disabled, restore old client values
+ CCompetitiveCvarManager::RestoreAllOriginalValues();
+ }
+}
+#endif
+
+static ConVar sv_competitive_minspec( "sv_competitive_minspec",
+ "0",
+ FCVAR_REPLICATED | FCVAR_NOTIFY,
+ "Enable to force certain client convars to minimum/maximum values to help prevent competitive advantages:\n \
+ r_drawdetailprops = 1\n \
+ r_staticprop_lod = minimum -1 maximum 3\n \
+ fps_max minimum 59 (0 works too)\n \
+ cl_detailfade minimum 400\n \
+ cl_detaildist minimum 1200\n \
+ cl_interp_ratio = minimum 1 maximum 2\n \
+ cl_interp = minimum 0 maximum 0.031\n \
+ "
+#ifdef CLIENT_DLL
+ ,sv_competitive_minspec_changed_f
+#endif
+ );
+
+#ifdef CLIENT_DLL
+
+ENABLE_COMPETITIVE_CONVAR( r_drawdetailprops, true, true ); // force r_drawdetailprops on
+ENABLE_COMPETITIVE_CONVAR( r_staticprop_lod, -1, 3 ); // force r_staticprop_lod from -1 to 3
+ENABLE_COMPETITIVE_CONVAR( fps_max, 59, FLT_MAX, 1, 0 ); // force fps_max above 59. One additional value (0) works
+ENABLE_COMPETITIVE_CONVAR( cl_detailfade, 400 ); // force cl_detailfade above 400.
+ENABLE_COMPETITIVE_CONVAR( cl_detaildist, 1200 ); // force cl_detaildist above 1200.
+ENABLE_COMPETITIVE_CONVAR( cl_interp_ratio, 1, 2 ); // force cl_interp_ratio from 1 to 2
+ENABLE_COMPETITIVE_CONVAR( cl_interp, 0, 0.031 ); // force cl_interp from 0.0152 to 0.031
+
+// Stubs for replay client code
+const char *GetMapDisplayName( const char *pMapName )
+{
+ return pMapName;
+}
+
+bool IsTakingAFreezecamScreenshot()
+{
+ return false;
+}
+
+#endif