//========= 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