//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================

#include "cbase.h"
#include "mp_shareddefs.h"
#include "teamplayroundbased_gamerules.h"

#ifdef CLIENT_DLL
	#include "iclientmode.h"
	#include <vgui_controls/AnimationController.h>
	#include <igameevents.h>
	#include "c_team.h"
	#include "c_playerresource.h"
	#define CTeam C_Team

#else
	#include "viewport_panel_names.h"
	#include "team.h"
	#include "mapentities.h"
	#include "gameinterface.h"
	#include "eventqueue.h"
	#include "team_control_point_master.h"
	#include "team_train_watcher.h"
	#include "serverbenchmark_base.h"

#if defined( REPLAY_ENABLED )	
	#include "replay/ireplaysystem.h"
	#include "replay/iserverreplaycontext.h"
	#include "replay/ireplaysessionrecorder.h"
#endif // REPLAY_ENABLED
#endif

#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
	#include "tf_gamerules.h"
	#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
		#include "tf_lobby.h"
		#ifdef GAME_DLL
			#include "player_vs_environment/tf_population_manager.h"
			#include "../server/tf/tf_gc_server.h"
			#include "../server/tf/tf_objective_resource.h"
		#else
			#include "../client/tf/tf_gc_client.h"
			#include "../client/tf/c_tf_objective_resource.h"
		#endif // GAME_DLL
	#endif // #if defined(TF_CLIENT_DLL) || defined(TF_DLL)
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#ifndef CLIENT_DLL
CUtlVector< CHandle<CTeamControlPointMaster> >		g_hControlPointMasters;

extern bool IsInCommentaryMode( void );

#if defined( REPLAY_ENABLED )
extern IReplaySystem *g_pReplay;
#endif // REPLAY_ENABLED
#endif

extern ConVar spec_freeze_time;
extern ConVar spec_freeze_traveltime;

#ifdef CLIENT_DLL
void RecvProxy_TeamplayRoundState( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
	CTeamplayRoundBasedRules *pGamerules = ( CTeamplayRoundBasedRules *)pStruct;
	int iRoundState = pData->m_Value.m_Int;
	pGamerules->SetRoundState( iRoundState );
}
#endif 

BEGIN_NETWORK_TABLE_NOBASE( CTeamplayRoundBasedRules, DT_TeamplayRoundBasedRules )
#ifdef CLIENT_DLL
	RecvPropInt( RECVINFO( m_iRoundState ), 0, RecvProxy_TeamplayRoundState ),
	RecvPropBool( RECVINFO( m_bInWaitingForPlayers ) ),
	RecvPropInt( RECVINFO( m_iWinningTeam ) ),
	RecvPropInt( RECVINFO( m_bInOvertime ) ),
	RecvPropInt( RECVINFO( m_bInSetup ) ),
	RecvPropInt( RECVINFO( m_bSwitchedTeamsThisRound ) ),
	RecvPropBool( RECVINFO( m_bAwaitingReadyRestart ) ),
	RecvPropTime( RECVINFO( m_flRestartRoundTime ) ),
	RecvPropTime( RECVINFO( m_flMapResetTime ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_flNextRespawnWave), RecvPropTime( RECVINFO(m_flNextRespawnWave[0]) ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_TeamRespawnWaveTimes), RecvPropFloat( RECVINFO(m_TeamRespawnWaveTimes[0]) ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_bTeamReady), RecvPropBool( RECVINFO(m_bTeamReady[0]) ) ),
	RecvPropBool( RECVINFO( m_bStopWatch ) ),
	RecvPropBool( RECVINFO( m_bMultipleTrains ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_bPlayerReady), RecvPropBool( RECVINFO(m_bPlayerReady[0]) ) ),

#else
	SendPropInt( SENDINFO( m_iRoundState ), 5 ),
	SendPropBool( SENDINFO( m_bInWaitingForPlayers ) ),
	SendPropInt( SENDINFO( m_iWinningTeam ), 3, SPROP_UNSIGNED ),
	SendPropBool( SENDINFO( m_bInOvertime ) ),
	SendPropBool( SENDINFO( m_bInSetup ) ),
	SendPropBool( SENDINFO( m_bSwitchedTeamsThisRound ) ),
	SendPropBool( SENDINFO( m_bAwaitingReadyRestart ) ),
	SendPropTime( SENDINFO( m_flRestartRoundTime ) ),
	SendPropTime( SENDINFO( m_flMapResetTime ) ),
	SendPropArray3( SENDINFO_ARRAY3(m_flNextRespawnWave), SendPropTime( SENDINFO_ARRAY(m_flNextRespawnWave) ) ),
	SendPropArray3( SENDINFO_ARRAY3(m_TeamRespawnWaveTimes), SendPropFloat( SENDINFO_ARRAY(m_TeamRespawnWaveTimes) ) ),
	SendPropArray3( SENDINFO_ARRAY3(m_bTeamReady), SendPropBool( SENDINFO_ARRAY(m_bTeamReady) ) ),
	SendPropBool( SENDINFO( m_bStopWatch ) ),
	SendPropBool( SENDINFO( m_bMultipleTrains ) ),
	SendPropArray3( SENDINFO_ARRAY3(m_bPlayerReady), SendPropBool( SENDINFO_ARRAY(m_bPlayerReady) ) ),
#endif
END_NETWORK_TABLE()

IMPLEMENT_NETWORKCLASS_ALIASED( TeamplayRoundBasedRulesProxy, DT_TeamplayRoundBasedRulesProxy )

#ifdef CLIENT_DLL
void RecvProxy_TeamplayRoundBasedRules( const RecvProp *pProp, void **pOut, void *pData, int objectID )
{
	CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
	Assert( pRules );
	*pOut = pRules;
}

BEGIN_RECV_TABLE( CTeamplayRoundBasedRulesProxy, DT_TeamplayRoundBasedRulesProxy )
	RecvPropDataTable( "teamplayroundbased_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_TeamplayRoundBasedRules ), RecvProxy_TeamplayRoundBasedRules )
END_RECV_TABLE()

void CTeamplayRoundBasedRulesProxy::OnPreDataChanged( DataUpdateType_t updateType )
{
	BaseClass::OnPreDataChanged( updateType );
	// Reroute data changed calls to the non-entity gamerules 
	CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
	Assert( pRules );
	pRules->OnPreDataChanged(updateType);
}
void CTeamplayRoundBasedRulesProxy::OnDataChanged( DataUpdateType_t updateType )
{
	BaseClass::OnDataChanged( updateType );
	// Reroute data changed calls to the non-entity gamerules 
	CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
	Assert( pRules );
	pRules->OnDataChanged(updateType);
}

#else
void* SendProxy_TeamplayRoundBasedRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID )
{
	CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
	Assert( pRules );
	pRecipients->SetAllRecipients();
	return pRules;
}

