source-engine-2018-hl2_src/game/shared/teamplayroundbased_gamerules.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

3746 lines
108 KiB
C++

//========= 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"
#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
// 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 );
}
void RecvProxy_StopWatch( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
*(bool*)(pOut) = ( pData->m_Value.m_Int > 0 );
IGameEvent *event = gameeventmanager->CreateEvent( "stop_watch_changed" );
if ( event )
{
// Client-side once it's actually happened
gameeventmanager->FireEventClientSide( event );
}
}
#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 ) ),
RecvPropInt( RECVINFO( m_nRoundsPlayed ) ),
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 ), 0, RecvProxy_StopWatch ),
RecvPropBool( RECVINFO( m_bMultipleTrains ) ),
RecvPropArray3( RECVINFO_ARRAY(m_bPlayerReady), RecvPropBool( RECVINFO(m_bPlayerReady[0]) ) ),
RecvPropBool( RECVINFO( m_bCheatsEnabledDuringLevel ) ),
RecvPropTime( RECVINFO( m_flCountdownTime ) ),
RecvPropTime( RECVINFO( m_flStateTransitionTime ) ),
#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 ) ),
SendPropInt( SENDINFO( m_nRoundsPlayed ), 4, SPROP_UNSIGNED ),
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) ) ),
SendPropBool( SENDINFO( m_bCheatsEnabledDuringLevel ) ),
SendPropTime( SENDINFO( m_flCountdownTime ) ),
SendPropTime( SENDINFO( m_flStateTransitionTime ) ),
#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
#ifdef GAME_DLL
void WinlimitChanged(IConVar *var, const char *pOldValue, float flOldValue)
{
IGameEvent * event = gameeventmanager->CreateEvent("winlimit_changed");
if (event)
{
gameeventmanager->FireEvent(event);
}
}
#endif // GAME_DLL
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 );
ConVar mp_tournament_post_match_period( "mp_tournament_post_match_period", "90", FCVAR_REPLICATED, "The amount of time (in seconds) before the server resets post-match.", true, 5, true, 300 );
#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
#ifdef TF_DLL
extern ConVar tf_competitive_preround_duration;
extern ConVar tf_competitive_preround_countdown_duration;
#endif // TF_DLL
//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,
"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
#ifdef GAME_DLL
, WinlimitChanged
#endif // GAME_DLL
);
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." );
#if defined( _DEBUG ) || defined( STAGING_ONLY )
ConVar mp_developer( "mp_developer", "0", FCVAR_ARCHIVE | FCVAR_REPLICATED | FCVAR_NOTIFY, "1: basic conveniences (instant respawn and class change, etc). 2: add combat conveniences (infinite ammo, buddha, etc)" );
#endif // _DEBUG || STAGING_ONLY
#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, "Automatically balance the teams based on mp_teams_unbalance_limit. 0 = off, 1 = forcibly switch, 2 = ask volunteers", true, 0, true, 2 );
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." );
#ifdef STAGING_ONLY
ConVar mp_state_debug( "mp_state_debug", "0", FCVAR_NOTIFY, "Set to 1 to print developer messages whenever game state changes." );
#endif // STAGING_ONLY
const char *m_pszRoundStateStrings[] =
{
"GR_STATE_INIT",
"GR_STATE_PREGAME",
"GR_STATE_STARTGAME",
"GR_STATE_PREROUND",
"GR_STATE_RND_RUNNING",
"GR_STATE_TEAM_WIN",
"GR_STATE_RESTART",
"GR_STATE_STALEMATE",
"GR_STATE_GAME_OVER",
"GR_STATE_BONUS",
"GR_STATE_BETWEEN_RNDS",
};
COMPILE_TIME_ASSERT( ARRAYSIZE( m_pszRoundStateStrings ) == GR_NUM_ROUND_STATES );
//-----------------------------------------------------------------------------
// 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
}
m_flStopWatchTotalTime = -1.0f;
m_bAllowBetweenRounds = true;
m_iRoundState.Set( GR_STATE_INIT );
m_bInOvertime.Set( false );
m_bInSetup.Set( false );
m_bSwitchedTeamsThisRound.Set( false );
m_iWinningTeam.Set( TEAM_UNASSIGNED );
m_iWinReason.Set( WINREASON_NONE );
m_bInWaitingForPlayers.Set( false );
m_bAwaitingReadyRestart.Set( false );
m_flRestartRoundTime.Set( -1.0f );
m_flMapResetTime.Set( 0.0f );
m_bStopWatch.Set( false );
m_bMultipleTrains.Set( false );
m_bCheatsEnabledDuringLevel.Set( false );
m_nRoundsPlayed.Set( 0 );
m_flCountdownTime.Set( -1.0f );
for ( int i = 0; i < MAX_PLAYERS; i++ )
{
m_bPlayerReady.Set( i, false );
}
#ifdef GAME_DLL
ListenForGameEvent( "server_changelevel_failed" );
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_bPrevRoundWasWaitingForPlayers = false;
m_iszPreviousRounds.RemoveAll();
SetFirstRoundPlayed( NULL_STRING );
m_bAllowStalemateAtTimelimit = false;
m_bChangelevelAfterStalemate = false;
m_flRoundStartTime = 0.0f;
m_flNewThrottledAlertTime = 0.0f;
m_flStartBalancingTeamsAt = 0.0f;
m_bPrintedUnbalanceWarning = false;
m_flFoundUnbalancedTeamsTime = -1.0f;
m_flWaitingForPlayersTimeEnds = -1.0f;
m_flLastTeamWin = -1.0f;
m_bUseAddScoreAnim = false;
if ( IsInTournamentMode() )
{
m_bAwaitingReadyRestart.Set( true );
}
m_flAutoBalanceQueueTimeEnd = -1.0f;
m_nAutoBalanceQueuePlayerIndex = -1;
m_nAutoBalanceQueuePlayerScore = -1;
SetDefLessFunc( m_GameTeams );
ResetPlayerAndTeamReadyState();
m_nLastEventFiredTime = 0.f;
m_hWaitingForPlayersTimer = NULL;
m_bStopWatchShouldBeTimedWin = false;
#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:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::LevelInitPostEntity( void )
{
BaseClass::LevelInitPostEntity();
#ifdef GAME_DLL
m_bCheatsEnabledDuringLevel = sv_cheats && sv_cheats->GetBool();
#endif // GAME_DLL
}
//-----------------------------------------------------------------------------
// 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;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::FireGameEvent( IGameEvent * event )
{
#ifdef GAME_DLL
const char *eventName = event->GetName();
if ( g_fGameOver && !Q_strcmp( eventName, "server_changelevel_failed" ) )
{
Warning( "In gameover, but failed to load the next map. Trying next map in cycle.\n" );
nextlevel.SetValue( "" );
ChangeLevel();
}
#endif
}
#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;
}
// Watch dog for cheats ever being enabled during a level
if ( !m_bCheatsEnabledDuringLevel && sv_cheats && sv_cheats->GetBool() )
{
m_bCheatsEnabledDuringLevel = true;
}
// 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() && ( m_flWaitingForPlayersTimeEnds < 0 ) && !IsInTournamentMode() )
{
m_bInWaitingForPlayers = false;
return;
}
m_bInWaitingForPlayers = bWaitingForPlayers;
if ( m_bInWaitingForPlayers )
{
m_flWaitingForPlayersTimeEnds = gpGlobals->curtime + mp_waitingforplayers_time.GetFloat();
}
else
{
m_flWaitingForPlayersTimeEnds = -1.0f;
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 );
}
bool bCancelWait = ( mp_waitingforplayers_cancel.GetBool() || IsInItemTestingMode() ) && !IsInTournamentMode();
#if defined( _DEBUG ) || defined( STAGING_ONLY )
if ( mp_developer.GetBool() )
bCancelWait = true;
#endif // _DEBUG || STAGING_ONLY
if ( bCancelWait )
{
// 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.Set( 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.Set( -1.f );
}
// 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;
#ifdef TF_DLL
if ( TFGameRules() && ( TFGameRules()->IsMannVsMachineMode() || TFGameRules()->IsCompetitiveMode() ) )
{
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.Set( 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( bool bAllowEnd /*= true*/ )
{
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 )
{
if ( bAllowEnd )
{
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( bool bAllowEnd /*= true*/ )
{
if ( m_bForceMapReset )
{
if ( nextlevel.GetString() && *nextlevel.GetString() )
{
if ( bAllowEnd )
{
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( bool bAllowEnd /*= true*/, int nAddValueWhenChecking /*= 0*/ )
{
// 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() + nAddValueWhenChecking ) >= iWinLimit )
{
if ( bAllowEnd )
{
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( bool bAllowEnd /*= true*/, int nAddValueWhenChecking /*= 0*/ )
{
if ( mp_maxrounds.GetInt() > 0 && IsInPreMatch() == false )
{
if ( ( m_nRoundsPlayed + nAddValueWhenChecking ) >= mp_maxrounds.GetInt() )
{
if ( bAllowEnd )
{
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 );
#ifdef STAGING_ONLY
if ( mp_state_debug.GetBool() )
{
DevMsg( "\n[STATE] -- %s (Previous: %s)\n", m_pszRoundStateStrings[(int)newState], m_pszRoundStateStrings[(int)m_prevState]);
}
#endif // STAGING_ONLY
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter( gamerules_roundstate_t newState )
{
m_iRoundState = newState;
m_pCurStateInfo = State_LookupInfo( newState );
m_flLastRoundStateChangeTime = gpGlobals->curtime;
if ( mp_showroundtransitions.GetInt() > 0 )
{
if ( m_pCurStateInfo )
Msg( "Gamerules: entering state '%s'\n", m_pCurStateInfo->m_pStateName );
else
Msg( "Gamerules: entering state #%d\n", newState );
}
// Initialize the new state.
if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState )
{
(this->*m_pCurStateInfo->pfnEnterState)();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave()
{
if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState )
{
(this->*m_pCurStateInfo->pfnLeaveState)();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think()
{
if ( m_pCurStateInfo && m_pCurStateInfo->pfnThink )
{
(this->*m_pCurStateInfo->pfnThink)();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CGameRulesRoundStateInfo* CTeamplayRoundBasedRules::State_LookupInfo( gamerules_roundstate_t state )
{
static CGameRulesRoundStateInfo playerStateInfos[] =
{
{ GR_STATE_INIT, "GR_STATE_INIT", &CTeamplayRoundBasedRules::State_Enter_INIT, NULL, &CTeamplayRoundBasedRules::State_Think_INIT },
{ GR_STATE_PREGAME, "GR_STATE_PREGAME", &CTeamplayRoundBasedRules::State_Enter_PREGAME, NULL, &CTeamplayRoundBasedRules::State_Think_PREGAME },
{ GR_STATE_STARTGAME, "GR_STATE_STARTGAME", &CTeamplayRoundBasedRules::State_Enter_STARTGAME, NULL, &CTeamplayRoundBasedRules::State_Think_STARTGAME },
{ GR_STATE_PREROUND, "GR_STATE_PREROUND", &CTeamplayRoundBasedRules::State_Enter_PREROUND, &CTeamplayRoundBasedRules::State_Leave_PREROUND, &CTeamplayRoundBasedRules::State_Think_PREROUND },
{ GR_STATE_RND_RUNNING, "GR_STATE_RND_RUNNING", &CTeamplayRoundBasedRules::State_Enter_RND_RUNNING, NULL, &CTeamplayRoundBasedRules::State_Think_RND_RUNNING },
{ GR_STATE_TEAM_WIN, "GR_STATE_TEAM_WIN", &CTeamplayRoundBasedRules::State_Enter_TEAM_WIN, NULL, &CTeamplayRoundBasedRules::State_Think_TEAM_WIN },
{ GR_STATE_RESTART, "GR_STATE_RESTART", &CTeamplayRoundBasedRules::State_Enter_RESTART, NULL, &CTeamplayRoundBasedRules::State_Think_RESTART },
{ GR_STATE_STALEMATE, "GR_STATE_STALEMATE", &CTeamplayRoundBasedRules::State_Enter_STALEMATE, &CTeamplayRoundBasedRules::State_Leave_STALEMATE, &CTeamplayRoundBasedRules::State_Think_STALEMATE },
{ GR_STATE_GAME_OVER, "GR_STATE_GAME_OVER", NULL, NULL, NULL },
{ GR_STATE_BONUS, "GR_STATE_BONUS", &CTeamplayRoundBasedRules::State_Enter_BONUS, &CTeamplayRoundBasedRules::State_Leave_BONUS, &CTeamplayRoundBasedRules::State_Think_BONUS },
{ GR_STATE_BETWEEN_RNDS, "GR_STATE_BETWEEN_RNDS", &CTeamplayRoundBasedRules::State_Enter_BETWEEN_RNDS, &CTeamplayRoundBasedRules::State_Leave_BETWEEN_RNDS, &CTeamplayRoundBasedRules::State_Think_BETWEEN_RNDS },
};
for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ )
{
if ( playerStateInfos[i].m_iRoundState == state )
return &playerStateInfos[i];
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_INIT( void )
{
InitTeams();
ResetMapTime();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_INIT( void )
{
State_Transition( GR_STATE_PREGAME );
}
//-----------------------------------------------------------------------------
// Purpose: The server is idle and waiting for enough players to start up again.
// When we find an active player go to GR_STATE_STARTGAME.
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_PREGAME( void )
{
m_flNextPeriodicThink = gpGlobals->curtime + 0.1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_PREGAME( void )
{
CheckRespawnWaves();
// we'll just stay in pregame for the bugbait reports
if ( IsLoadingBugBaitReport() || gpGlobals->eLoadType == MapLoad_Background )
return;
// Commentary stays in this mode too
if ( IsInCommentaryMode() )
return;
if ( ( BHavePlayers() ) || ( IsInArenaMode() && ( m_flWaitingForPlayersTimeEnds == 0.f ) ) )
{
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() )
{
if ( BHavePlayers() )
{
#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();
}
#ifdef TF_DLL
// Only allow at the very beginning of the game, or between waves in mvm
else if ( TFGameRules() && TFGameRules()->UsePlayerReadyStatusMode() && m_bAllowBetweenRounds )
{
State_Transition( GR_STATE_BETWEEN_RNDS );
m_bAllowBetweenRounds = false;
if ( TFGameRules()->IsMannVsMachineMode() )
{
TFObjectiveResource()->SetMannVsMachineBetweenWaves( true );
}
StopWatchModeThink();
return;
}
#endif // TF_DLL
else
{
float flTransitionTime = 5 * mp_enableroundwaittime.GetFloat();
#ifdef TF_DLL
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
{
flTransitionTime = tf_competitive_preround_duration.GetFloat();
m_flCountdownTime = -1.f;
if ( ( TFGameRules()->GetRoundsPlayed() > 0 ) && !( GetActiveRoundTimer() && ( GetActiveRoundTimer()->GetSetupTimeLength() > 0 ) ) )
{
// we do a countdown after the first round, so we need some extra pre-round time
flTransitionTime += tf_competitive_preround_countdown_duration.GetFloat();
m_flCountdownTime = gpGlobals->curtime + tf_competitive_preround_countdown_duration.GetFloat();
CTFPlayer *pPlayer;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
if ( !pPlayer )
continue;
if ( pPlayer->GetTeamNumber() < FIRST_GAME_TEAM )
continue;
pPlayer->TeamFortress_SetSpeed();
}
}
}
#endif // TF_DLL
m_flStateTransitionTime = gpGlobals->curtime + flTransitionTime;
}
StopWatchModeThink();
PreRound_Start();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave_PREROUND( void )
{
PreRound_End();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_PREROUND( void )
{
if( gpGlobals->curtime > m_flStateTransitionTime )
{
if ( IsInArenaMode() == true )
{
if ( IsInWaitingForPlayers() == true )
{
if ( IsInTournamentMode() == true )
{
// check round restart
CheckReadyRestart();
State_Transition( GR_STATE_STALEMATE );
}
return;
}
State_Transition( GR_STATE_STALEMATE );
// hide the class composition panel
}
else
{
State_Transition( GR_STATE_RND_RUNNING );
}
}
#ifdef TF_DLL
else
{
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
{
if ( ( TFGameRules()->GetRoundsPlayed() > 0 ) && ( m_flCountdownTime > 0 ) )
{
if ( gpGlobals->curtime > m_flCountdownTime )
{
CTFPlayer *pPlayer;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
if ( !pPlayer )
continue;
if ( pPlayer->GetTeamNumber() < FIRST_GAME_TEAM )
continue;
pPlayer->TeamFortress_SetSpeed();
}
}
}
}
}
#endif
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.Set( -1.f );
#ifdef TF_DLL
if ( TFGameRules() )
{
if ( TFGameRules()->IsMannVsMachineMode() )
{
if ( g_pPopulationManager && TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() )
{
g_pPopulationManager->StartCurrentWave();
m_bAllowBetweenRounds = true;
return;
}
}
else if ( TFGameRules()->IsCompetitiveMode() )
{
TFGameRules()->StartCompetitiveMatch();
return;
}
else if ( mp_tournament.GetBool() )
{
// Temp
TFGameRules()->StartCompetitiveMatch();
return;
}
}
#endif // TF_DLL
// time to restart!
State_Transition( GR_STATE_RESTART );
}
bool bProcessReadyRestart = m_bAwaitingReadyRestart;
#ifdef TF_DLL
bProcessReadyRestart &= TFGameRules() && !TFGameRules()->UsePlayerReadyStatusMode();
#endif // TF_DLL
// check ready restart
if ( bProcessReadyRestart )
{
bool bTeamNotReady = false;
for ( int i = LAST_SHARED_TEAM + 1; i < GetNumberOfTeams(); i++ )
{
if ( !m_bTeamReady[i] )
{
bTeamNotReady = true;
break;
}
}
if ( !bTeamNotReady )
{
mp_restartgame.SetValue( 5 );
m_bAwaitingReadyRestart = false;
ShouldResetScores( true, true );
ShouldResetRoundsPlayed( true );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_RND_RUNNING( void )
{
//if we don't find any active players, return to GR_STATE_PREGAME
if( !BHavePlayers() )
{
#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 ( 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 )
{
// 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++;
}
bool bGameOver = IsGameOver();
m_flStateTransitionTime = gpGlobals->curtime + GetBonusRoundTime( bGameOver );
InternalHandleTeamWin( m_iWinningTeam );
SendWinPanelInfo( bGameOver );
#ifdef TF_DLL
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() && bGameOver )
{
TFGameRules()->StopCompetitiveMatch( CMsgGC_Match_Result_Status_MATCH_SUCCEEDED );
}
#endif // TF_DLL
}
//-----------------------------------------------------------------------------
// 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 )
{
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() )
{
bool bShowScorboard = true;
#ifdef TF_DLL
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
{
bShowScorboard = false;
}
#endif // TF_DLL
for ( int i = 1; i <= MAX_PLAYERS; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
if ( bShowScorboard )
{
pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD );
}
}
RestartTournament();
if ( IsInArenaMode() )
{
#if defined( REPLAY_ENABLED )
if ( g_pReplay )
{
// Write replay and stop recording if appropriate
g_pReplay->SV_EndRecordingSession();
}
#endif
State_Transition( GR_STATE_PREROUND );
}
#ifdef TF_DLL
else if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && g_pPopulationManager )
{
// one of the convars mp_timelimit, mp_winlimit, mp_maxrounds, or nextlevel has been triggered
for ( int i = 1; i <= MAX_PLAYERS; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
pPlayer->AddFlag( FL_FROZEN );
}
g_fGameOver = true;
g_pPopulationManager->SetMapRestartTime( gpGlobals->curtime + 10.0f );
State_Enter( GR_STATE_GAME_OVER );
return;
}
else if ( TFGameRules() && TFGameRules()->UsePlayerReadyStatusMode() )
{
for ( int i = 1; i <= MAX_PLAYERS; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
pPlayer->AddFlag( FL_FROZEN );
}
g_fGameOver = true;
State_Enter( GR_STATE_GAME_OVER );
m_flStateTransitionTime = gpGlobals->curtime + GetPostMatchPeriod();
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
{
TFGameRules()->MatchSummaryStart();
}
return;
}
#endif // TF_DLL
else
{
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( !BHavePlayers() && 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*/, bool bFinal /*= 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;
// 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 );
// this needs to be AFTER we add score above (for TF)
PlayWinSong( team );
m_flLastTeamWin = gpGlobals->curtime;
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_win" );
if ( event )
{
event->SetInt( "team", team );
event->SetInt( "winreason", iWinReason );
event->SetBool( "full_round", bForceMapReset );
event->SetFloat( "round_time", gpGlobals->curtime - m_flRoundStartTime );
event->SetBool( "was_sudden_death", bWasSuddenDeath );
// let derived classes add more fields to the event
FillOutTeamplayRoundWinEvent( event );
gameeventmanager->FireEvent( event );
}
// send team scores
SendTeamScoresEvent();
if ( team == TEAM_UNASSIGNED )
{
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBaseMultiplayerPlayer *pPlayer = ToBaseMultiplayerPlayer( UTIL_PlayerByIndex( i ) );
if ( !pPlayer )
continue;
pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_STALEMATE );
}
}
// Auto scramble teams?
if ( bForceMapReset && mp_scrambleteams_auto.GetBool() )
{
if ( IsInArenaMode() || IsInTournamentMode() || ShouldSkipAutoScramble() )
return;
#ifndef DEBUG
// Don't bother on a listen server - usually not desirable
if ( !engine->IsDedicatedServer() )
return;
#endif // DEBUG
// Skip if we have a nextlevel set
if ( !FStrEq( nextlevel.GetString(), "" ) )
return;
// Track the team scores
if ( m_iWinningTeam != TEAM_UNASSIGNED )
{
// m_GameTeams differs from g_Teams by storing only "Real" teams
if ( m_GameTeams.Count() == 0 )
{
int iTeamIndex = FIRST_GAME_TEAM;
CTeam *pTeam;
for ( pTeam = GetGlobalTeam(iTeamIndex); pTeam != NULL; pTeam = GetGlobalTeam(++iTeamIndex) )
{
m_GameTeams.Insert( iTeamIndex, 0 );
}
}
// Safety net hack - we assume there are only two "Real" teams
// driller: need to make this work in all cases
if ( m_GameTeams.Count() != 2 )
return;
}
// Look for impending level change
if ( ( ( mp_timelimit.GetInt() > 0 && CanChangelevelBecauseOfTimeLimit() ) || m_bChangelevelAfterStalemate ) && GetTimeLeft() <= 300 )
return;
if ( mp_winlimit.GetInt() || mp_maxrounds.GetInt() )
{
int nRoundsPlayed = GetRoundsPlayed();
if ( ( mp_maxrounds.GetInt() - nRoundsPlayed ) == 1 )
{
return;
}
int nWinLimit = mp_winlimit.GetInt();
for ( int iIndex = m_GameTeams.FirstInorder(); iIndex != m_GameTeams.InvalidIndex(); iIndex = m_GameTeams.NextInorder( iIndex ) )
{
int nTeamScore = GetGlobalTeam( m_GameTeams.Key( iIndex ) )->GetScore();
if ( nWinLimit - nTeamScore == 1 )
{
return;
}
}
}
// Increment win counters
int iWinningTeamIndex = m_GameTeams.Find( m_iWinningTeam );
if ( iWinningTeamIndex != m_GameTeams.InvalidIndex() )
{
m_GameTeams[iWinningTeamIndex]++;
}
else
{
Assert( iWinningTeamIndex == m_GameTeams.InvalidIndex() );
return;
}
// Did we hit our win delta?
int nWinDelta = abs( m_GameTeams[1] - m_GameTeams[0] );
if ( nWinDelta >= mp_scrambleteams_auto_windifference.GetInt() )
{
// Let the server know we're going to scramble on round restart
#ifdef TF_DLL
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_alert" );
if ( event )
{
event->SetInt( "alert_type", HUD_ALERT_SCRAMBLE_TEAMS );
gameeventmanager->FireEvent( event );
}
#else
const char *pszMessage = "#game_scramble_onrestart";
if ( pszMessage )
{
UTIL_ClientPrintAll( HUD_PRINTCENTER, pszMessage );
UTIL_ClientPrintAll( HUD_PRINTCONSOLE, pszMessage );
}
#endif
UTIL_LogPrintf( "World triggered \"ScrambleTeams_Auto\"\n" );
SetScrambleTeams( true );
ShouldResetScores( true, false );
ShouldResetRoundsPlayed( false );
}
// If we switch teams after this win, swap scores
if ( ShouldSwitchTeams() )
{
int nTempScore = m_GameTeams[0];
m_GameTeams[0] = m_GameTeams[1];
m_GameTeams[1] = nTempScore;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Input for other entities to declare a stalemate
// Most often a team_control_point_master saying that the
// round timer expired
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetStalemate( int iReason, bool bForceMapReset /* = true */, bool bSwitchTeams /* = false */ )
{
if ( IsInTournamentMode() == true && IsInPreMatch() == true )
return;
if ( !mp_stalemate_enable.GetBool() )
{
SetWinningTeam( TEAM_UNASSIGNED, WINREASON_STALEMATE, bForceMapReset, bSwitchTeams );
return;
}
if ( InStalemate() )
return;
SetForceMapReset( bForceMapReset );
m_iWinningTeam = TEAM_UNASSIGNED;
PlaySuddenDeathSong();
State_Transition( GR_STATE_STALEMATE );
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_stalemate" );
if ( event )
{
event->SetInt( "reason", iReason );
gameeventmanager->FireEvent( event );
}
}
#ifdef GAME_DLL
void CC_CH_ForceRespawn( void )
{
CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
if ( pRules )
{
pRules->RespawnPlayers( true );
}
}
static ConCommand mp_forcerespawnplayers("mp_forcerespawnplayers", CC_CH_ForceRespawn, "Force all players to respawn.", FCVAR_CHEAT );
static ConVar mp_tournament_allow_non_admin_restart( "mp_tournament_allow_non_admin_restart", "1", FCVAR_NONE, "Allow mp_tournament_restart command to be issued by players other than admin.");
void CC_CH_TournamentRestart( void )
{
if ( mp_tournament_allow_non_admin_restart.GetBool() == false )
{
if ( !UTIL_IsCommandIssuedByServerAdmin() )
return;
}
#ifdef TF_DLL
if ( TFGameRules() && ( TFGameRules()->IsMannVsMachineMode() || TFGameRules()->IsCompetitiveMode() ) )
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;
// we might have had a stalemate during the last round
// so reset this bool each time we restart the tournament
m_bChangelevelAfterStalemate = 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() );
}
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();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::InitTeams( void )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::BHavePlayers( void )
{
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = ToBasePlayer( UTIL_PlayerByIndex( i ) );
if ( pPlayer && pPlayer->IsReadyToPlay() )
{
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// 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:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::ResetPlayerAndTeamReadyState( void )
{
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 );
}
#ifdef GAME_DLL
// Note <= MAX_PLAYERS vs < MAX_PLAYERS above
for ( int i = 0; i <= MAX_PLAYERS; i++ )
{
m_bPlayerReadyBefore[i] = false;
}
#endif // GAME_DLL
}
//-----------------------------------------------------------------------------
// 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;
// this is the same check we use in State_Think_RND_RUNNING()
// don't show the timelimit timer if we're not going to end the map when it runs out
bool bAllowStalemate = ( m_bAllowStalemateAtTimelimit || ( mp_match_end_at_timelimit.GetBool() && !IsValveMap() ) );
if ( !bAllowStalemate )
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
// Free any edicts that were marked deleted. This should hopefully clear some out
// so the below function can use the now freed ones.
engine->AllowImmediateEdictReuse();
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 );
}
}
}
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::BalanceTeams( bool bRequireSwitcheesToBeDead )
{
if ( mp_autoteambalance.GetBool() == false || ( IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true ) )
{
return;
}
#if defined( _DEBUG ) || defined( STAGING_ONLY )
if ( mp_developer.GetBool() )
return;
#endif // _DEBUG || STAGING_ONLY
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--;
}
}
}
#endif // GAME_DLL
//-----------------------------------------------------------------------------
// 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();
}
#ifdef TF_DLL
IGameEvent *event = gameeventmanager->CreateEvent( "scorestats_accumulated_reset" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
#endif // TF_DLL
}
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
{
BroadcastSound( TEAM_UNASSIGNED, UTIL_VarArgs("Game.TeamWin%d", team ) );
for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ )
{
if ( i == team )
{
BroadcastSound( i, WinSongName( i ) );
}
else
{
const char *pchLoseSong = LoseSongName( i );
if ( pchLoseSong )
{
BroadcastSound( i, pchLoseSong );
}
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::PlaySuddenDeathSong( void )
{
BroadcastSound( TEAM_UNASSIGNED, "Game.SuddenDeath" );
for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ )
{
BroadcastSound( i, "Game.SuddenDeath" );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::PlayStalemateSong( void )
{
BroadcastSound( TEAM_UNASSIGNED, GetStalemateSong( TEAM_UNASSIGNED ) );
for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ )
{
BroadcastSound( i, GetStalemateSong( i ) );
}
}
bool CTeamplayRoundBasedRules::PlayThrottledAlert( int iTeam, const char *sound, float fDelayBeforeNext )
{
if ( m_flNewThrottledAlertTime <= gpGlobals->curtime )
{
BroadcastSound( iTeam, sound );
m_flNewThrottledAlertTime = gpGlobals->curtime + fDelayBeforeNext;
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::BroadcastSound( int iTeam, const char *sound, int iAdditionalSoundFlags )
{
//send it to everyone
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_broadcast_audio" );
if ( event )
{
event->SetInt( "team", iTeam );
event->SetString( "sound", sound );
event->SetInt( "additional_flags", iAdditionalSoundFlags );
gameeventmanager->FireEvent( event );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::AddPlayedRound( string_t strName )
{
if ( strName != NULL_STRING )
{
m_iszPreviousRounds.AddToHead( strName );
// we only need to store the last two rounds that we've played
if ( m_iszPreviousRounds.Count() > 2 )
{
// remove all but two of the entries (should only ever have to remove 1 when we're at 3)
for ( int i = m_iszPreviousRounds.Count() - 1 ; i > 1 ; i-- )
{
m_iszPreviousRounds.Remove( i );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::IsPreviouslyPlayedRound( string_t strName )
{
return ( m_iszPreviousRounds.Find( strName ) != m_iszPreviousRounds.InvalidIndex() );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
string_t CTeamplayRoundBasedRules::GetLastPlayedRound( void )
{
return ( m_iszPreviousRounds.Count() ? m_iszPreviousRounds[0] : NULL_STRING );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTeamRoundTimer *CTeamplayRoundBasedRules::GetActiveRoundTimer( void )
{
#ifdef TF_DLL
int iTimerEntIndex = ObjectiveResource()->GetTimerInHUD();
return ( dynamic_cast<CTeamRoundTimer *>( UTIL_EntityByIndex( iTimerEntIndex ) ) );
#else
return NULL;
#endif
}
#endif // GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: How long are the respawn waves for this team currently?
//-----------------------------------------------------------------------------
float CTeamplayRoundBasedRules::GetRespawnWaveMaxLength( int iTeam, bool bScaleWithNumPlayers /* = true */ )
{
if ( State_Get() != GR_STATE_RND_RUNNING )
return 0;
if ( mp_disable_respawn_times.GetBool() == true )
return 0.0f;
//Let's just turn off respawn times while players are messing around waiting for the tournament to start
if ( IsInTournamentMode() == true && IsInPreMatch() == true )
return 0.0f;
float flTime = ( ( m_TeamRespawnWaveTimes[iTeam] >= 0 ) ? m_TeamRespawnWaveTimes[iTeam] : mp_respawnwavetime.GetFloat() );
// For long respawn times, scale the time as the number of players drops
if ( bScaleWithNumPlayers && flTime > 5 )
{
flTime = MAX( 5, flTime * GetRespawnTimeScalar(iTeam) );
}
return flTime;
}
//-----------------------------------------------------------------------------
// Purpose: returns true if we are running tournament mode
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::IsInTournamentMode( void )
{
return mp_tournament.GetBool();
}
//-----------------------------------------------------------------------------
// Purpose: returns true if we are running highlander mode
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::IsInHighlanderMode( void )
{
#if defined( TF_CLIENT_DLL ) || defined( TF_DLL )
// can't use highlander mode and the queue system
if ( IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
return false;
return mp_highlander.GetBool();
#else
return false;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTeamplayRoundBasedRules::GetBonusRoundTime( bool bGameOver /* = false*/ )
{
return Max( 5, mp_bonusroundtime.GetInt() );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTeamplayRoundBasedRules::GetPostMatchPeriod( void )
{
return mp_tournament_post_match_period.GetInt();
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::Update( float frametime )
{
BaseClass::Update( frametime );
int nTime = 0;
if ( m_flRestartRoundTime > gpGlobals->curtime )
{
nTime = ceil( m_flRestartRoundTime - gpGlobals->curtime );
}
else if ( m_flCountdownTime > gpGlobals->curtime )
{
nTime = ceil( m_flCountdownTime - gpGlobals->curtime );
}
if ( nTime != m_nLastEventFiredTime )
{
m_nLastEventFiredTime = nTime;
IGameEvent * event = gameeventmanager->CreateEvent( "restart_timer_time" );
if ( event )
{
event->SetInt( "time", nTime );
gameeventmanager->FireEventClientSide( event );
}
}
}
#endif
//-----------------------------------------------------------------------------
// Purpose: returns true if we should even bother to do balancing stuff
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::ShouldBalanceTeams( void )
{
if ( IsInTournamentMode() )
return false;
if ( IsInTraining() || IsInItemTestingMode() )
return false;
#if defined( _DEBUG ) || defined( STAGING_ONLY )
if ( mp_developer.GetBool() )
return false;
#endif // _DEBUG || STAGING_ONLY
if ( mp_teams_unbalance_limit.GetInt() <= 0 )
return false;
#if defined( TF_DLL ) || defined( TF_CLIENT_DLL )
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
return false;
#endif // TF_DLL
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() )
return false;
#if defined( _DEBUG ) || defined( STAGING_ONLY )
if ( mp_developer.GetBool() )
return false;
#endif // _DEBUG || STAGING_ONLY
// if they are joining a non-playing team, allow
if ( iNewTeam < FIRST_GAME_TEAM )
return false;
#if defined( TF_DLL ) || defined( TF_CLIENT_DLL )
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
return false;
#endif // TF_DLL
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() || ( IsInArenaMode() && !tf_arena_use_queue.GetBool() ) )
{
if ( !ShouldBalanceTeams() )
return false;
}
#ifndef CLIENT_DLL
if ( IsInCommentaryMode() )
return false;
#endif
#if defined( TF_DLL ) || defined( TF_CLIENT_DLL )
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
return false;
#endif // TF_DLL
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() && tf_arena_use_queue.GetBool() )
{
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