diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/shared/teamplayroundbased_gamerules.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/shared/teamplayroundbased_gamerules.cpp')
| -rw-r--r-- | mp/src/game/shared/teamplayroundbased_gamerules.cpp | 7136 |
1 files changed, 3568 insertions, 3568 deletions
diff --git a/mp/src/game/shared/teamplayroundbased_gamerules.cpp b/mp/src/game/shared/teamplayroundbased_gamerules.cpp index a843d134..38f1294d 100644 --- a/mp/src/game/shared/teamplayroundbased_gamerules.cpp +++ b/mp/src/game/shared/teamplayroundbased_gamerules.cpp @@ -1,3568 +1,3568 @@ -//========= 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, NULL, &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_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->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, "Game.YourTeamWon" );
- }
- else
- {
- const char *pchLoseSong = LoseSongName();
- 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, "Game.Stalemate" );
-
- for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ )
- {
- BroadcastSound( i, "Game.Stalemate" );
- }
-}
-
-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)
+//========= 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, NULL, &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_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->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, "Game.YourTeamWon" ); + } + else + { + const char *pchLoseSong = LoseSongName(); + 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, "Game.Stalemate" ); + + for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ ) + { + BroadcastSound( i, "Game.Stalemate" ); + } +} + +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) |