BEGIN_SEND_TABLE( CTeamplayRoundBasedRulesProxy, DT_TeamplayRoundBasedRulesProxy )
	SendPropDataTable( "teamplayroundbased_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_TeamplayRoundBasedRules ), SendProxy_TeamplayRoundBasedRules )
END_SEND_TABLE()

BEGIN_DATADESC( CTeamplayRoundBasedRulesProxy )
	// Inputs.
	DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetStalemateOnTimelimit", InputSetStalemateOnTimelimit ),
END_DATADESC()

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRulesProxy::InputSetStalemateOnTimelimit( inputdata_t &inputdata )
{
	TeamplayRoundBasedRules()->SetStalemateOnTimelimit( inputdata.value.Bool() );
}
#endif

ConVar mp_capstyle( "mp_capstyle", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Sets the style of capture points used. 0 = Fixed players required to cap. 1 = More players cap faster, but longer cap times." );
ConVar mp_blockstyle( "mp_blockstyle", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Sets the style of capture point blocking used. 0 = Blocks break captures completely. 1 = Blocks only pause captures." );
ConVar mp_respawnwavetime( "mp_respawnwavetime", "10.0", FCVAR_NOTIFY | FCVAR_REPLICATED, "Time between respawn waves." );
ConVar mp_capdeteriorate_time( "mp_capdeteriorate_time", "90.0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Time it takes for a full capture point to deteriorate." );
ConVar mp_tournament( "mp_tournament", "0", FCVAR_REPLICATED | FCVAR_NOTIFY );

#if defined( TF_CLIENT_DLL ) || defined( TF_DLL )
ConVar mp_highlander( "mp_highlander", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Allow only 1 of each player class type." );
#endif

//Arena Mode
ConVar tf_arena_preround_time( "tf_arena_preround_time", "10", FCVAR_NOTIFY | FCVAR_REPLICATED, "Length of the Pre-Round time", true, 5.0, true, 15.0 );
ConVar tf_arena_round_time( "tf_arena_round_time", "0", FCVAR_NOTIFY | FCVAR_REPLICATED );
ConVar tf_arena_max_streak( "tf_arena_max_streak", "3", FCVAR_NOTIFY | FCVAR_REPLICATED, "Teams will be scrambled if one team reaches this streak" );
ConVar tf_arena_use_queue( "tf_arena_use_queue", "1", FCVAR_REPLICATED | FCVAR_NOTIFY, "Enables the spectator queue system for Arena." );

ConVar mp_teams_unbalance_limit( "mp_teams_unbalance_limit", "1", FCVAR_REPLICATED | FCVAR_NOTIFY,
					 "Teams are unbalanced when one team has this many more players than the other team. (0 disables check)",
					 true, 0,	// min value
					 true, 30	// max value
					 );

ConVar mp_maxrounds( "mp_maxrounds", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "max number of rounds to play before server changes maps", true, 0, false, 0 );
ConVar mp_winlimit( "mp_winlimit", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Max score one team can reach before server changes maps", true, 0, false, 0 );
ConVar mp_disable_respawn_times( "mp_disable_respawn_times", "0", FCVAR_NOTIFY | FCVAR_REPLICATED );
ConVar mp_bonusroundtime( "mp_bonusroundtime", "15", FCVAR_REPLICATED, "Time after round win until round restarts", true, 5, true, 15 );
ConVar mp_stalemate_meleeonly( "mp_stalemate_meleeonly", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Restrict everyone to melee weapons only while in Sudden Death." );
ConVar mp_forceautoteam( "mp_forceautoteam", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Automatically assign players to teams when joining." );

#ifdef GAME_DLL
ConVar mp_showroundtransitions( "mp_showroundtransitions", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Show gamestate round transitions." );
ConVar mp_enableroundwaittime( "mp_enableroundwaittime", "1", FCVAR_REPLICATED, "Enable timers to wait between rounds." );
ConVar mp_showcleanedupents( "mp_showcleanedupents", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Show entities that are removed on round respawn." );
ConVar mp_restartround( "mp_restartround", "0", FCVAR_GAMEDLL, "If non-zero, the current round will restart in the specified number of seconds" );	

ConVar mp_stalemate_timelimit( "mp_stalemate_timelimit", "240", FCVAR_REPLICATED, "Timelimit (in seconds) of the stalemate round." );
ConVar mp_autoteambalance( "mp_autoteambalance", "1", FCVAR_NOTIFY );

ConVar mp_stalemate_enable( "mp_stalemate_enable", "0", FCVAR_NOTIFY, "Enable/Disable stalemate mode." );
ConVar mp_match_end_at_timelimit( "mp_match_end_at_timelimit", "0", FCVAR_NOTIFY, "Allow the match to end when mp_timelimit hits instead of waiting for the end of the current round." );

ConVar mp_holiday_nogifts( "mp_holiday_nogifts", "0", FCVAR_NOTIFY, "Set to 1 to prevent holiday gifts from spawning when players are killed." );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------	
void cc_SwitchTeams( const CCommand& args )
{
	if ( UTIL_IsCommandIssuedByServerAdmin() )
	{
		CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );

		if ( pRules )
		{
			pRules->SetSwitchTeams( true );
			mp_restartgame.SetValue( 5 );
			pRules->ShouldResetScores( false, false );
			pRules->ShouldResetRoundsPlayed( false );
		}
	}
}

static ConCommand mp_switchteams( "mp_switchteams", cc_SwitchTeams, "Switch teams and restart the game" );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------	
void cc_ScrambleTeams( const CCommand& args )
{
	if ( UTIL_IsCommandIssuedByServerAdmin() )
	{
		CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );

		if ( pRules )
		{
			pRules->SetScrambleTeams( true );
			mp_restartgame.SetValue( 5 );
			pRules->ShouldResetScores( true, false );

			if ( args.ArgC() == 2 )
			{
				// Don't reset the roundsplayed when mp_scrambleteams 2 is passed
				if ( atoi( args[1] ) == 2 )
				{
					pRules->ShouldResetRoundsPlayed( false );
				}
			}
		}
	}
}

static ConCommand mp_scrambleteams( "mp_scrambleteams", cc_ScrambleTeams, "Scramble the teams and restart the game" );
ConVar mp_scrambleteams_auto( "mp_scrambleteams_auto", "1", FCVAR_NOTIFY, "Server will automatically scramble the teams if criteria met.  Only works on dedicated servers." );
ConVar mp_scrambleteams_auto_windifference( "mp_scrambleteams_auto_windifference", "2", FCVAR_NOTIFY, "Number of round wins a team must lead by in order to trigger an auto scramble." );

// Classnames of entities that are preserved across round restarts
static const char *s_PreserveEnts[] =
{
	"player",
	"viewmodel",
	"worldspawn",
	"soundent",
	"ai_network",
	"ai_hint",
	"env_soundscape",
	"env_soundscape_proxy",
	"env_soundscape_triggerable",
	"env_sprite",
	"env_sun",
	"env_wind",
	"env_fog_controller",
	"func_wall",
	"func_illusionary",
	"info_node",
	"info_target",
	"info_node_hint",
	"point_commentary_node",
	"point_viewcontrol",
	"func_precipitation",
	"func_team_wall",
	"shadow_control",
	"sky_camera",
	"scene_manager",
	"trigger_soundscape",
	"commentary_auto",
	"point_commentary_node",
	"point_commentary_viewpoint",
	"bot_roster",
	"info_populator",
	"", // END Marker
};

CON_COMMAND_F( mp_forcewin, "Forces team to win", FCVAR_CHEAT )
{
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
	if ( pRules )
	{
		int iTeam = TEAM_UNASSIGNED;		
		if ( args.ArgC() == 1 )
		{
			// if no team specified, use player 1's team
			iTeam = UTIL_PlayerByIndex( 1 )->GetTeamNumber();	
		}
		else if ( args.ArgC() == 2 )
		{
			// if team # specified, use that
			iTeam = atoi( args[1] );
		}
		else
		{
			Msg( "Usage: mp_forcewin <opt: team#>" );
			return;
		}

		int iWinReason = ( TEAM_UNASSIGNED == iTeam ? WINREASON_STALEMATE : WINREASON_ALL_POINTS_CAPTURED );
		pRules->SetWinningTeam( iTeam, iWinReason );
	}
}

#endif // GAME_DLL

// Utility function
bool FindInList( const char **pStrings, const char *pToFind )
{
	int i = 0;
	while ( pStrings[i][0] != 0 )
	{
		if ( Q_stricmp( pStrings[i], pToFind ) == 0 )
			return true;
		i++;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTeamplayRoundBasedRules::CTeamplayRoundBasedRules( void )
{
	for ( int i = 0; i < MAX_TEAMS; i++ )
	{
		m_flNextRespawnWave.Set( i, 0 );
		m_TeamRespawnWaveTimes.Set( i, -1.0f );
			
		m_bTeamReady.Set( i, false );

#ifdef GAME_DLL
		m_flOriginalTeamRespawnWaveTime[i] = -1.0f;
#endif
	}

	for ( int i = 0; i < MAX_PLAYERS; i++ )
	{
		m_bPlayerReady.Set( i, false );
	}

	m_bInOvertime = false;
	m_bInSetup = false;
	m_bSwitchedTeamsThisRound = false;
	m_flStopWatchTotalTime = -1.0f;
	m_bMultipleTrains = false;

#ifdef GAME_DLL
	m_pCurStateInfo = NULL;
	State_Transition( GR_STATE_PREGAME );

	m_bResetTeamScores = true;
	m_bResetPlayerScores = true;
	m_bResetRoundsPlayed = true;
	InitTeams();
	ResetMapTime();
	ResetScores();
	SetForceMapReset( true );
	SetRoundToPlayNext( NULL_STRING );
	m_bInWaitingForPlayers  = false;
	m_bAwaitingReadyRestart = false;
	m_flRestartRoundTime = -1;
	m_flMapResetTime = 0;
	m_bPrevRoundWasWaitingForPlayers = false;
	m_iWinningTeam = TEAM_UNASSIGNED;

	m_iszPreviousRounds.RemoveAll();
	SetFirstRoundPlayed( NULL_STRING );

	m_bAllowStalemateAtTimelimit = false;
	m_bChangelevelAfterStalemate = false;
	m_flRoundStartTime = 0;
	m_flNewThrottledAlertTime = 0;
	m_flStartBalancingTeamsAt = 0;
	m_bPrintedUnbalanceWarning = false;
	m_flFoundUnbalancedTeamsTime = -1;
	m_flWaitingForPlayersTimeEnds = 0.0f;

	m_nRoundsPlayed = 0;
	m_bUseAddScoreAnim = false;

	m_bStopWatch = false;
	m_bAwaitingReadyRestart = false;

	if ( IsInTournamentMode() == true )
	{
		m_bAwaitingReadyRestart = true;
	}

	m_flAutoBalanceQueueTimeEnd = -1;
	m_nAutoBalanceQueuePlayerIndex = -1;
	m_nAutoBalanceQueuePlayerScore = -1;

	SetDefLessFunc( m_GameTeams );

#endif
}

#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetTeamRespawnWaveTime( int iTeam, float flValue ) 
{ 
	if ( flValue < 0 )
	{
		flValue = 0;
	}

	// initialized to -1 so we can try to determine if this is the first spawn time we have received for this team
	if ( m_flOriginalTeamRespawnWaveTime[iTeam] < 0 )
	{
		m_flOriginalTeamRespawnWaveTime[iTeam] = flValue;
	}

	m_TeamRespawnWaveTimes.Set( iTeam, flValue );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::AddTeamRespawnWaveTime( int iTeam, float flValue ) 
{ 
	float flAddAmount = flValue;
	float flCurrentSetting = m_TeamRespawnWaveTimes[iTeam];
	float flNewValue;

	if ( flCurrentSetting < 0 )
	{
		flCurrentSetting = mp_respawnwavetime.GetFloat();
	}

	// initialized to -1 so we can try to determine if this is the first spawn time we have received for this team
	if ( m_flOriginalTeamRespawnWaveTime[iTeam] < 0 )
	{
		m_flOriginalTeamRespawnWaveTime[iTeam] = flCurrentSetting;
	}

	flNewValue = flCurrentSetting + flAddAmount;

	if ( flNewValue < 0 )
	{
		flNewValue = 0;
	}

	m_TeamRespawnWaveTimes.Set( iTeam, flNewValue );
}
#endif

//-----------------------------------------------------------------------------
// Purpose: don't let us spawn before our freezepanel time would have ended, even if we skip it
//-----------------------------------------------------------------------------
float CTeamplayRoundBasedRules::GetNextRespawnWave( int iTeam, CBasePlayer *pPlayer ) 
{ 
	if ( State_Get() == GR_STATE_STALEMATE )
		return 0;

	// If we are purely checking when the next respawn wave is for this team
	if ( pPlayer == NULL )
	{
		return m_flNextRespawnWave[iTeam];
	}

	// The soonest this player may spawn
	float flMinSpawnTime = GetMinTimeWhenPlayerMaySpawn( pPlayer );
	if ( ShouldRespawnQuickly( pPlayer ) )
	{
		return flMinSpawnTime;
	}

	// the next scheduled respawn wave time
	float flNextRespawnTime = m_flNextRespawnWave[iTeam];

	// the length of one respawn wave. We'll check in increments of this
	float flRespawnWaveMaxLen = GetRespawnWaveMaxLength( iTeam );

	if ( flRespawnWaveMaxLen <= 0 )
	{
		return flNextRespawnTime;
	}

	// Keep adding the length of one respawn until we find a wave that
	// this player will be eligible to spawn in.
	while ( flNextRespawnTime < flMinSpawnTime )
	{
		flNextRespawnTime += flRespawnWaveMaxLen; 
	}

	return flNextRespawnTime; 
}

//-----------------------------------------------------------------------------
// Purpose: Is the player past the required delays for spawning
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::HasPassedMinRespawnTime( CBasePlayer *pPlayer )
{
	float flMinSpawnTime = GetMinTimeWhenPlayerMaySpawn( pPlayer ); 

	return ( gpGlobals->curtime > flMinSpawnTime );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CTeamplayRoundBasedRules::GetMinTimeWhenPlayerMaySpawn( CBasePlayer *pPlayer )
{
	// Min respawn time is the sum of
	//
	// a) the length of one full *unscaled* respawn wave for their team
	//		and
	// b) death anim length + freeze panel length

	float flDeathAnimLength = 2.0 + spec_freeze_traveltime.GetFloat() + spec_freeze_time.GetFloat();

	float fMinDelay = flDeathAnimLength;

	if ( !ShouldRespawnQuickly( pPlayer ) )
	{
		fMinDelay += GetRespawnWaveMaxLength( pPlayer->GetTeamNumber(), false );
	}

	return pPlayer->GetDeathTime() + fMinDelay;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CTeamplayRoundBasedRules::GetRespawnTimeScalar( int iTeam )
{
	// For long respawn times, scale the time as the number of players drops
	int iOptimalPlayers = 8;	// 16 players total, 8 per team

	int iNumPlayers = GetGlobalTeam(iTeam)->GetNumPlayers();

	float flScale = RemapValClamped( iNumPlayers, 1, iOptimalPlayers, 0.25, 1.0 );
	return flScale;
}

#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetForceMapReset( bool reset )
{
	m_bForceMapReset = reset;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::Think( void )
{
	if ( g_fGameOver )   // someone else quit the game already
	{
		// check to see if we should change levels now
		if ( m_flIntermissionEndTime && ( m_flIntermissionEndTime < gpGlobals->curtime ) )
		{
			if ( !IsX360() )
			{
				ChangeLevel(); // intermission is over
			}
			else
			{
				IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
				if ( event )
				{
					event->SetBool( "forceupload", true );
					gameeventmanager->FireEvent( event );
				}
				engine->MultiplayerEndGame();
			}

			// Don't run this code again
			m_flIntermissionEndTime = 0.f;
		}	

		return;
	}

	State_Think();

	if ( m_hWaitingForPlayersTimer )
	{
		Assert( m_bInWaitingForPlayers );
	}

	if ( gpGlobals->curtime > m_flNextPeriodicThink )
	{
		// Don't end the game during win or stalemate states
		if ( State_Get() != GR_STATE_TEAM_WIN && State_Get() != GR_STATE_STALEMATE && State_Get() != GR_STATE_GAME_OVER ) 
		{
			if ( CheckWinLimit() )
				return;

			if ( CheckMaxRounds() )
				return;
		}

		CheckRestartRound();
		CheckWaitingForPlayers();

		m_flNextPeriodicThink = gpGlobals->curtime + 1.0;
	}

	// Bypass teamplay think.
	CGameRules::Think();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::TimerMayExpire( void )
{
#ifndef CSTRIKE_DLL
	// team_train_watchers can also prevent timer expiring ( overtime )
	CTeamTrainWatcher *pWatcher = dynamic_cast<CTeamTrainWatcher*>( gEntList.FindEntityByClassname( NULL, "team_train_watcher" ) );
	while ( pWatcher )
	{
		if ( !pWatcher->TimerMayExpire() )
		{
			return false;
		}

		pWatcher = dynamic_cast<CTeamTrainWatcher*>( gEntList.FindEntityByClassname( pWatcher, "team_train_watcher" ) );
	}
#endif

	return BaseClass::TimerMayExpire();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckChatText( CBasePlayer *pPlayer, char *pText )
{
	CheckChatForReadySignal( pPlayer, pText );

	BaseClass::CheckChatText( pPlayer, pText );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckChatForReadySignal( CBasePlayer *pPlayer, const char *chatmsg )
{
	if ( IsInTournamentMode() == false )
	{	
		if( m_bAwaitingReadyRestart && FStrEq( chatmsg, mp_clan_ready_signal.GetString() ) )
		{
			int iTeam = pPlayer->GetTeamNumber();
			if ( iTeam > LAST_SHARED_TEAM && iTeam < GetNumberOfTeams() )
			{
				m_bTeamReady.Set( iTeam, true );

				IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_team_ready" );
				if ( event )
				{
					event->SetInt( "team", iTeam );
					gameeventmanager->FireEvent( event );
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::GoToIntermission( void )
{
	if ( IsInTournamentMode() == true )
		return;

	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 );
		}
	}

	// Print out map stats to a text file
	//WriteStatsFile( "stats.xml" );

	State_Enter( GR_STATE_GAME_OVER );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetInWaitingForPlayers( bool bWaitingForPlayers  )
{
	// never waiting for players when loading a bug report
	if ( IsLoadingBugBaitReport() || gpGlobals->eLoadType == MapLoad_Background )
	{
		m_bInWaitingForPlayers = false;
		return;
	}

	if( m_bInWaitingForPlayers == bWaitingForPlayers  )
		return;

	if ( IsInArenaMode() == true && m_flWaitingForPlayersTimeEnds == -1 && IsInTournamentMode() == false )
	{
		m_bInWaitingForPlayers = false;
		return;
	}

	m_bInWaitingForPlayers = bWaitingForPlayers;

	if( m_bInWaitingForPlayers )
	{
		m_flWaitingForPlayersTimeEnds = gpGlobals->curtime + mp_waitingforplayers_time.GetFloat();
	}
	else
	{
		m_flWaitingForPlayersTimeEnds = -1;

		if ( m_hWaitingForPlayersTimer )
		{
			UTIL_Remove( m_hWaitingForPlayersTimer );
		}

		RestoreActiveTimer();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetOvertime( bool bOvertime ) 
{ 
	if ( m_bInOvertime == bOvertime )
		return;

	if ( bOvertime )
	{
		UTIL_LogPrintf( "World triggered \"Round_Overtime\"\n" );
	}

	m_bInOvertime = bOvertime;

	if ( m_bInOvertime )
	{
		// tell train watchers that we've transitioned to overtime

#ifndef CSTRIKE_DLL
		CTeamTrainWatcher *pWatcher = dynamic_cast<CTeamTrainWatcher*>( gEntList.FindEntityByClassname( NULL, "team_train_watcher" ) );
		while ( pWatcher )
		{
			variant_t emptyVariant;
			pWatcher->AcceptInput( "OnStartOvertime", NULL, NULL, emptyVariant, 0 );

			pWatcher = dynamic_cast<CTeamTrainWatcher*>( gEntList.FindEntityByClassname( pWatcher, "team_train_watcher" ) );
		}
#endif
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetSetup( bool bSetup ) 
{ 
	if ( m_bInSetup == bSetup )
		return;

	m_bInSetup = bSetup;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckWaitingForPlayers( void )
{
	// never waiting for players when loading a bug report, or training
	if ( IsLoadingBugBaitReport() || gpGlobals->eLoadType == MapLoad_Background || !AllowWaitingForPlayers() )
		return;

	if( mp_waitingforplayers_restart.GetBool() )
	{
		if( m_bInWaitingForPlayers )
		{
			m_flWaitingForPlayersTimeEnds = gpGlobals->curtime + mp_waitingforplayers_time.GetFloat();

			if ( m_hWaitingForPlayersTimer )
			{
				variant_t sVariant;
				sVariant.SetInt( m_flWaitingForPlayersTimeEnds - gpGlobals->curtime );
				m_hWaitingForPlayersTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
			}
		}
		else
		{
			SetInWaitingForPlayers( true );
		}

		mp_waitingforplayers_restart.SetValue( 0 );
	}

	if( (mp_waitingforplayers_cancel.GetBool() || IsInItemTestingMode()) && IsInTournamentMode() == false )
	{
		// Cancel the wait period and manually Resume() the timer if 
		// it's not supposed to start paused at the beginning of a round.
		// We must do this before SetInWaitingForPlayers() is called because it will
		// restore the timer in the HUD and set the handle to NULL
#ifndef CSTRIKE_DLL
		if ( m_hPreviousActiveTimer.Get() )
		{
			CTeamRoundTimer *pTimer = dynamic_cast<CTeamRoundTimer*>( m_hPreviousActiveTimer.Get() );
			if ( pTimer && !pTimer->StartPaused() )
			{
				pTimer->ResumeTimer();
			}
		}
#endif
		SetInWaitingForPlayers( false );
		mp_waitingforplayers_cancel.SetValue( 0 );
	}

	if( m_bInWaitingForPlayers )
	{
		if ( IsInTournamentMode() == true )
			return;

		// only exit the waitingforplayers if the time is up, and we are not in a round
		// restart countdown already, and we are not waiting for a ready restart
		if( gpGlobals->curtime > m_flWaitingForPlayersTimeEnds && m_flRestartRoundTime < 0 && !m_bAwaitingReadyRestart )
		{
			m_flRestartRoundTime = gpGlobals->curtime;	// reset asap

			if ( IsInArenaMode() == true )
			{
				if ( gpGlobals->curtime > m_flWaitingForPlayersTimeEnds )
				{
					SetInWaitingForPlayers( false );
					State_Transition( GR_STATE_PREROUND );
				}

				return;
			}

			// if "waiting for players" is ending and we're restarting...
			// keep the current round that we're already running around in as the first round after the restart
			CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
			if ( pMaster && pMaster->PlayingMiniRounds() && pMaster->GetCurrentRound() )
			{
				SetRoundToPlayNext( pMaster->GetRoundToUseAfterRestart() );
			}
		}
		else
		{
			if ( !m_hWaitingForPlayersTimer )
			{
				// Stop any timers, and bring up a new one
				HideActiveTimer();

#ifndef CSTRIKE_DLL
				variant_t sVariant;
				m_hWaitingForPlayersTimer = (CTeamRoundTimer*)CBaseEntity::Create( "team_round_timer", vec3_origin, vec3_angle );
				m_hWaitingForPlayersTimer->SetName( MAKE_STRING("zz_teamplay_waiting_timer") );
				m_hWaitingForPlayersTimer->KeyValue( "show_in_hud", "1" );
				sVariant.SetInt( m_flWaitingForPlayersTimeEnds - gpGlobals->curtime );
				m_hWaitingForPlayersTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
				m_hWaitingForPlayersTimer->AcceptInput( "Resume", NULL, NULL, sVariant, 0 );
				m_hWaitingForPlayersTimer->AcceptInput( "Enable", NULL, NULL, sVariant, 0 );
#endif
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckRestartRound( void )
{
	if( mp_clan_readyrestart.GetBool() && IsInTournamentMode() == false )
	{
		m_bAwaitingReadyRestart = true;

		for ( int i = LAST_SHARED_TEAM+1; i < GetNumberOfTeams(); i++ )
		{
			m_bTeamReady.Set( i, false );
		}

		const char *pszReadyString = mp_clan_ready_signal.GetString();

		UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#clan_ready_rules", pszReadyString );
		UTIL_ClientPrintAll( HUD_PRINTTALK, "#clan_ready_rules", pszReadyString );

		// Don't let them put anything malicious in there
		if( pszReadyString == NULL || Q_strlen(pszReadyString) > 16 )
		{
			pszReadyString = "ready";
		}

		IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_ready_restart" );
		if ( event )
		{
			gameeventmanager->FireEvent( event );
		}

		mp_clan_readyrestart.SetValue( 0 );

		// cancel any restart round in progress
		m_flRestartRoundTime = -1;
	}

	// Restart the game if specified by the server
	int iRestartDelay = mp_restartround.GetInt();
	bool bRestartGameNow = mp_restartgame_immediate.GetBool();
	if ( iRestartDelay == 0 && !bRestartGameNow )
	{
		iRestartDelay = mp_restartgame.GetInt();
	}

	if ( iRestartDelay > 0 || bRestartGameNow )
	{
		int iDelayMax = 60;

#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
		if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
		{
			iDelayMax = 180;
		}
#endif // #if defined(TF_CLIENT_DLL) || defined(TF_DLL)

		if ( iRestartDelay > iDelayMax )
		{
			iRestartDelay = iDelayMax;
		}

		if ( mp_restartgame.GetInt() > 0 || bRestartGameNow )
		{
			SetForceMapReset( true );
		}
		else
		{
			SetForceMapReset( false );
		}

		SetInStopWatch( false );

		if ( bRestartGameNow )
		{
			iRestartDelay = 0;
		}

		m_flRestartRoundTime = gpGlobals->curtime + iRestartDelay;

		IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_restart_seconds" );
		if ( event )
		{
			event->SetInt( "seconds", iRestartDelay );
			gameeventmanager->FireEvent( event );
		}

		if ( IsInTournamentMode() == false )
		{
			// let the players know
			const char *pFormat = NULL;

			if ( mp_restartgame.GetInt() > 0 )
			{
				if ( ShouldSwitchTeams() )
				{
					pFormat = ( iRestartDelay > 1 ) ? "#game_switch_in_secs" : "#game_switch_in_sec";
				}
				else if ( ShouldScrambleTeams() )
				{
					pFormat = ( iRestartDelay > 1 ) ? "#game_scramble_in_secs" : "#game_scramble_in_sec";

#ifdef TF_DLL
					IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_alert" );
					if ( event )
					{
						event->SetInt( "alert_type", HUD_ALERT_SCRAMBLE_TEAMS );
						gameeventmanager->FireEvent( event );
					}

					pFormat = NULL;
#endif
				}
			}
			else if ( mp_restartround.GetInt() > 0 )
			{
				pFormat = ( iRestartDelay > 1 ) ? "#round_restart_in_secs" : "#round_restart_in_sec";
			}

			if ( pFormat )
			{
				char strRestartDelay[64];
				Q_snprintf( strRestartDelay, sizeof( strRestartDelay ), "%d", iRestartDelay );
				UTIL_ClientPrintAll( HUD_PRINTCENTER, pFormat, strRestartDelay );
				UTIL_ClientPrintAll( HUD_PRINTCONSOLE, pFormat, strRestartDelay );
			}
		}

		mp_restartround.SetValue( 0 );
		mp_restartgame.SetValue( 0 );
		mp_restartgame_immediate.SetValue( 0 );

		// cancel any ready restart in progress
		m_bAwaitingReadyRestart = false;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::CheckTimeLimit( void )
{
	if ( IsInPreMatch() == true )
		return false;

	if ( ( mp_timelimit.GetInt() > 0 && CanChangelevelBecauseOfTimeLimit() ) || m_bChangelevelAfterStalemate )
	{
		// If there's less than 5 minutes to go, just switch now. This avoids the problem
		// of sudden death modes starting shortly after a new round starts.
		const int iMinTime = 5;
		bool bSwitchDueToTime = ( mp_timelimit.GetInt() > iMinTime && GetTimeLeft() < (iMinTime * 60) );

		if ( IsInTournamentMode() == true  )
		{
			if ( TournamentModeCanEndWithTimelimit() == false )
			{
				return false;
			}

			bSwitchDueToTime = false;
		}

		if ( IsInArenaMode() == true )
		{
			bSwitchDueToTime = false;
		}

		if( GetTimeLeft() <= 0 || m_bChangelevelAfterStalemate || bSwitchDueToTime )
		{
			IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_game_over" );
			if ( event )
			{
				event->SetString( "reason", "Reached Time Limit" );
				gameeventmanager->FireEvent( event );
			}

			SendTeamScoresEvent();

			GoToIntermission();
			return true;
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::IsGameUnderTimeLimit( void )
{
	return ( mp_timelimit.GetInt() > 0 );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CTeamplayRoundBasedRules::GetTimeLeft( void )
{
	float flTimeLimit = mp_timelimit.GetInt() * 60;
	float flMapChangeTime = m_flMapResetTime + flTimeLimit;

	// If the round timer is longer, let the round complete
	// TFTODO: Do we need to worry about the timelimit running our during a round?

	int iTime = (int)(flMapChangeTime - gpGlobals->curtime);
	if ( iTime < 0 )
	{
		iTime = 0;
	}

	return ( iTime );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::CheckNextLevelCvar( void )
{
	if ( m_bForceMapReset )
	{
		if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) )
		{
			IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_game_over" );
			if ( event )
			{
				event->SetString( "reason", "NextLevel CVAR" );
				gameeventmanager->FireEvent( event );
			}

			GoToIntermission();
			return true;
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::CheckWinLimit( void )
{
	// has one team won the specified number of rounds?
	int iWinLimit = mp_winlimit.GetInt();

	if ( iWinLimit > 0 )
	{
		for ( int i = LAST_SHARED_TEAM+1; i < GetNumberOfTeams(); i++ )
		{
			CTeam *pTeam = GetGlobalTeam(i);
			Assert( pTeam );

			if ( pTeam->GetScore() >= iWinLimit )
			{
				IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_game_over" );
				if ( event )
				{
					event->SetString( "reason", "Reached Win Limit" );
					gameeventmanager->FireEvent( event );
				}

				GoToIntermission();
				return true;
			}
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::CheckMaxRounds()
{
	if ( mp_maxrounds.GetInt() > 0 && IsInPreMatch() == false )
	{
		if ( m_nRoundsPlayed >= mp_maxrounds.GetInt() )
		{
			IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_game_over" );
			if ( event )
			{
				event->SetString( "reason", "Reached Round Limit" );
				gameeventmanager->FireEvent( event );
			}

			GoToIntermission();
			return true;
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Transition( gamerules_roundstate_t newState )
{
	m_prevState = State_Get();

	State_Leave();
	State_Enter( newState );
}	

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter( gamerules_roundstate_t newState )
{
	m_iRoundState = newState;
	m_pCurStateInfo = State_LookupInfo( newState );

	m_flLastRoundStateChangeTime = gpGlobals->curtime;

	if ( mp_showroundtransitions.GetInt() > 0 )
	{
		if ( m_pCurStateInfo )
			Msg( "Gamerules: entering state '%s'\n", m_pCurStateInfo->m_pStateName );
		else
			Msg( "Gamerules: entering state #%d\n", newState );
	}

	// Initialize the new state.
	if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState )
	{
		(this->*m_pCurStateInfo->pfnEnterState)();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave()
{
	if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState )
	{
		(this->*m_pCurStateInfo->pfnLeaveState)();
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think()
{
	if ( m_pCurStateInfo && m_pCurStateInfo->pfnThink )
	{
		(this->*m_pCurStateInfo->pfnThink)();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CGameRulesRoundStateInfo* CTeamplayRoundBasedRules::State_LookupInfo( gamerules_roundstate_t state )
{
	static CGameRulesRoundStateInfo playerStateInfos[] =
	{
		{ GR_STATE_INIT,		"GR_STATE_INIT",		&CTeamplayRoundBasedRules::State_Enter_INIT, NULL, &CTeamplayRoundBasedRules::State_Think_INIT },
		{ GR_STATE_PREGAME,		"GR_STATE_PREGAME",		&CTeamplayRoundBasedRules::State_Enter_PREGAME, NULL, &CTeamplayRoundBasedRules::State_Think_PREGAME },
		{ GR_STATE_STARTGAME,	"GR_STATE_STARTGAME",	&CTeamplayRoundBasedRules::State_Enter_STARTGAME, NULL, &CTeamplayRoundBasedRules::State_Think_STARTGAME },
		{ GR_STATE_PREROUND,	"GR_STATE_PREROUND",	&CTeamplayRoundBasedRules::State_Enter_PREROUND, &CTeamplayRoundBasedRules::State_Leave_PREROUND, &CTeamplayRoundBasedRules::State_Think_PREROUND },
		{ GR_STATE_RND_RUNNING,	"GR_STATE_RND_RUNNING",	&CTeamplayRoundBasedRules::State_Enter_RND_RUNNING, NULL,	&CTeamplayRoundBasedRules::State_Think_RND_RUNNING },
		{ GR_STATE_TEAM_WIN,	"GR_STATE_TEAM_WIN",	&CTeamplayRoundBasedRules::State_Enter_TEAM_WIN, NULL,	&CTeamplayRoundBasedRules::State_Think_TEAM_WIN },
		{ GR_STATE_RESTART,		"GR_STATE_RESTART",		&CTeamplayRoundBasedRules::State_Enter_RESTART,	NULL, &CTeamplayRoundBasedRules::State_Think_RESTART },
		{ GR_STATE_STALEMATE,	"GR_STATE_STALEMATE",	&CTeamplayRoundBasedRules::State_Enter_STALEMATE,	&CTeamplayRoundBasedRules::State_Leave_STALEMATE, &CTeamplayRoundBasedRules::State_Think_STALEMATE },
		{ GR_STATE_GAME_OVER,	"GR_STATE_GAME_OVER",	NULL, NULL, NULL },
		{ GR_STATE_BONUS,		"GR_STATE_BONUS",		&CTeamplayRoundBasedRules::State_Enter_BONUS, &CTeamplayRoundBasedRules::State_Leave_BONUS,	&CTeamplayRoundBasedRules::State_Think_BONUS },
		{ GR_STATE_BETWEEN_RNDS, "GR_STATE_BETWEEN_RNDS", &CTeamplayRoundBasedRules::State_Enter_BETWEEN_RNDS, &CTeamplayRoundBasedRules::State_Leave_BETWEEN_RNDS,	&CTeamplayRoundBasedRules::State_Think_BETWEEN_RNDS },
	};

	for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ )
	{
		if ( playerStateInfos[i].m_iRoundState == state )
			return &playerStateInfos[i];
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_INIT( void )
{
	InitTeams();
	ResetMapTime();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_INIT( void )
{
	State_Transition( GR_STATE_PREGAME );
}

//-----------------------------------------------------------------------------
// Purpose: The server is idle and waiting for enough players to start up again. 
//			When we find an active player go to GR_STATE_STARTGAME.
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_PREGAME( void )
{
	m_flNextPeriodicThink = gpGlobals->curtime + 0.1;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_PREGAME( void )
{
	CheckRespawnWaves();

	// we'll just stay in pregame for the bugbait reports
	if ( IsLoadingBugBaitReport() || gpGlobals->eLoadType == MapLoad_Background )
		return;

	// Commentary stays in this mode too
	if ( IsInCommentaryMode() )
		return;
	
	if( CountActivePlayers() > 0 || (IsInArenaMode() == true && m_flWaitingForPlayersTimeEnds == 0.0f) )
	{
		State_Transition( GR_STATE_STARTGAME );			
	}
}

//-----------------------------------------------------------------------------
// Purpose: Wait a bit and then spawn everyone into the preround
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_STARTGAME( void )
{
	m_flStateTransitionTime = gpGlobals->curtime;

	m_bInitialSpawn = true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_STARTGAME()
{
	if( gpGlobals->curtime > m_flStateTransitionTime )
	{
		if ( !IsInTraining() && !IsInItemTestingMode() )
		{
			ConVarRef tf_bot_offline_practice( "tf_bot_offline_practice" );
			if ( mp_waitingforplayers_time.GetFloat() > 0 && tf_bot_offline_practice.GetInt() == 0 )
			{
				// go into waitingforplayers, reset at end of it
				SetInWaitingForPlayers( true );
			}
		}

		State_Transition( GR_STATE_PREROUND );
	}
}
	
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_PREROUND( void )
{
	BalanceTeams( false );

	m_flStartBalancingTeamsAt = gpGlobals->curtime + 60.0;

	RoundRespawn();

	IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_start" );
	if ( event )
	{
		event->SetBool( "full_reset", m_bForceMapReset );
		gameeventmanager->FireEvent( event );
	}

	if ( IsInArenaMode() == true )
	{
		if ( CountActivePlayers() > 0 )
		{
#ifndef CSTRIKE_DLL
			variant_t sVariant;
			if ( !m_hStalemateTimer )
			{
				m_hStalemateTimer = (CTeamRoundTimer*)CBaseEntity::Create( "team_round_timer", vec3_origin, vec3_angle );
			}
			m_hStalemateTimer->KeyValue( "show_in_hud", "1" );

			sVariant.SetInt( tf_arena_preround_time.GetInt() );

			m_hStalemateTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
			m_hStalemateTimer->AcceptInput( "Resume", NULL, NULL, sVariant, 0 );
			m_hStalemateTimer->AcceptInput( "Enable", NULL, NULL, sVariant, 0 );
#endif
			IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_update_timer" );
			if ( event )
			{
				gameeventmanager->FireEvent( event );
			}
		}

		m_flStateTransitionTime = gpGlobals->curtime + tf_arena_preround_time.GetInt();
	}
#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
	else if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
	{
		State_Transition( GR_STATE_BETWEEN_RNDS );
		TFObjectiveResource()->SetMannVsMachineBetweenWaves( true );
	}
#endif // #if defined(TF_CLIENT_DLL) || defined(TF_DLL)
	else
	{
		m_flStateTransitionTime = gpGlobals->curtime + 5 * mp_enableroundwaittime.GetFloat();
	}

	StopWatchModeThink();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave_PREROUND( void )
{
	PreRound_End();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_PREROUND( void )
{
	if( gpGlobals->curtime > m_flStateTransitionTime )
	{
		if ( IsInArenaMode() == true )
		{
			if ( IsInWaitingForPlayers() == true )
			{
				if ( IsInTournamentMode() == true )
				{
					// check round restart
					CheckReadyRestart();
					State_Transition( GR_STATE_STALEMATE );
				}

				return;
			}

			State_Transition( GR_STATE_STALEMATE );	

			// hide the class composition panel
		}
		else
		{
			State_Transition( GR_STATE_RND_RUNNING );
		}
	}

	CheckRespawnWaves();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_RND_RUNNING( void )
{
	SetupOnRoundRunning();

	IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_active" );
	if ( event )
	{
		gameeventmanager->FireEvent( event );
	}

	if( !IsInWaitingForPlayers() )
	{
		PlayStartRoundVoice();
	}

	m_bChangeLevelOnRoundEnd = false;
	m_bPrevRoundWasWaitingForPlayers = false;

	m_flNextBalanceTeamsTime = gpGlobals->curtime + 1.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckReadyRestart( void )
{
	// check round restart
	if( m_flRestartRoundTime > 0 && m_flRestartRoundTime <= gpGlobals->curtime && !g_pServerBenchmark->IsBenchmarkRunning() )
	{
		m_flRestartRoundTime = -1;

#ifdef TF_DLL
		if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && g_pPopulationManager )
		{
			if ( TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() )
			{
				g_pPopulationManager->StartCurrentWave();
			}
			
			return;
		}
#endif // TF_DLL

		// time to restart!
		State_Transition( GR_STATE_RESTART );
	}

	// check ready restart
	if( m_bAwaitingReadyRestart )
	{
		int nTime = 5;
		bool bTeamReady = false;

#ifdef TF_DLL
		if ( TFGameRules() )
		{
			if ( TFGameRules()->IsMannVsMachineMode() )
			{
				bTeamReady = AreDefendingPlayersReady();
				if ( bTeamReady )
				{
					nTime = 10;
				}
			}
			else
			{
				bTeamReady = m_bTeamReady[TF_TEAM_BLUE] && m_bTeamReady[TF_TEAM_RED];
			}
		}
#endif // TF_DLL

		if ( bTeamReady )
		{
			//State_Transition( GR_STATE_RESTART );
			mp_restartgame.SetValue( nTime );
			m_bAwaitingReadyRestart = false;

			ShouldResetScores( true, true );
			ShouldResetRoundsPlayed( true );
		}
	}
}

#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::AreDefendingPlayersReady()
{
	// Get list of defenders
	CUtlVector<LobbyPlayerInfo_t> vecMvMDefenders;
	GetMvMPotentialDefendersLobbyPlayerInfo( vecMvMDefenders );

	// Scan all the players, and bail as soon as we find one person
	// worth waiting for
	bool bAtLeastOnePersonReady = false;
	for ( int i = 0; i < vecMvMDefenders.Count(); i++ )
	{

		// Are they on the red team?
		const LobbyPlayerInfo_t &p = vecMvMDefenders[i];
		if ( !p.m_bConnected || p.m_iTeam == TEAM_UNASSIGNED || p.m_nEntNum <= 0 || p.m_nEntNum >= MAX_PLAYERS )
		{
			// They're still getting set up.  We'll wait for them,
			// but only if they are in the lobby
			if ( p.m_bInLobby )
				return false;
		}
		else if ( p.m_iTeam == TF_TEAM_PVE_DEFENDERS )
		{

			// If he isn't ready, then we aren't ready
			if ( !m_bPlayerReady[ p.m_nEntNum ] )
				return false;

			// He's totally ready
			bAtLeastOnePersonReady = true;
		}
		else
		{
			// And you may ask yourself, "How did I get here?"
			Assert( p.m_iTeam == TF_TEAM_PVE_DEFENDERS );
		}
	}

	// We didn't find anybody who we should wait for, so
	// if at least one person is ready, then we're ready
	return bAtLeastOnePersonReady;
}

#endif // #if defined(TF_CLIENT_DLL) || defined(TF_DLL)

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_RND_RUNNING( void )
{
	//if we don't find any active players, return to GR_STATE_PREGAME
	if( CountActivePlayers() <= 0 )
	{
#if defined( REPLAY_ENABLED )
		if ( g_pReplay )
		{
			// Write replay and stop recording if appropriate
			g_pReplay->SV_EndRecordingSession();
		}
#endif

		State_Transition( GR_STATE_PREGAME );
		return;
	}

	if ( m_flNextBalanceTeamsTime < gpGlobals->curtime )
	{
		BalanceTeams( true );
		m_flNextBalanceTeamsTime = gpGlobals->curtime + 1.0f;
	}

	CheckRespawnWaves();

	// check round restart
	CheckReadyRestart();


	// See if we're coming up to the server timelimit, in which case force a stalemate immediately.
	if ( State_Get() == GR_STATE_RND_RUNNING && mp_timelimit.GetInt() > 0 && IsInPreMatch() == false && GetTimeLeft() <= 0 )
	{
		if ( m_bAllowStalemateAtTimelimit || ( mp_match_end_at_timelimit.GetBool() && !IsValveMap() ) )
		{
			int iDrawScoreCheck = -1;
			int iWinningTeam = 0;
			bool bTeamsAreDrawn = true;
			for ( int i = FIRST_GAME_TEAM; (i < GetNumberOfTeams()) && bTeamsAreDrawn; i++ )
			{
				int iTeamScore = GetGlobalTeam(i)->GetScore();

				if ( iTeamScore > iDrawScoreCheck )
				{
					iWinningTeam = i;
				}

				if ( iTeamScore != iDrawScoreCheck )
				{
					if ( iDrawScoreCheck == -1 )
					{
						iDrawScoreCheck = iTeamScore;
					}
					else
					{
						bTeamsAreDrawn = false;
					}
				}
			}

			if ( bTeamsAreDrawn )
			{
				if ( CanGoToStalemate() )
				{
					m_bChangelevelAfterStalemate = true;
					SetStalemate( STALEMATE_SERVER_TIMELIMIT, m_bForceMapReset );
				}
				else
				{
					SetOvertime( true );
				}
			}
			else
			{
				SetWinningTeam( iWinningTeam, WINREASON_TIMELIMIT, true, false, true );
			}
		}
	}

	StopWatchModeThink();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_TEAM_WIN( void )
{
	float flTime = GetBonusRoundTime();

	m_flStateTransitionTime = gpGlobals->curtime + flTime;

	// if we're forcing the map to reset it must be the end of a "full" round not a mini-round
	if ( m_bForceMapReset )
	{
		m_nRoundsPlayed++;
	}

	InternalHandleTeamWin( m_iWinningTeam );

	SendWinPanelInfo();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_TEAM_WIN( void )
{
	if( gpGlobals->curtime > m_flStateTransitionTime )
	{
		bool bDone = !(!CheckTimeLimit() && !CheckWinLimit() && !CheckMaxRounds() && !CheckNextLevelCvar());

		// check the win limit, max rounds, time limit and nextlevel cvar before starting the next round
		if ( bDone == false )
		{
			PreviousRoundEnd();

			if ( ShouldGoToBonusRound() )
			{
				State_Transition( GR_STATE_BONUS );
			}
			else
			{
#if defined( REPLAY_ENABLED )
				if ( g_pReplay )
				{
					// Write replay and stop recording if appropriate
					g_pReplay->SV_EndRecordingSession();
				}
#endif

				State_Transition( GR_STATE_PREROUND );
			}
		}
		else if ( IsInTournamentMode() == true )
		{
			for ( int i = 1; i <= MAX_PLAYERS; i++ )
			{
				CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );

				if ( !pPlayer )
					continue;

				pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD );
			}

			RestartTournament();

			if ( IsInArenaMode() == true )
			{
#if defined( REPLAY_ENABLED )
				if ( g_pReplay )
				{
					// Write replay and stop recording if appropriate
					g_pReplay->SV_EndRecordingSession();
				}
#endif

				State_Transition( GR_STATE_PREROUND );
			}
			else
			{
#ifdef TF_DLL
				if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
				{
					// one of the convars mp_timelimit, mp_winlimit, mp_maxrounds, or nextlevel has been triggered
					if ( g_pPopulationManager )
					{
						for ( int i = 1; i <= MAX_PLAYERS; i++ )
						{
							CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );

							if ( !pPlayer )
								continue;

							pPlayer->AddFlag( FL_FROZEN );
							pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD );
						}

						g_fGameOver = true;
						g_pPopulationManager->SetMapRestartTime( gpGlobals->curtime + 10.0f );
						State_Enter( GR_STATE_GAME_OVER );
						return;
					}
				}

#endif // TF_DLL
				State_Transition( GR_STATE_RND_RUNNING );
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_STALEMATE( void )
{
	m_flStalemateStartTime = gpGlobals->curtime;
	SetupOnStalemateStart();

	// Stop any timers, and bring up a new one
	HideActiveTimer();

	if ( m_hStalemateTimer )
	{
		UTIL_Remove( m_hStalemateTimer );
		m_hStalemateTimer = NULL;
	}

	int iTimeLimit = mp_stalemate_timelimit.GetInt();

	if ( IsInArenaMode() == true )
	{
		iTimeLimit = tf_arena_round_time.GetInt();
	}

	if ( iTimeLimit > 0 )
	{
#ifndef CSTRIKE_DLL
		variant_t sVariant;
		if ( !m_hStalemateTimer )
		{
			m_hStalemateTimer = (CTeamRoundTimer*)CBaseEntity::Create( "team_round_timer", vec3_origin, vec3_angle );
		}
		m_hStalemateTimer->KeyValue( "show_in_hud", "1" );
		sVariant.SetInt( iTimeLimit );
	
		m_hStalemateTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
		m_hStalemateTimer->AcceptInput( "Resume", NULL, NULL, sVariant, 0 );
		m_hStalemateTimer->AcceptInput( "Enable", NULL, NULL, sVariant, 0 );
#endif
		IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_update_timer" );
		if ( event )
		{
			gameeventmanager->FireEvent( event );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave_STALEMATE( void )
{
	SetupOnStalemateEnd();

	if ( m_hStalemateTimer )
	{
		UTIL_Remove( m_hStalemateTimer );
	}

	if ( IsInArenaMode() == false )
	{
		RestoreActiveTimer();

		IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_update_timer" );
		if ( event )
		{
			gameeventmanager->FireEvent( event );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_BONUS( void )
{
	SetupOnBonusStart();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave_BONUS( void )
{
	SetupOnBonusEnd();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_BONUS( void )
{
	BonusStateThink();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_BETWEEN_RNDS( void )
{
	BetweenRounds_Start();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave_BETWEEN_RNDS( void )
{
	BetweenRounds_End();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_BETWEEN_RNDS( void )
{
	BetweenRounds_Think();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::HideActiveTimer( void )
{
	// We can't handle this, because we won't be able to restore multiple timers
	Assert( m_hPreviousActiveTimer.Get() == NULL );

	m_hPreviousActiveTimer = NULL;

#ifndef CSTRIKE_DLL
	CBaseEntity *pEntity = NULL;
	variant_t sVariant;
	sVariant.SetInt( false );

	while ((pEntity = gEntList.FindEntityByClassname( pEntity, "team_round_timer" )) != NULL)
	{
		CTeamRoundTimer *pTimer = assert_cast<CTeamRoundTimer*>(pEntity);
		if ( pTimer && pTimer->ShowInHud() )
		{
			Assert( !m_hPreviousActiveTimer );
			m_hPreviousActiveTimer = pTimer;
			pEntity->AcceptInput( "ShowInHUD", NULL, NULL, sVariant, 0 );
		}
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::RestoreActiveTimer( void )
{
	if ( m_hPreviousActiveTimer )
	{
		variant_t sVariant;
		sVariant.SetInt( true );
		m_hPreviousActiveTimer->AcceptInput( "ShowInHUD", NULL, NULL, sVariant, 0 );
		m_hPreviousActiveTimer = NULL;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_STALEMATE( void )
{
	//if we don't find any active players, return to GR_STATE_PREGAME
	if( CountActivePlayers() <= 0 && IsInArenaMode() == false )
	{
#if defined( REPLAY_ENABLED )
		if ( g_pReplay )
		{
			// Write replay and stop recording if appropriate
			g_pReplay->SV_EndRecordingSession();
		}
#endif

		State_Transition( GR_STATE_PREGAME );
		return;
	}

	if ( IsInTournamentMode() == true && IsInWaitingForPlayers() == true )
	{
		CheckReadyRestart();
		CheckRespawnWaves();
		return;
	}

	int iDeadTeam = TEAM_UNASSIGNED;
	int iAliveTeam = TEAM_UNASSIGNED;

	// If a team is fully killed, the other team has won
	for ( int i = LAST_SHARED_TEAM+1; i < GetNumberOfTeams(); i++ )
	{
		CTeam *pTeam = GetGlobalTeam(i);
		Assert( pTeam );

		int iPlayers = pTeam->GetNumPlayers();
		if ( iPlayers )
		{
			bool bFoundLiveOne = false;
			for ( int player = 0; player < iPlayers; player++ )
			{
				if ( pTeam->GetPlayer(player) && pTeam->GetPlayer(player)->IsAlive() )
				{
					bFoundLiveOne = true;
					break;
				}
			}

			if ( bFoundLiveOne )
			{
				iAliveTeam = i;
			}
			else
			{
				iDeadTeam = i;
			}
		}
		else
		{
			iDeadTeam = i;
		}
	}

	if ( iDeadTeam && iAliveTeam )
	{
		// The live team has won. 
		bool bMasterHandled = false;
		if ( !m_bForceMapReset )
		{
			// We're not resetting the map, so give the winners control
			// of all the points that were in play this round.
			// Find the control point master.
			CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
			if ( pMaster )
			{
				variant_t sVariant;
				sVariant.SetInt( iAliveTeam );
				pMaster->AcceptInput( "SetWinnerAndForceCaps", NULL, NULL, sVariant, 0 );
				bMasterHandled = true;
			}
		}

		if ( !bMasterHandled )
		{
			SetWinningTeam( iAliveTeam, WINREASON_OPPONENTS_DEAD, m_bForceMapReset );
		}
	}
	else if ( ( iDeadTeam && iAliveTeam == TEAM_UNASSIGNED ) || 
			  ( m_hStalemateTimer && TimerMayExpire() && m_hStalemateTimer->GetTimeRemaining() <= 0 ) )
	{
		bool bFullReset = true;

		CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;

		if ( pMaster && pMaster->PlayingMiniRounds() )
		{
			// we don't need to do a full map reset for maps with mini-rounds
			bFullReset = false;
		}

		// Both teams are dead. Pure stalemate.
		SetWinningTeam( TEAM_UNASSIGNED, WINREASON_STALEMATE, bFullReset, false );
	}
}

//-----------------------------------------------------------------------------
// Purpose: manual restart
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_RESTART( void )
{
	// send scores
	SendTeamScoresEvent();

	// send restart event
	IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_restart_round" );
	if ( event )
	{
		gameeventmanager->FireEvent( event );
	}

	m_bPrevRoundWasWaitingForPlayers = m_bInWaitingForPlayers;
	SetInWaitingForPlayers( false );

	ResetScores();

	// reset the round time
	ResetMapTime();

	State_Transition( GR_STATE_PREROUND );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_RESTART( void )
{
	// should never get here, State_Enter_RESTART sets us into a different state
	Assert( 0 ); 
}

//-----------------------------------------------------------------------------
// Purpose: Sorts teams by score
//-----------------------------------------------------------------------------
int TeamScoreSort( CTeam* const *pTeam1, CTeam* const *pTeam2 )
{
	if ( !*pTeam1 )
		return -1;

	if ( !*pTeam2 )
		return -1;

	if ( (*pTeam1)->GetScore() > (*pTeam2)->GetScore() )
	{
		return 1;
	}

	return -1;
}

//-----------------------------------------------------------------------------
// Purpose: Input for other entities to declare a round winner.
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetWinningTeam( int team, int iWinReason, bool bForceMapReset /* = true */, bool bSwitchTeams /* = false*/, bool bDontAddScore /* = false*/ )
{
	// Commentary doesn't let anyone win
	if ( IsInCommentaryMode() )
		return;

	if ( ( team != TEAM_UNASSIGNED ) && ( team <= LAST_SHARED_TEAM || team >= GetNumberOfTeams() ) )
	{
		Assert( !"SetWinningTeam() called with invalid team." );
		return;
	}

	// are we already in this state?
	if ( State_Get() == GR_STATE_TEAM_WIN )
		return;

	SetForceMapReset( bForceMapReset );
	SetSwitchTeams( bSwitchTeams );

	m_iWinningTeam = team;
	m_iWinReason = iWinReason;

	PlayWinSong( team );

	// only reward the team if they have won the map and we're going to do a full reset or the time has run out and we're changing maps
	bool bRewardTeam = bForceMapReset || ( IsGameUnderTimeLimit() && ( GetTimeLeft() <= 0 ) );

	if ( bDontAddScore == true )
	{
		bRewardTeam = false;
	}

	m_bUseAddScoreAnim = false;
	if ( bRewardTeam && ( team != TEAM_UNASSIGNED ) && ShouldScorePerRound() )
	{
		GetGlobalTeam( team )->AddScore( TEAMPLAY_ROUND_WIN_SCORE );
		m_bUseAddScoreAnim = true;
	}

	// this was a sudden death win if we were in stalemate then a team won it
	bool bWasSuddenDeath = ( InStalemate() && m_iWinningTeam >= FIRST_GAME_TEAM );

	State_Transition( GR_STATE_TEAM_WIN );

	IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_win" );
	if ( event )
	{
		event->SetInt( "team", team );
		event->SetInt( "winreason", iWinReason );
		event->SetBool( "full_round", bForceMapReset );
		event->SetFloat( "round_time", gpGlobals->curtime - m_flRoundStartTime );
		event->SetBool( "was_sudden_death", bWasSuddenDeath );
		// let derived classes add more fields to the event
		FillOutTeamplayRoundWinEvent( event );
		gameeventmanager->FireEvent( event );
	}

	// send team scores
	SendTeamScoresEvent();

	if ( team == TEAM_UNASSIGNED )
	{
		for ( int i = 1; i <= gpGlobals->maxClients; i++ )
		{
			CBaseMultiplayerPlayer *pPlayer = ToBaseMultiplayerPlayer( UTIL_PlayerByIndex( i ) );
			if ( !pPlayer )
				continue;

			pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_STALEMATE );
		}
	}

	// Auto scramble teams?
	if ( bForceMapReset && mp_scrambleteams_auto.GetBool() )
	{
		if ( IsInArenaMode() || IsInTournamentMode() || ShouldSkipAutoScramble() )
			return;

#ifndef DEBUG
		// Don't bother on a listen server - usually not desirable
		if ( !engine->IsDedicatedServer() )
			return;
#endif // DEBUG

		// Skip if we have a nextlevel set
		if ( !FStrEq( nextlevel.GetString(), "" ) )
			return;

		// Track the team scores
		if ( m_iWinningTeam != TEAM_UNASSIGNED )
		{
			// m_GameTeams differs from g_Teams by storing only "Real" teams
			if ( m_GameTeams.Count() == 0 )
			{
				int iTeamIndex = FIRST_GAME_TEAM;
				CTeam *pTeam;
				for ( pTeam = GetGlobalTeam(iTeamIndex); pTeam != NULL; pTeam = GetGlobalTeam(++iTeamIndex) )
				{
					m_GameTeams.Insert( iTeamIndex, 0 );
				}
			}

			// Safety net hack - we assume there are only two "Real" teams
			// driller:  need to make this work in all cases
			if ( m_GameTeams.Count() != 2 )
				return;
		}

		// Look for impending level change
		if ( ( ( mp_timelimit.GetInt() > 0 && CanChangelevelBecauseOfTimeLimit() ) || m_bChangelevelAfterStalemate ) && GetTimeLeft() <= 300 )
			return;

		if ( mp_winlimit.GetInt() || mp_maxrounds.GetInt() )
		{
			int nRoundsPlayed = GetRoundsPlayed();
			if ( ( mp_maxrounds.GetInt() - nRoundsPlayed ) == 1 )
			{
				return;
			}

			int nWinLimit = mp_winlimit.GetInt();
			for ( int iIndex = m_GameTeams.FirstInorder(); iIndex != m_GameTeams.InvalidIndex(); iIndex = m_GameTeams.NextInorder( iIndex ) )
			{
				int nTeamScore = GetGlobalTeam( m_GameTeams.Key( iIndex ) )->GetScore();
				if ( nWinLimit - nTeamScore == 1 )
				{
					return;
				}
			}
		}

		// Increment win counters
		int iWinningTeamIndex = m_GameTeams.Find( m_iWinningTeam );
		if ( iWinningTeamIndex != m_GameTeams.InvalidIndex() )
		{
			m_GameTeams[iWinningTeamIndex]++;
		}
		else
		{
			Assert( iWinningTeamIndex == m_GameTeams.InvalidIndex() );
			return;
		}

		// Did we hit our win delta?
		int nWinDelta = abs( m_GameTeams[1] - m_GameTeams[0] );
		if ( nWinDelta >= mp_scrambleteams_auto_windifference.GetInt() )
		{
			// Let the server know we're going to scramble on round restart
#ifdef TF_DLL
			IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_alert" );
			if ( event )
			{
				event->SetInt( "alert_type", HUD_ALERT_SCRAMBLE_TEAMS );
				gameeventmanager->FireEvent( event );
			}
#else
			const char *pszMessage = "#game_scramble_onrestart";
			if ( pszMessage )
			{
				UTIL_ClientPrintAll( HUD_PRINTCENTER, pszMessage );
				UTIL_ClientPrintAll( HUD_PRINTCONSOLE, pszMessage );
			}
#endif
			UTIL_LogPrintf( "World triggered \"ScrambleTeams_Auto\"\n" );

			SetScrambleTeams( true );
			ShouldResetScores( true, false );
			ShouldResetRoundsPlayed( false );
		}

		// If we switch teams after this win, swap scores
		if ( ShouldSwitchTeams() )
		{
			int nTempScore = m_GameTeams[0];
			m_GameTeams[0] = m_GameTeams[1];
			m_GameTeams[1] = nTempScore;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Input for other entities to declare a stalemate
//			Most often a team_control_point_master saying that the
//			round timer expired 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetStalemate( int iReason, bool bForceMapReset /* = true */, bool bSwitchTeams /* = false */ )
{
	if ( IsInTournamentMode() == true && IsInPreMatch() == true )
		return;

	if ( !mp_stalemate_enable.GetBool() )
	{
		SetWinningTeam( TEAM_UNASSIGNED, WINREASON_STALEMATE, bForceMapReset, bSwitchTeams );
		return;
	}

	if ( InStalemate() )
		return;

	SetForceMapReset( bForceMapReset );

	m_iWinningTeam = TEAM_UNASSIGNED;

	PlaySuddenDeathSong();

	State_Transition( GR_STATE_STALEMATE );

	IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_stalemate" );
	if ( event )
	{
		event->SetInt( "reason", iReason );
		gameeventmanager->FireEvent( event );
	}
}

#ifdef GAME_DLL
void CC_CH_ForceRespawn( void )
{
	CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
	if ( pRules )
	{
		pRules->RespawnPlayers( true );
	}
}
static ConCommand mp_forcerespawnplayers("mp_forcerespawnplayers", CC_CH_ForceRespawn, "Force all players to respawn.", FCVAR_CHEAT );

static ConVar mp_tournament_allow_non_admin_restart( "mp_tournament_allow_non_admin_restart", "1", FCVAR_NONE, "Allow mp_tournament_restart command to be issued by players other than admin.");
void CC_CH_TournamentRestart( void )
{
	if ( mp_tournament_allow_non_admin_restart.GetBool() == false )
	{
		if ( !UTIL_IsCommandIssuedByServerAdmin() )
			return;
	}

#ifdef TF_DLL
	if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
		return;
#endif // TF_DLL

	CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
	if ( pRules )
	{
		pRules->RestartTournament();
	}
}
static ConCommand mp_tournament_restart("mp_tournament_restart", CC_CH_TournamentRestart, "Restart Tournament Mode on the current level."  );

void CTeamplayRoundBasedRules::RestartTournament( void )
{
	if ( IsInTournamentMode() == false )
		return;

	SetInWaitingForPlayers( true );
	m_bAwaitingReadyRestart = true;
	m_flStopWatchTotalTime = -1.0f;
	m_bStopWatch = false;

	for ( int i = 0; i < MAX_TEAMS; i++ )
	{
		m_bTeamReady.Set( i, false );
	}

	for ( int i = 0; i < MAX_PLAYERS; i++ )
	{
		m_bPlayerReady.Set( i, false );
	}
}

#endif


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : bForceRespawn - respawn player even if dead or dying
//			bTeam - if true, only respawn the passed team
//			iTeam  - team to respawn
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::RespawnPlayers( bool bForceRespawn, bool bTeam /* = false */, int iTeam/* = TEAM_UNASSIGNED */ )
{
	if ( bTeam )
	{
		Assert( iTeam > LAST_SHARED_TEAM && iTeam < GetNumberOfTeams() );
	}	

	int iPlayersSpawned = 0;

	CBasePlayer *pPlayer;
	for ( int i = 1; i <= gpGlobals->maxClients; i++ )
	{
		pPlayer = ToBasePlayer( UTIL_PlayerByIndex( i ) );

		if ( !pPlayer )
			continue;

		// Check for team specific spawn
		if ( bTeam && pPlayer->GetTeamNumber() != iTeam )
			continue;

		// players that haven't chosen a team/class can never spawn
		if ( !pPlayer->IsReadyToPlay() )
		{
			// Let the player spawn immediately when they do pick a class
			if ( pPlayer->ShouldGainInstantSpawn() )
			{
				pPlayer->AllowInstantSpawn();
			}

			continue;
		}

		// If we aren't force respawning, don't respawn players that:
		// - are alive
		// - are still in the death anim stage of dying
		if ( !bForceRespawn )
		{
			if ( pPlayer->IsAlive() )
				continue; 

			if ( m_iRoundState != GR_STATE_PREROUND )
			{
					// If the player hasn't been dead the minimum respawn time, he
				// waits until the next wave.
				if ( bTeam && !HasPassedMinRespawnTime( pPlayer ) )
					continue;
				
				if ( !pPlayer->IsReadyToSpawn() )
				{
					// Let the player spawn immediately when they do pick a class
					if ( pPlayer->ShouldGainInstantSpawn() )
					{
						pPlayer->AllowInstantSpawn();
					}

					continue;
				}

			
			}
		}

		// Respawn this player
		pPlayer->ForceRespawn();
		iPlayersSpawned++;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::InitTeams( void )
{
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CTeamplayRoundBasedRules::CountActivePlayers( void )
{
	int i;
	int count = 0;
	CBasePlayer *pPlayer;

	for (i = 1; i <= gpGlobals->maxClients; i++ )
	{
		pPlayer = ToBasePlayer( UTIL_PlayerByIndex( i ) );

		if ( pPlayer )
		{
			if( pPlayer->IsReadyToPlay() )
			{
				count++;
			}
		}
	}

	return count;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::HandleTimeLimitChange( void )
{
	// check that we have an active timer in the HUD and use mp_timelimit if we don't
	if ( !MapHasActiveTimer() && ( mp_timelimit.GetInt() > 0 && GetTimeLeft() > 0  ) )
	{
		CreateTimeLimitTimer();
	}
	else
	{
		if ( m_hTimeLimitTimer )
		{
			UTIL_Remove( m_hTimeLimitTimer );
			m_hTimeLimitTimer = NULL;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::MapHasActiveTimer( void )
{
#ifndef CSTRIKE_DLL
	CBaseEntity *pEntity = NULL;
	while ( ( pEntity = gEntList.FindEntityByClassname( pEntity, "team_round_timer" ) ) != NULL )
	{
		CTeamRoundTimer *pTimer = assert_cast<CTeamRoundTimer*>( pEntity );
		if ( pTimer && pTimer->ShowInHud() && ( Q_stricmp( STRING( pTimer->GetEntityName() ), "zz_teamplay_timelimit_timer" ) != 0 ) )
		{
			return true;
		}
	}
#endif
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CreateTimeLimitTimer( void )
{
	if ( IsInArenaMode () == true || IsInKothMode() == true )
		return;

#ifndef CSTRIKE_DLL
	if ( !m_hTimeLimitTimer )
	{
		m_hTimeLimitTimer = (CTeamRoundTimer*)CBaseEntity::Create( "team_round_timer", vec3_origin, vec3_angle );
		m_hTimeLimitTimer->SetName( MAKE_STRING( "zz_teamplay_timelimit_timer" ) );
	}

	variant_t sVariant;
	m_hTimeLimitTimer->KeyValue( "show_in_hud", "1" );
	sVariant.SetInt( GetTimeLeft() );
	m_hTimeLimitTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
	m_hTimeLimitTimer->AcceptInput( "Resume", NULL, NULL, sVariant, 0 );
	m_hTimeLimitTimer->AcceptInput( "Enable", NULL, NULL, sVariant, 0 );
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::RoundRespawn( void )
{
	m_flRoundStartTime = gpGlobals->curtime;

	if ( m_bForceMapReset || m_bPrevRoundWasWaitingForPlayers )
	{
		CleanUpMap();

		// clear out the previously played rounds
		m_iszPreviousRounds.RemoveAll();

		if ( mp_timelimit.GetInt() > 0 && GetTimeLeft() > 0  )
		{
			// check that we have an active timer in the HUD and use mp_timelimit if we don't
			if ( !MapHasActiveTimer() )
			{
				CreateTimeLimitTimer();
			}
		}

		m_iLastCapPointChanged = 0;
	}

	// reset our spawn times to the original values
	for ( int i = 0; i < MAX_TEAMS; i++ )
	{
		if ( m_flOriginalTeamRespawnWaveTime[i] >= 0 )
		{
			m_TeamRespawnWaveTimes.Set( i, m_flOriginalTeamRespawnWaveTime[i] );
		}
	}

	if ( !IsInWaitingForPlayers() )
	{
		if ( m_bForceMapReset )
		{
			UTIL_LogPrintf( "World triggered \"Round_Start\"\n" );
		}
	}
	
	// Setup before respawning players, so we can mess with spawnpoints
	SetupOnRoundStart();

	// Do we need to switch the teams?
	m_bSwitchedTeamsThisRound = false;
	if ( ShouldSwitchTeams() )
	{
		m_bSwitchedTeamsThisRound = true;
		HandleSwitchTeams();
		SetSwitchTeams( false );
	}

	// Do we need to switch the teams?
	if ( ShouldScrambleTeams() )
	{
		HandleScrambleTeams();
		SetScrambleTeams( false );
	}

#if defined( REPLAY_ENABLED )
	bool bShouldWaitToStartRecording = ShouldWaitToStartRecording();
	if ( g_pReplay && g_pReplay->SV_ShouldBeginRecording( bShouldWaitToStartRecording ) )
	{
		// Tell the replay manager that it should begin recording the new round as soon as possible
		g_pReplay->SV_GetContext()->GetSessionRecorder()->StartRecording();
	}
#endif

	RespawnPlayers( true );

	// reset per-round scores for each player
	for ( int i = 1; i <= MAX_PLAYERS; i++ )
	{
		CBasePlayer *pPlayer = ToBasePlayer( UTIL_PlayerByIndex( i ) );

		if ( pPlayer )
		{
			pPlayer->ResetPerRoundStats();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Recreate all the map entities from the map data (preserving their indices),
//			then remove everything else except the players.
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CleanUpMap()
{
	if( mp_showcleanedupents.GetInt() )
	{
		Msg( "CleanUpMap\n===============\n" );
		Msg( "  Entities: %d (%d edicts)\n", gEntList.NumberOfEntities(), gEntList.NumberOfEdicts() );
	}

	// Get rid of all entities except players.
	CBaseEntity *pCur = gEntList.FirstEnt();
	while ( pCur )
	{
		if ( !RoundCleanupShouldIgnore( pCur ) )
		{
			if( mp_showcleanedupents.GetInt() & 1 )
			{
				Msg( "Removed Entity: %s\n", pCur->GetClassname() );
			}
			UTIL_Remove( pCur );
		}

		pCur = gEntList.NextEnt( pCur );
	}

	// Clear out the event queue
	g_EventQueue.Clear();

	// Really remove the entities so we can have access to their slots below.
	gEntList.CleanupDeleteList();

	engine->AllowImmediateEdictReuse();

	if ( mp_showcleanedupents.GetInt() & 2 )
	{
		Msg( "  Entities Left:\n" );
		pCur = gEntList.FirstEnt();
		while ( pCur )
		{
			Msg( "  %s (%d)\n", pCur->GetClassname(), pCur->entindex() );
			pCur = gEntList.NextEnt( pCur );
		}
	}

	// Now reload the map entities.
	class CTeamplayMapEntityFilter : public IMapEntityFilter
	{
	public:
		CTeamplayMapEntityFilter()
		{
			m_pRules = assert_cast<CTeamplayRoundBasedRules*>( GameRules() );
		}

		virtual bool ShouldCreateEntity( const char *pClassname )
		{
			// Don't recreate the preserved entities.
			if ( m_pRules->ShouldCreateEntity( pClassname ) )
				return true;

			// 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 
				// CTeamplayMapEntityFilter, 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.
		CTeamplayRoundBasedRules *m_pRules;
	};
	CTeamplayMapEntityFilter filter;
	filter.m_iIterator = g_MapEntityRefs.Head();

	// DO NOT CALL SPAWN ON info_node ENTITIES!

	MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::ShouldCreateEntity( const char *pszClassName )
{
	return !FindInList( s_PreserveEnts, pszClassName );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::RoundCleanupShouldIgnore( CBaseEntity *pEnt )
{
	return FindInList( s_PreserveEnts, pEnt->GetClassname() );
}

//-----------------------------------------------------------------------------
// Purpose: Sort function for sorting players by time spent connected ( user ID )
//-----------------------------------------------------------------------------
static int SwitchPlayersSort(  CBaseMultiplayerPlayer * const *p1, CBaseMultiplayerPlayer * const *p2 )
{
	// sort by score
	return ( (*p2)->GetTeamBalanceScore() - (*p1)->GetTeamBalanceScore() );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckRespawnWaves( void )
{
	for ( int team = LAST_SHARED_TEAM+1; team < GetNumberOfTeams(); team++ )
	{
		if ( m_flNextRespawnWave[team] && m_flNextRespawnWave[team] > gpGlobals->curtime )
			continue;

		RespawnTeam( team );

		// Set m_flNextRespawnWave to 0 when we don't have a respawn time to reduce networking
		float flNextRespawnLength = GetRespawnWaveMaxLength( team );
		if ( flNextRespawnLength )
		{
			m_flNextRespawnWave.Set( team, gpGlobals->curtime + flNextRespawnLength );
		}
		else
		{
			m_flNextRespawnWave.Set( team, 0.0f );
		}

	}
}

//-----------------------------------------------------------------------------
// Purpose: Return true if the teams are balanced after this function
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::BalanceTeams( bool bRequireSwitcheesToBeDead )
{
	if ( mp_autoteambalance.GetBool() == false || ( IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true ) )
	{
		return;
	}

	if ( IsInTraining() || IsInItemTestingMode() )
	{
		return;
	}

	// we don't balance for a period of time at the start of the game
	if ( gpGlobals->curtime < m_flStartBalancingTeamsAt )
	{
		return;
	}

	// wrap with this bool, indicates it's a round running switch and not a between rounds insta-switch
	if ( bRequireSwitcheesToBeDead )
	{
#ifndef CSTRIKE_DLL
		// we don't balance if there is less than 60 seconds on the active timer
		CTeamRoundTimer *pActiveTimer = GetActiveRoundTimer();
		if ( pActiveTimer && pActiveTimer->GetTimeRemaining() < 60 )
		{
			return;
		}
#endif
	}

	int iHeaviestTeam = TEAM_UNASSIGNED, iLightestTeam = TEAM_UNASSIGNED;

	// Figure out if we're unbalanced
	if ( !AreTeamsUnbalanced( iHeaviestTeam, iLightestTeam ) )
	{
		m_flFoundUnbalancedTeamsTime = -1;
		m_bPrintedUnbalanceWarning = false;
		return;
	}

	if ( m_flFoundUnbalancedTeamsTime < 0 )
	{
		m_flFoundUnbalancedTeamsTime = gpGlobals->curtime;
	}

	// if teams have been unbalanced for X seconds, play a warning 
	if ( !m_bPrintedUnbalanceWarning && ( ( gpGlobals->curtime - m_flFoundUnbalancedTeamsTime ) > 1.0 ) )
	{
		// print unbalance warning
		UTIL_ClientPrintAll( HUD_PRINTTALK, "#game_auto_team_balance_in", "5" );
		m_bPrintedUnbalanceWarning = true;
	}

	// teams are unblanced, figure out some players that need to be switched

	CTeam *pHeavyTeam = GetGlobalTeam( iHeaviestTeam );
	CTeam *pLightTeam = GetGlobalTeam( iLightestTeam );

	Assert( pHeavyTeam && pLightTeam );

	int iNumSwitchesRequired = ( pHeavyTeam->GetNumPlayers() - pLightTeam->GetNumPlayers() ) / 2;

	// sort the eligible players and switch the n best candidates
	CUtlVector<CBaseMultiplayerPlayer *> vecPlayers;

	CBaseMultiplayerPlayer *pPlayer;

	int iScore;

	int i;
	for ( i = 0; i < pHeavyTeam->GetNumPlayers(); i++ )
	{
		pPlayer = ToBaseMultiplayerPlayer( pHeavyTeam->GetPlayer(i) );

		if ( !pPlayer )
			continue;

		if ( !pPlayer->CanBeAutobalanced() )
			continue;

		// calculate a score for this player. higher is more likely to be switched
		iScore = pPlayer->CalculateTeamBalanceScore();

		pPlayer->SetTeamBalanceScore( iScore );

		vecPlayers.AddToTail( pPlayer );
	}

	// sort the vector
	vecPlayers.Sort( SwitchPlayersSort );

	int iNumEligibleSwitchees = iNumSwitchesRequired + 2;

	for ( int i=0; i<vecPlayers.Count() && iNumSwitchesRequired > 0 && i < iNumEligibleSwitchees; i++ )
	{
		pPlayer = vecPlayers.Element(i);

		Assert( pPlayer );

		if ( !pPlayer )
			continue;

		if ( bRequireSwitcheesToBeDead == false || !pPlayer->IsAlive() )
		{
			// We're trying to avoid picking a player that's recently
			// been auto-balanced by delaying their selection in the hope
			// that a better candidate comes along.
			if ( bRequireSwitcheesToBeDead )
			{
				int nPlayerTeamBalanceScore = pPlayer->CalculateTeamBalanceScore();

				// Do we already have someone in the queue?
				if ( m_nAutoBalanceQueuePlayerIndex > 0 )
				{
					// Is this player's score worse?
					if ( nPlayerTeamBalanceScore < m_nAutoBalanceQueuePlayerScore )
					{
						m_nAutoBalanceQueuePlayerIndex = pPlayer->entindex();
						m_nAutoBalanceQueuePlayerScore = nPlayerTeamBalanceScore;
					}
				}
				// Has this person been switched recently?
				else if ( nPlayerTeamBalanceScore < -10000 ) 
				{
					// Put them in the queue
					m_nAutoBalanceQueuePlayerIndex = pPlayer->entindex();
					m_nAutoBalanceQueuePlayerScore = nPlayerTeamBalanceScore;
					m_flAutoBalanceQueueTimeEnd = gpGlobals->curtime + 3.0f;

					continue;
				}

				// If this is the player in the queue...
				if ( m_nAutoBalanceQueuePlayerIndex == pPlayer->entindex() )
				{
					// Pass until their timer is up
					if ( m_flAutoBalanceQueueTimeEnd > gpGlobals->curtime )
						continue;
				}
			}
				
			pPlayer->ChangeTeam( iLightestTeam );
			pPlayer->SetLastForcedChangeTeamTimeToNow();

			m_nAutoBalanceQueuePlayerScore = -1;
			m_nAutoBalanceQueuePlayerIndex = -1;

			IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_teambalanced_player" );
			if ( event )
			{
				event->SetInt( "player", pPlayer->entindex() );
				event->SetInt( "team", iLightestTeam );
				gameeventmanager->FireEvent( event );
			}

			// tell people that we've switched this player
			UTIL_ClientPrintAll( HUD_PRINTTALK, "#game_player_was_team_balanced", pPlayer->GetPlayerName() );

			iNumSwitchesRequired--;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::ResetScores( void )
{
	if ( m_bResetTeamScores )
	{
		for ( int i = 0; i < GetNumberOfTeams(); i++ )
		{
			GetGlobalTeam( i )->ResetScores();
		}
	}

	if ( m_bResetPlayerScores )
	{
		CBasePlayer *pPlayer;

		for( int i = 1; i <= gpGlobals->maxClients; i++ )
		{
			pPlayer = ToBasePlayer( UTIL_PlayerByIndex( i ) );

			if (pPlayer == NULL)
				continue;

			if (FNullEnt( pPlayer->edict() ))
				continue;

			pPlayer->ResetScores();
		}
	}

	if ( m_bResetRoundsPlayed )
	{
		m_nRoundsPlayed = 0;
	}

	// assume we always want to reset the scores 
	// unless someone tells us not to for the next reset 
	m_bResetTeamScores = true;
	m_bResetPlayerScores = true;
	m_bResetRoundsPlayed = true;
	//m_flStopWatchTime = -1.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::ResetMapTime( void )
{
	m_flMapResetTime = gpGlobals->curtime;

	// send an event with the time remaining until map change
	IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_map_time_remaining" );
	if ( event )
	{
		event->SetInt( "seconds", GetTimeLeft() );
		gameeventmanager->FireEvent( event );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::PlayStartRoundVoice( void )
{
	for ( int i = LAST_SHARED_TEAM+1; i < GetNumberOfTeams(); i++ )
	{
		BroadcastSound( i, UTIL_VarArgs("Game.TeamRoundStart%d", i ) );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::PlayWinSong( int team )
{
	if ( team == TEAM_UNASSIGNED )
	{
		PlayStalemateSong();
	}
	else
	{
#if defined (TF_DLL) || defined (TF_CLIENT_DLL)
		if ( TFGameRules() && TFGameRules()->IsPlayingSpecialDeliveryMode() )
			return;
#endif // TF_DLL

		BroadcastSound( TEAM_UNASSIGNED, UTIL_VarArgs("Game.TeamWin%d", team ) );

		for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ )
		{
			if ( i == team )
			{
				BroadcastSound( i, WinSongName( i ) );
			}
			else
			{
				const char *pchLoseSong = LoseSongName( i );
				if ( pchLoseSong )
				{
					BroadcastSound( i, pchLoseSong );
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::PlaySuddenDeathSong( void )
{
	BroadcastSound( TEAM_UNASSIGNED, "Game.SuddenDeath" );

	for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ )
	{
		BroadcastSound( i, "Game.SuddenDeath" );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::PlayStalemateSong( void )
{
	BroadcastSound( TEAM_UNASSIGNED, GetStalemateSong( TEAM_UNASSIGNED ) );

	for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ )
	{
		BroadcastSound( i, GetStalemateSong( i ) );
	}
}

bool CTeamplayRoundBasedRules::PlayThrottledAlert( int iTeam, const char *sound, float fDelayBeforeNext )
{
	if ( m_flNewThrottledAlertTime <= gpGlobals->curtime )
	{
		BroadcastSound( iTeam, sound );
		m_flNewThrottledAlertTime = gpGlobals->curtime + fDelayBeforeNext;
		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::BroadcastSound( int iTeam, const char *sound, int iAdditionalSoundFlags )
{
	//send it to everyone
	IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_broadcast_audio" );
	if ( event )
	{
		event->SetInt( "team", iTeam );
		event->SetString( "sound", sound );
		event->SetInt( "additional_flags", iAdditionalSoundFlags );
		gameeventmanager->FireEvent( event );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::AddPlayedRound( string_t strName )
{
	if ( strName != NULL_STRING )
	{
		m_iszPreviousRounds.AddToHead( strName );

		// we only need to store the last two rounds that we've played
		if ( m_iszPreviousRounds.Count() > 2 )
		{
			// remove all but two of the entries (should only ever have to remove 1 when we're at 3)
			for ( int i = m_iszPreviousRounds.Count() - 1 ; i > 1 ; i-- )
			{
				m_iszPreviousRounds.Remove( i );
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::IsPreviouslyPlayedRound( string_t strName )
{
	return ( m_iszPreviousRounds.Find( strName ) != m_iszPreviousRounds.InvalidIndex() );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
string_t CTeamplayRoundBasedRules::GetLastPlayedRound( void )
{
	return ( m_iszPreviousRounds.Count() ? m_iszPreviousRounds[0] : NULL_STRING );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTeamRoundTimer *CTeamplayRoundBasedRules::GetActiveRoundTimer( void )
{
#ifdef TF_DLL
	int iTimerEntIndex = ObjectiveResource()->GetTimerInHUD();
	return ( dynamic_cast<CTeamRoundTimer *>( UTIL_EntityByIndex( iTimerEntIndex ) ) );
#else
	return NULL;
#endif
}

#endif // GAME_DLL

//-----------------------------------------------------------------------------
// Purpose: How long are the respawn waves for this team currently?
//-----------------------------------------------------------------------------
float CTeamplayRoundBasedRules::GetRespawnWaveMaxLength( int iTeam, bool bScaleWithNumPlayers /* = true */ )
{
	if ( State_Get() != GR_STATE_RND_RUNNING )
		return 0;

	if ( mp_disable_respawn_times.GetBool() == true )
		return 0.0f;

	//Let's just turn off respawn times while players are messing around waiting for the tournament to start
	if ( IsInTournamentMode() == true && IsInPreMatch() == true )
		return 0.0f;

	float flTime = ( ( m_TeamRespawnWaveTimes[iTeam] >= 0 ) ? m_TeamRespawnWaveTimes[iTeam] : mp_respawnwavetime.GetFloat() );

	// For long respawn times, scale the time as the number of players drops
	if ( bScaleWithNumPlayers && flTime > 5 )
	{
		flTime = MAX( 5, flTime * GetRespawnTimeScalar(iTeam) );
	}

	return flTime;
}

//-----------------------------------------------------------------------------
// Purpose: returns true if we are running tournament mode
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::IsInTournamentMode( void )
{
	return mp_tournament.GetBool();
}

//-----------------------------------------------------------------------------
// Purpose: returns true if we are running highlander mode
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::IsInHighlanderMode( void )
{
#if defined( TF_CLIENT_DLL ) || defined( TF_DLL )
	// can't use highlander mode and the queue system
	if ( IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
		return false;

	return mp_highlander.GetBool();
#else
	return false;
#endif
}

int CTeamplayRoundBasedRules::GetBonusRoundTime( void )
{
	return MAX( 5, mp_bonusroundtime.GetFloat() );
}

//-----------------------------------------------------------------------------
// Purpose: returns true if we should even bother to do balancing stuff
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::ShouldBalanceTeams( void )
{
	if ( IsInTournamentMode() == true )
		return false;

	if ( IsInTraining() == true || IsInItemTestingMode() )
		return false;

	if ( mp_teams_unbalance_limit.GetInt() <= 0 )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: returns true if the passed team change would cause unbalanced teams
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::WouldChangeUnbalanceTeams( int iNewTeam, int iCurrentTeam  )
{
	// players are allowed to change to their own team
	if( iNewTeam == iCurrentTeam )
		return false;

	// if mp_teams_unbalance_limit is 0, don't check
	if ( ShouldBalanceTeams() == false )
		return false;

	// if they are joining a non-playing team, allow
	if ( iNewTeam < FIRST_GAME_TEAM )
		return false;

	CTeam *pNewTeam = GetGlobalTeam( iNewTeam );

	if ( !pNewTeam )
	{
		Assert( 0 );
		return true;
	}

	// add one because we're joining this team
	int iNewTeamPlayers = pNewTeam->GetNumPlayers() + 1;

	// for each game team
	int i = FIRST_GAME_TEAM;

	CTeam *pTeam;

	for ( pTeam = GetGlobalTeam(i); pTeam != NULL; pTeam = GetGlobalTeam(++i) )
	{
		if ( pTeam == pNewTeam )
			continue;

		int iNumPlayers = pTeam->GetNumPlayers();

		if ( i == iCurrentTeam )
		{
			iNumPlayers = MAX( 0, iNumPlayers-1 );
		}

		if ( ( iNewTeamPlayers - iNumPlayers ) > mp_teams_unbalance_limit.GetInt() )
		{
			return true;
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::AreTeamsUnbalanced( int &iHeaviestTeam, int &iLightestTeam )
{
	if ( IsInArenaMode() == false || (IsInArenaMode() && tf_arena_use_queue.GetBool() == false) )
	{
		if ( ShouldBalanceTeams() == false )
		{
			return false;
		}
	}

#ifndef CLIENT_DLL
	if ( IsInCommentaryMode() )
		return false;
#endif

	int iMostPlayers = 0;
	int iLeastPlayers = MAX_PLAYERS + 1;

	int i = FIRST_GAME_TEAM;

	for ( CTeam *pTeam = GetGlobalTeam(i); pTeam != NULL; pTeam = GetGlobalTeam(++i) )
	{
		int iNumPlayers = pTeam->GetNumPlayers();

		if ( iNumPlayers < iLeastPlayers )
		{
			iLeastPlayers = iNumPlayers;
			iLightestTeam = i;
		}

		if ( iNumPlayers > iMostPlayers )
		{
			iMostPlayers = iNumPlayers;
			iHeaviestTeam = i;	
		}
	}

	if ( IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
	{
		if ( iMostPlayers == 0 && iMostPlayers == iLeastPlayers )
			return true;

		if ( iMostPlayers != iLeastPlayers )
			return true;
		
		return false;
	}

	if ( ( iMostPlayers - iLeastPlayers ) > mp_teams_unbalance_limit.GetInt() )
	{
		return true;
	}

	return false;
}

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetRoundState( int iRoundState )
{
	m_iRoundState = iRoundState;
	m_flLastRoundStateChangeTime = gpGlobals->curtime;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::OnPreDataChanged( DataUpdateType_t updateType )
{
	m_bOldInWaitingForPlayers = m_bInWaitingForPlayers;
	m_bOldInOvertime = m_bInOvertime;
	m_bOldInSetup = m_bInSetup;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::OnDataChanged( DataUpdateType_t updateType )
{
	if ( updateType == DATA_UPDATE_CREATED || 
		 m_bOldInWaitingForPlayers != m_bInWaitingForPlayers ||
		 m_bOldInOvertime != m_bInOvertime ||
		 m_bOldInSetup != m_bInSetup )
	{
		IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_update_timer" );
		if ( event )
		{
			gameeventmanager->FireEventClientSide( event );
		}
	}

	if ( updateType == DATA_UPDATE_CREATED )
	{
		if ( State_Get() == GR_STATE_STALEMATE )
		{
			IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_stalemate" );
			if ( event )
			{
				event->SetInt( "reason", STALEMATE_JOIN_MID );
				gameeventmanager->FireEventClientSide( event );
			}
		}
	}

	if ( m_bInOvertime && ( m_bOldInOvertime != m_bInOvertime ) )
	{
		HandleOvertimeBegin();
	}
}
#endif // CLIENT_DLL

#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::ResetTeamsRoundWinTracking( void )
{
	if ( m_GameTeams.Count() != 2 )
		return;

	m_GameTeams[0] = 0;
	m_GameTeams[1] = 0;
}
#endif // GAME_DLL

#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
//-----------------------------------------------------------------------------
// Purpose: Are you now, or are you ever going to be, a member of the defending party?
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::GetMvMPotentialDefendersLobbyPlayerInfo( CUtlVector<LobbyPlayerInfo_t> &vecMvMDefenders, bool bIncludeBots /*= false*/  )
{
	GetAllPlayersLobbyInfo( vecMvMDefenders, bIncludeBots );

	// Now scan through and remove the spectators
	for (int i = vecMvMDefenders.Count() - 1 ; i >= 0 ; --i )
	{
		switch ( vecMvMDefenders[i].m_iTeam )
		{
			case TEAM_UNASSIGNED:
			case TF_TEAM_PVE_DEFENDERS:
				break;

			default:
				AssertMsg1( false, "Bogus team %d", vecMvMDefenders[i].m_iTeam );
			case TF_TEAM_PVE_INVADERS:
			case TEAM_SPECTATOR:
				vecMvMDefenders.FastRemove( i );
				break;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::GetAllPlayersLobbyInfo( CUtlVector<LobbyPlayerInfo_t> &vecPlayers, bool bIncludeBots )
{
	vecPlayers.RemoveAll();

	// Locate the lobby
	CTFLobby *pLobby = GTFGCClientSystem()->GetLobby();
	if ( pLobby )
	{
		for ( int i = 0 ; i < pLobby->GetNumMembers() ; ++i )
		{
			LobbyPlayerInfo_t &mbr = vecPlayers[vecPlayers.AddToTail()];
			mbr.m_nEntNum = 0; // assume he isn't in the game yet
			mbr.m_sPlayerName = pLobby->GetMemberDetails( i )->name().c_str();
			mbr.m_steamID = pLobby->GetMember( i );
			mbr.m_iTeam = TEAM_UNASSIGNED;
			mbr.m_bConnected = false;
			mbr.m_bBot = false;
			mbr.m_bInLobby = true;
			mbr.m_bSquadSurplus = pLobby->GetMemberDetails( i )->squad_surplus();
		}
	}

	// Scan all players
	for ( int i = 1; i <= MAX_PLAYERS; i++ )
	{

		// Locate the info for this player, depending on whether
		// we're on the server or client
		#ifdef CLIENT_DLL
			player_info_t pi;
			if ( !engine->GetPlayerInfo( i, &pi ) )
				continue;
			if ( pi.ishltv || pi.isreplay )
				continue;
			bool bBot = pi.fakeplayer;
		#else
			CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
			if ( !pPlayer )
				continue;
			if ( pPlayer->IsHLTV() || pPlayer->IsReplay() )
				continue;
			bool bBot = pPlayer->IsBot();
		#endif

		// Discard bots?
		if ( bBot && !bIncludeBots )
			continue;

		// See if we already found him in the lobby
		CSteamID steamID = GetSteamIDForPlayerIndex( i );
		#ifdef GAME_DLL
			CSteamID steamID2;
			if ( pPlayer->GetSteamID( &steamID2 ) )
			{
				Assert( steamID == steamID2 );
			}
		#endif
		LobbyPlayerInfo_t *mbr = NULL;
		if ( steamID.IsValid() )
		{
			for ( int j = 0 ; j < vecPlayers.Count() ; ++j )
			{
				if ( vecPlayers[j].m_steamID == steamID )
				{
					Assert( mbr == NULL );
					mbr = &vecPlayers[j];
					#ifndef _DEBUG
						break; // in debug, keep looking so the assert above can fire
					#endif
				}
			}
		}

		// Create a new entry for him if we didn't already find one
		if ( mbr == NULL )
		{
			mbr = &vecPlayers[vecPlayers.AddToTail()];
			mbr->m_bInLobby = false;
			mbr->m_steamID = steamID;
			mbr->m_bSquadSurplus = false;
		}

		// Fill in the rest of the info
		mbr->m_bBot = bBot;
		mbr->m_nEntNum = i;
		#ifdef CLIENT_DLL
			mbr->m_sPlayerName = g_PR->GetPlayerName( i );
			mbr->m_iTeam = g_PR->GetTeam( i );
			mbr->m_bConnected = g_PR->IsConnected( i );
		#else
			mbr->m_sPlayerName = pPlayer->GetPlayerName();
			mbr->m_iTeam = pPlayer->GetTeamNumber();
			mbr->m_bConnected = pPlayer->IsConnected();
		#endif
	}
}

#endif // #if defined(TF_CLIENT_DLL) || defined(TF_DLL)