source-sdk-2013-mapbase/sp/src/game/shared/teamplayroundbased_gamerules.cpp
Joe Ludwig beaae8ac45 Updated the SDK with the latest code from the TF and HL2 branches
* Adds support for Visual Studio 2012 and 2013
* VR Mode:
. Switches from headtrack.dll to sourcevr.dll
. Improved readability of the UI in VR
. Removed the IPD calibration tool. TF2 will now obey the Oculus
configuration file. Use the Oculus calibration tool in your SDK or
install and run "OpenVR" under Tools in Steam to calibrate your IPD.
. Added dropdown to enable VR mode in the Video options. Removed the -vr
command line option.
. Added the ability to switch in and out of VR mode without quitting the
game
. By default VR mode will run full screen. To switch back to a
borderless window set the vr_force_windowed convar.
. Added support for VR mode on Linux
* Many assorted bug fixes and other changes from Team Fortress in
various shared files
2013-12-03 08:54:16 -08:00

3578 lines
103 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"
#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
#include "tf_lobby.h"
#ifdef GAME_DLL
#include "player_vs_environment/tf_population_manager.h"
#include "../server/tf/tf_gc_server.h"
#include "../server/tf/tf_objective_resource.h"
#else
#include "../client/tf/tf_gc_client.h"
#include "../client/tf/c_tf_objective_resource.h"
#endif // GAME_DLL
#endif // #if defined(TF_CLIENT_DLL) || defined(TF_DLL)
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifndef CLIENT_DLL
CUtlVector< CHandle<CTeamControlPointMaster> > g_hControlPointMasters;
extern bool IsInCommentaryMode( void );
#if defined( REPLAY_ENABLED )
extern IReplaySystem *g_pReplay;
#endif // REPLAY_ENABLED
#endif
extern ConVar spec_freeze_time;
extern ConVar spec_freeze_traveltime;
#ifdef CLIENT_DLL
void RecvProxy_TeamplayRoundState( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
CTeamplayRoundBasedRules *pGamerules = ( CTeamplayRoundBasedRules *)pStruct;
int iRoundState = pData->m_Value.m_Int;
pGamerules->SetRoundState( iRoundState );
}
#endif
BEGIN_NETWORK_TABLE_NOBASE( CTeamplayRoundBasedRules, DT_TeamplayRoundBasedRules )
#ifdef CLIENT_DLL
RecvPropInt( RECVINFO( m_iRoundState ), 0, RecvProxy_TeamplayRoundState ),
RecvPropBool( RECVINFO( m_bInWaitingForPlayers ) ),
RecvPropInt( RECVINFO( m_iWinningTeam ) ),
RecvPropInt( RECVINFO( m_bInOvertime ) ),
RecvPropInt( RECVINFO( m_bInSetup ) ),
RecvPropInt( RECVINFO( m_bSwitchedTeamsThisRound ) ),
RecvPropBool( RECVINFO( m_bAwaitingReadyRestart ) ),
RecvPropTime( RECVINFO( m_flRestartRoundTime ) ),
RecvPropTime( RECVINFO( m_flMapResetTime ) ),
RecvPropArray3( RECVINFO_ARRAY(m_flNextRespawnWave), RecvPropTime( RECVINFO(m_flNextRespawnWave[0]) ) ),
RecvPropArray3( RECVINFO_ARRAY(m_TeamRespawnWaveTimes), RecvPropFloat( RECVINFO(m_TeamRespawnWaveTimes[0]) ) ),
RecvPropArray3( RECVINFO_ARRAY(m_bTeamReady), RecvPropBool( RECVINFO(m_bTeamReady[0]) ) ),
RecvPropBool( RECVINFO( m_bStopWatch ) ),
RecvPropBool( RECVINFO( m_bMultipleTrains ) ),
RecvPropArray3( RECVINFO_ARRAY(m_bPlayerReady), RecvPropBool( RECVINFO(m_bPlayerReady[0]) ) ),
#else
SendPropInt( SENDINFO( m_iRoundState ), 5 ),
SendPropBool( SENDINFO( m_bInWaitingForPlayers ) ),
SendPropInt( SENDINFO( m_iWinningTeam ), 3, SPROP_UNSIGNED ),
SendPropBool( SENDINFO( m_bInOvertime ) ),
SendPropBool( SENDINFO( m_bInSetup ) ),
SendPropBool( SENDINFO( m_bSwitchedTeamsThisRound ) ),
SendPropBool( SENDINFO( m_bAwaitingReadyRestart ) ),
SendPropTime( SENDINFO( m_flRestartRoundTime ) ),
SendPropTime( SENDINFO( m_flMapResetTime ) ),
SendPropArray3( SENDINFO_ARRAY3(m_flNextRespawnWave), SendPropTime( SENDINFO_ARRAY(m_flNextRespawnWave) ) ),
SendPropArray3( SENDINFO_ARRAY3(m_TeamRespawnWaveTimes), SendPropFloat( SENDINFO_ARRAY(m_TeamRespawnWaveTimes) ) ),
SendPropArray3( SENDINFO_ARRAY3(m_bTeamReady), SendPropBool( SENDINFO_ARRAY(m_bTeamReady) ) ),
SendPropBool( SENDINFO( m_bStopWatch ) ),
SendPropBool( SENDINFO( m_bMultipleTrains ) ),
SendPropArray3( SENDINFO_ARRAY3(m_bPlayerReady), SendPropBool( SENDINFO_ARRAY(m_bPlayerReady) ) ),
#endif
END_NETWORK_TABLE()
IMPLEMENT_NETWORKCLASS_ALIASED( TeamplayRoundBasedRulesProxy, DT_TeamplayRoundBasedRulesProxy )
#ifdef CLIENT_DLL
void RecvProxy_TeamplayRoundBasedRules( const RecvProp *pProp, void **pOut, void *pData, int objectID )
{
CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
Assert( pRules );
*pOut = pRules;
}
BEGIN_RECV_TABLE( CTeamplayRoundBasedRulesProxy, DT_TeamplayRoundBasedRulesProxy )
RecvPropDataTable( "teamplayroundbased_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_TeamplayRoundBasedRules ), RecvProxy_TeamplayRoundBasedRules )
END_RECV_TABLE()
void CTeamplayRoundBasedRulesProxy::OnPreDataChanged( DataUpdateType_t updateType )
{
BaseClass::OnPreDataChanged( updateType );
// Reroute data changed calls to the non-entity gamerules
CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
Assert( pRules );
pRules->OnPreDataChanged(updateType);
}
void CTeamplayRoundBasedRulesProxy::OnDataChanged( DataUpdateType_t updateType )
{
BaseClass::OnDataChanged( updateType );
// Reroute data changed calls to the non-entity gamerules
CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
Assert( pRules );
pRules->OnDataChanged(updateType);
}
#else
void* SendProxy_TeamplayRoundBasedRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID )
{
CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
Assert( pRules );
pRecipients->SetAllRecipients();
return pRules;
}
BEGIN_SEND_TABLE( CTeamplayRoundBasedRulesProxy, DT_TeamplayRoundBasedRulesProxy )
SendPropDataTable( "teamplayroundbased_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_TeamplayRoundBasedRules ), SendProxy_TeamplayRoundBasedRules )
END_SEND_TABLE()
BEGIN_DATADESC( CTeamplayRoundBasedRulesProxy )
// Inputs.
DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetStalemateOnTimelimit", InputSetStalemateOnTimelimit ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRulesProxy::InputSetStalemateOnTimelimit( inputdata_t &inputdata )
{
TeamplayRoundBasedRules()->SetStalemateOnTimelimit( inputdata.value.Bool() );
}
#endif
ConVar mp_capstyle( "mp_capstyle", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Sets the style of capture points used. 0 = Fixed players required to cap. 1 = More players cap faster, but longer cap times." );
ConVar mp_blockstyle( "mp_blockstyle", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Sets the style of capture point blocking used. 0 = Blocks break captures completely. 1 = Blocks only pause captures." );
ConVar mp_respawnwavetime( "mp_respawnwavetime", "10.0", FCVAR_NOTIFY | FCVAR_REPLICATED, "Time between respawn waves." );
ConVar mp_capdeteriorate_time( "mp_capdeteriorate_time", "90.0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Time it takes for a full capture point to deteriorate." );
ConVar mp_tournament( "mp_tournament", "0", FCVAR_REPLICATED | FCVAR_NOTIFY );
#if defined( TF_CLIENT_DLL ) || defined( TF_DLL )
ConVar mp_highlander( "mp_highlander", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Allow only 1 of each player class type." );
#endif
//Arena Mode
ConVar tf_arena_preround_time( "tf_arena_preround_time", "10", FCVAR_NOTIFY | FCVAR_REPLICATED, "Length of the Pre-Round time", true, 5.0, true, 15.0 );
ConVar tf_arena_round_time( "tf_arena_round_time", "0", FCVAR_NOTIFY | FCVAR_REPLICATED );
ConVar tf_arena_max_streak( "tf_arena_max_streak", "3", FCVAR_NOTIFY | FCVAR_REPLICATED, "Teams will be scrambled if one team reaches this streak" );
ConVar tf_arena_use_queue( "tf_arena_use_queue", "1", FCVAR_REPLICATED | FCVAR_NOTIFY, "Enables the spectator queue system for Arena." );
ConVar mp_teams_unbalance_limit( "mp_teams_unbalance_limit", "1", FCVAR_REPLICATED | FCVAR_NOTIFY,
"Teams are unbalanced when one team has this many more players than the other team. (0 disables check)",
true, 0, // min value
true, 30 // max value
);
ConVar mp_maxrounds( "mp_maxrounds", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "max number of rounds to play before server changes maps", true, 0, false, 0 );
ConVar mp_winlimit( "mp_winlimit", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Max score one team can reach before server changes maps", true, 0, false, 0 );
ConVar mp_disable_respawn_times( "mp_disable_respawn_times", "0", FCVAR_NOTIFY | FCVAR_REPLICATED );
ConVar mp_bonusroundtime( "mp_bonusroundtime", "15", FCVAR_REPLICATED, "Time after round win until round restarts", true, 5, true, 15 );
ConVar mp_stalemate_meleeonly( "mp_stalemate_meleeonly", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Restrict everyone to melee weapons only while in Sudden Death." );
ConVar mp_forceautoteam( "mp_forceautoteam", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Automatically assign players to teams when joining." );
#ifdef GAME_DLL
ConVar mp_showroundtransitions( "mp_showroundtransitions", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Show gamestate round transitions." );
ConVar mp_enableroundwaittime( "mp_enableroundwaittime", "1", FCVAR_REPLICATED, "Enable timers to wait between rounds." );
ConVar mp_showcleanedupents( "mp_showcleanedupents", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Show entities that are removed on round respawn." );
ConVar mp_restartround( "mp_restartround", "0", FCVAR_GAMEDLL, "If non-zero, the current round will restart in the specified number of seconds" );
ConVar mp_stalemate_timelimit( "mp_stalemate_timelimit", "240", FCVAR_REPLICATED, "Timelimit (in seconds) of the stalemate round." );
ConVar mp_autoteambalance( "mp_autoteambalance", "1", FCVAR_NOTIFY );
ConVar mp_stalemate_enable( "mp_stalemate_enable", "0", FCVAR_NOTIFY, "Enable/Disable stalemate mode." );
ConVar mp_match_end_at_timelimit( "mp_match_end_at_timelimit", "0", FCVAR_NOTIFY, "Allow the match to end when mp_timelimit hits instead of waiting for the end of the current round." );
ConVar mp_holiday_nogifts( "mp_holiday_nogifts", "0", FCVAR_NOTIFY, "Set to 1 to prevent holiday gifts from spawning when players are killed." );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void cc_SwitchTeams( const CCommand& args )
{
if ( UTIL_IsCommandIssuedByServerAdmin() )
{
CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
if ( pRules )
{
pRules->SetSwitchTeams( true );
mp_restartgame.SetValue( 5 );
pRules->ShouldResetScores( false, false );
pRules->ShouldResetRoundsPlayed( false );
}
}
}
static ConCommand mp_switchteams( "mp_switchteams", cc_SwitchTeams, "Switch teams and restart the game" );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void cc_ScrambleTeams( const CCommand& args )
{
if ( UTIL_IsCommandIssuedByServerAdmin() )
{
CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
if ( pRules )
{
pRules->SetScrambleTeams( true );
mp_restartgame.SetValue( 5 );
pRules->ShouldResetScores( true, false );
if ( args.ArgC() == 2 )
{
// Don't reset the roundsplayed when mp_scrambleteams 2 is passed
if ( atoi( args[1] ) == 2 )
{
pRules->ShouldResetRoundsPlayed( false );
}
}
}
}
}
static ConCommand mp_scrambleteams( "mp_scrambleteams", cc_ScrambleTeams, "Scramble the teams and restart the game" );
ConVar mp_scrambleteams_auto( "mp_scrambleteams_auto", "1", FCVAR_NOTIFY, "Server will automatically scramble the teams if criteria met. Only works on dedicated servers." );
ConVar mp_scrambleteams_auto_windifference( "mp_scrambleteams_auto_windifference", "2", FCVAR_NOTIFY, "Number of round wins a team must lead by in order to trigger an auto scramble." );
// Classnames of entities that are preserved across round restarts
static const char *s_PreserveEnts[] =
{
"player",
"viewmodel",
"worldspawn",
"soundent",
"ai_network",
"ai_hint",
"env_soundscape",
"env_soundscape_proxy",
"env_soundscape_triggerable",
"env_sprite",
"env_sun",
"env_wind",
"env_fog_controller",
"func_wall",
"func_illusionary",
"info_node",
"info_target",
"info_node_hint",
"point_commentary_node",
"point_viewcontrol",
"func_precipitation",
"func_team_wall",
"shadow_control",
"sky_camera",
"scene_manager",
"trigger_soundscape",
"commentary_auto",
"point_commentary_node",
"point_commentary_viewpoint",
"bot_roster",
"info_populator",
"", // END Marker
};
CON_COMMAND_F( mp_forcewin, "Forces team to win", FCVAR_CHEAT )
{
if ( !UTIL_IsCommandIssuedByServerAdmin() )
return;
CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
if ( pRules )
{
int iTeam = TEAM_UNASSIGNED;
if ( args.ArgC() == 1 )
{
// if no team specified, use player 1's team
iTeam = UTIL_PlayerByIndex( 1 )->GetTeamNumber();
}
else if ( args.ArgC() == 2 )
{
// if team # specified, use that
iTeam = atoi( args[1] );
}
else
{
Msg( "Usage: mp_forcewin <opt: team#>" );
return;
}
int iWinReason = ( TEAM_UNASSIGNED == iTeam ? WINREASON_STALEMATE : WINREASON_ALL_POINTS_CAPTURED );
pRules->SetWinningTeam( iTeam, iWinReason );
}
}
#endif // GAME_DLL
// Utility function
bool FindInList( const char **pStrings, const char *pToFind )
{
int i = 0;
while ( pStrings[i][0] != 0 )
{
if ( Q_stricmp( pStrings[i], pToFind ) == 0 )
return true;
i++;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTeamplayRoundBasedRules::CTeamplayRoundBasedRules( void )
{
for ( int i = 0; i < MAX_TEAMS; i++ )
{
m_flNextRespawnWave.Set( i, 0 );
m_TeamRespawnWaveTimes.Set( i, -1.0f );
m_bTeamReady.Set( i, false );
#ifdef GAME_DLL
m_flOriginalTeamRespawnWaveTime[i] = -1.0f;
#endif
}
for ( int i = 0; i < MAX_PLAYERS; i++ )
{
m_bPlayerReady.Set( i, false );
}
m_bInOvertime = false;
m_bInSetup = false;
m_bSwitchedTeamsThisRound = false;
m_flStopWatchTotalTime = -1.0f;
m_bMultipleTrains = false;
#ifdef GAME_DLL
m_pCurStateInfo = NULL;
State_Transition( GR_STATE_PREGAME );
m_bResetTeamScores = true;
m_bResetPlayerScores = true;
m_bResetRoundsPlayed = true;
InitTeams();
ResetMapTime();
ResetScores();
SetForceMapReset( true );
SetRoundToPlayNext( NULL_STRING );
m_bInWaitingForPlayers = false;
m_bAwaitingReadyRestart = false;
m_flRestartRoundTime = -1;
m_flMapResetTime = 0;
m_bPrevRoundWasWaitingForPlayers = false;
m_iWinningTeam = TEAM_UNASSIGNED;
m_iszPreviousRounds.RemoveAll();
SetFirstRoundPlayed( NULL_STRING );
m_bAllowStalemateAtTimelimit = false;
m_bChangelevelAfterStalemate = false;
m_flRoundStartTime = 0;
m_flNewThrottledAlertTime = 0;
m_flStartBalancingTeamsAt = 0;
m_bPrintedUnbalanceWarning = false;
m_flFoundUnbalancedTeamsTime = -1;
m_flWaitingForPlayersTimeEnds = 0.0f;
m_nRoundsPlayed = 0;
m_bUseAddScoreAnim = false;
m_bStopWatch = false;
m_bAwaitingReadyRestart = false;
if ( IsInTournamentMode() == true )
{
m_bAwaitingReadyRestart = true;
}
m_flAutoBalanceQueueTimeEnd = -1;
m_nAutoBalanceQueuePlayerIndex = -1;
m_nAutoBalanceQueuePlayerScore = -1;
SetDefLessFunc( m_GameTeams );
#endif
}
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetTeamRespawnWaveTime( int iTeam, float flValue )
{
if ( flValue < 0 )
{
flValue = 0;
}
// initialized to -1 so we can try to determine if this is the first spawn time we have received for this team
if ( m_flOriginalTeamRespawnWaveTime[iTeam] < 0 )
{
m_flOriginalTeamRespawnWaveTime[iTeam] = flValue;
}
m_TeamRespawnWaveTimes.Set( iTeam, flValue );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::AddTeamRespawnWaveTime( int iTeam, float flValue )
{
float flAddAmount = flValue;
float flCurrentSetting = m_TeamRespawnWaveTimes[iTeam];
float flNewValue;
if ( flCurrentSetting < 0 )
{
flCurrentSetting = mp_respawnwavetime.GetFloat();
}
// initialized to -1 so we can try to determine if this is the first spawn time we have received for this team
if ( m_flOriginalTeamRespawnWaveTime[iTeam] < 0 )
{
m_flOriginalTeamRespawnWaveTime[iTeam] = flCurrentSetting;
}
flNewValue = flCurrentSetting + flAddAmount;
if ( flNewValue < 0 )
{
flNewValue = 0;
}
m_TeamRespawnWaveTimes.Set( iTeam, flNewValue );
}
#endif
//-----------------------------------------------------------------------------
// Purpose: don't let us spawn before our freezepanel time would have ended, even if we skip it
//-----------------------------------------------------------------------------
float CTeamplayRoundBasedRules::GetNextRespawnWave( int iTeam, CBasePlayer *pPlayer )
{
if ( State_Get() == GR_STATE_STALEMATE )
return 0;
// If we are purely checking when the next respawn wave is for this team
if ( pPlayer == NULL )
{
return m_flNextRespawnWave[iTeam];
}
// The soonest this player may spawn
float flMinSpawnTime = GetMinTimeWhenPlayerMaySpawn( pPlayer );
if ( ShouldRespawnQuickly( pPlayer ) )
{
return flMinSpawnTime;
}
// the next scheduled respawn wave time
float flNextRespawnTime = m_flNextRespawnWave[iTeam];
// the length of one respawn wave. We'll check in increments of this
float flRespawnWaveMaxLen = GetRespawnWaveMaxLength( iTeam );
if ( flRespawnWaveMaxLen <= 0 )
{
return flNextRespawnTime;
}
// Keep adding the length of one respawn until we find a wave that
// this player will be eligible to spawn in.
while ( flNextRespawnTime < flMinSpawnTime )
{
flNextRespawnTime += flRespawnWaveMaxLen;
}
return flNextRespawnTime;
}
//-----------------------------------------------------------------------------
// Purpose: Is the player past the required delays for spawning
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::HasPassedMinRespawnTime( CBasePlayer *pPlayer )
{
float flMinSpawnTime = GetMinTimeWhenPlayerMaySpawn( pPlayer );
return ( gpGlobals->curtime > flMinSpawnTime );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CTeamplayRoundBasedRules::GetMinTimeWhenPlayerMaySpawn( CBasePlayer *pPlayer )
{
// Min respawn time is the sum of
//
// a) the length of one full *unscaled* respawn wave for their team
// and
// b) death anim length + freeze panel length
float flDeathAnimLength = 2.0 + spec_freeze_traveltime.GetFloat() + spec_freeze_time.GetFloat();
float fMinDelay = flDeathAnimLength;
if ( !ShouldRespawnQuickly( pPlayer ) )
{
fMinDelay += GetRespawnWaveMaxLength( pPlayer->GetTeamNumber(), false );
}
return pPlayer->GetDeathTime() + fMinDelay;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CTeamplayRoundBasedRules::GetRespawnTimeScalar( int iTeam )
{
// For long respawn times, scale the time as the number of players drops
int iOptimalPlayers = 8; // 16 players total, 8 per team
int iNumPlayers = GetGlobalTeam(iTeam)->GetNumPlayers();
float flScale = RemapValClamped( iNumPlayers, 1, iOptimalPlayers, 0.25, 1.0 );
return flScale;
}
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetForceMapReset( bool reset )
{
m_bForceMapReset = reset;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::Think( void )
{
if ( g_fGameOver ) // someone else quit the game already
{
// check to see if we should change levels now
if ( m_flIntermissionEndTime && ( m_flIntermissionEndTime < gpGlobals->curtime ) )
{
if ( !IsX360() )
{
ChangeLevel(); // intermission is over
}
else
{
IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
if ( event )
{
event->SetBool( "forceupload", true );
gameeventmanager->FireEvent( event );
}
engine->MultiplayerEndGame();
}
// Don't run this code again
m_flIntermissionEndTime = 0.f;
}
return;
}
State_Think();
if ( m_hWaitingForPlayersTimer )
{
Assert( m_bInWaitingForPlayers );
}
if ( gpGlobals->curtime > m_flNextPeriodicThink )
{
// Don't end the game during win or stalemate states
if ( State_Get() != GR_STATE_TEAM_WIN && State_Get() != GR_STATE_STALEMATE && State_Get() != GR_STATE_GAME_OVER )
{
if ( CheckWinLimit() )
return;
if ( CheckMaxRounds() )
return;
}
CheckRestartRound();
CheckWaitingForPlayers();
m_flNextPeriodicThink = gpGlobals->curtime + 1.0;
}
// Bypass teamplay think.
CGameRules::Think();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::TimerMayExpire( void )
{
#ifndef CSTRIKE_DLL
// team_train_watchers can also prevent timer expiring ( overtime )
CTeamTrainWatcher *pWatcher = dynamic_cast<CTeamTrainWatcher*>( gEntList.FindEntityByClassname( NULL, "team_train_watcher" ) );
while ( pWatcher )
{
if ( !pWatcher->TimerMayExpire() )
{
return false;
}
pWatcher = dynamic_cast<CTeamTrainWatcher*>( gEntList.FindEntityByClassname( pWatcher, "team_train_watcher" ) );
}
#endif
return BaseClass::TimerMayExpire();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckChatText( CBasePlayer *pPlayer, char *pText )
{
CheckChatForReadySignal( pPlayer, pText );
BaseClass::CheckChatText( pPlayer, pText );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckChatForReadySignal( CBasePlayer *pPlayer, const char *chatmsg )
{
if ( IsInTournamentMode() == false )
{
if( m_bAwaitingReadyRestart && FStrEq( chatmsg, mp_clan_ready_signal.GetString() ) )
{
int iTeam = pPlayer->GetTeamNumber();
if ( iTeam > LAST_SHARED_TEAM && iTeam < GetNumberOfTeams() )
{
m_bTeamReady.Set( iTeam, true );
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_team_ready" );
if ( event )
{
event->SetInt( "team", iTeam );
gameeventmanager->FireEvent( event );
}
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::GoToIntermission( void )
{
if ( IsInTournamentMode() == true )
return;
BaseClass::GoToIntermission();
// set all players to FL_FROZEN
for ( int i = 1; i <= MAX_PLAYERS; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( pPlayer )
{
pPlayer->AddFlag( FL_FROZEN );
}
}
// Print out map stats to a text file
//WriteStatsFile( "stats.xml" );
State_Enter( GR_STATE_GAME_OVER );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetInWaitingForPlayers( bool bWaitingForPlayers )
{
// never waiting for players when loading a bug report
if ( IsLoadingBugBaitReport() || gpGlobals->eLoadType == MapLoad_Background )
{
m_bInWaitingForPlayers = false;
return;
}
if( m_bInWaitingForPlayers == bWaitingForPlayers )
return;
if ( IsInArenaMode() == true && m_flWaitingForPlayersTimeEnds == -1 && IsInTournamentMode() == false )
{
m_bInWaitingForPlayers = false;
return;
}
m_bInWaitingForPlayers = bWaitingForPlayers;
if( m_bInWaitingForPlayers )
{
m_flWaitingForPlayersTimeEnds = gpGlobals->curtime + mp_waitingforplayers_time.GetFloat();
}
else
{
m_flWaitingForPlayersTimeEnds = -1;
if ( m_hWaitingForPlayersTimer )
{
UTIL_Remove( m_hWaitingForPlayersTimer );
}
RestoreActiveTimer();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetOvertime( bool bOvertime )
{
if ( m_bInOvertime == bOvertime )
return;
if ( bOvertime )
{
UTIL_LogPrintf( "World triggered \"Round_Overtime\"\n" );
}
m_bInOvertime = bOvertime;
if ( m_bInOvertime )
{
// tell train watchers that we've transitioned to overtime
#ifndef CSTRIKE_DLL
CTeamTrainWatcher *pWatcher = dynamic_cast<CTeamTrainWatcher*>( gEntList.FindEntityByClassname( NULL, "team_train_watcher" ) );
while ( pWatcher )
{
variant_t emptyVariant;
pWatcher->AcceptInput( "OnStartOvertime", NULL, NULL, emptyVariant, 0 );
pWatcher = dynamic_cast<CTeamTrainWatcher*>( gEntList.FindEntityByClassname( pWatcher, "team_train_watcher" ) );
}
#endif
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetSetup( bool bSetup )
{
if ( m_bInSetup == bSetup )
return;
m_bInSetup = bSetup;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckWaitingForPlayers( void )
{
// never waiting for players when loading a bug report, or training
if ( IsLoadingBugBaitReport() || gpGlobals->eLoadType == MapLoad_Background || !AllowWaitingForPlayers() )
return;
if( mp_waitingforplayers_restart.GetBool() )
{
if( m_bInWaitingForPlayers )
{
m_flWaitingForPlayersTimeEnds = gpGlobals->curtime + mp_waitingforplayers_time.GetFloat();
if ( m_hWaitingForPlayersTimer )
{
variant_t sVariant;
sVariant.SetInt( m_flWaitingForPlayersTimeEnds - gpGlobals->curtime );
m_hWaitingForPlayersTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
}
}
else
{
SetInWaitingForPlayers( true );
}
mp_waitingforplayers_restart.SetValue( 0 );
}
if( (mp_waitingforplayers_cancel.GetBool() || IsInItemTestingMode()) && IsInTournamentMode() == false )
{
// Cancel the wait period and manually Resume() the timer if
// it's not supposed to start paused at the beginning of a round.
// We must do this before SetInWaitingForPlayers() is called because it will
// restore the timer in the HUD and set the handle to NULL
#ifndef CSTRIKE_DLL
if ( m_hPreviousActiveTimer.Get() )
{
CTeamRoundTimer *pTimer = dynamic_cast<CTeamRoundTimer*>( m_hPreviousActiveTimer.Get() );
if ( pTimer && !pTimer->StartPaused() )
{
pTimer->ResumeTimer();
}
}
#endif
SetInWaitingForPlayers( false );
mp_waitingforplayers_cancel.SetValue( 0 );
}
if( m_bInWaitingForPlayers )
{
if ( IsInTournamentMode() == true )
return;
// only exit the waitingforplayers if the time is up, and we are not in a round
// restart countdown already, and we are not waiting for a ready restart
if( gpGlobals->curtime > m_flWaitingForPlayersTimeEnds && m_flRestartRoundTime < 0 && !m_bAwaitingReadyRestart )
{
m_flRestartRoundTime = gpGlobals->curtime; // reset asap
if ( IsInArenaMode() == true )
{
if ( gpGlobals->curtime > m_flWaitingForPlayersTimeEnds )
{
SetInWaitingForPlayers( false );
State_Transition( GR_STATE_PREROUND );
}
return;
}
// if "waiting for players" is ending and we're restarting...
// keep the current round that we're already running around in as the first round after the restart
CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
if ( pMaster && pMaster->PlayingMiniRounds() && pMaster->GetCurrentRound() )
{
SetRoundToPlayNext( pMaster->GetRoundToUseAfterRestart() );
}
}
else
{
if ( !m_hWaitingForPlayersTimer )
{
// Stop any timers, and bring up a new one
HideActiveTimer();
#ifndef CSTRIKE_DLL
variant_t sVariant;
m_hWaitingForPlayersTimer = (CTeamRoundTimer*)CBaseEntity::Create( "team_round_timer", vec3_origin, vec3_angle );
m_hWaitingForPlayersTimer->SetName( MAKE_STRING("zz_teamplay_waiting_timer") );
m_hWaitingForPlayersTimer->KeyValue( "show_in_hud", "1" );
sVariant.SetInt( m_flWaitingForPlayersTimeEnds - gpGlobals->curtime );
m_hWaitingForPlayersTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
m_hWaitingForPlayersTimer->AcceptInput( "Resume", NULL, NULL, sVariant, 0 );
m_hWaitingForPlayersTimer->AcceptInput( "Enable", NULL, NULL, sVariant, 0 );
#endif
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckRestartRound( void )
{
if( mp_clan_readyrestart.GetBool() && IsInTournamentMode() == false )
{
m_bAwaitingReadyRestart = true;
for ( int i = LAST_SHARED_TEAM+1; i < GetNumberOfTeams(); i++ )
{
m_bTeamReady.Set( i, false );
}
const char *pszReadyString = mp_clan_ready_signal.GetString();
UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#clan_ready_rules", pszReadyString );
UTIL_ClientPrintAll( HUD_PRINTTALK, "#clan_ready_rules", pszReadyString );
// Don't let them put anything malicious in there
if( pszReadyString == NULL || Q_strlen(pszReadyString) > 16 )
{
pszReadyString = "ready";
}
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_ready_restart" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
mp_clan_readyrestart.SetValue( 0 );
// cancel any restart round in progress
m_flRestartRoundTime = -1;
}
// Restart the game if specified by the server
int iRestartDelay = mp_restartround.GetInt();
bool bRestartGameNow = mp_restartgame_immediate.GetBool();
if ( iRestartDelay == 0 && !bRestartGameNow )
{
iRestartDelay = mp_restartgame.GetInt();
}
if ( iRestartDelay > 0 || bRestartGameNow )
{
int iDelayMax = 60;
#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
iDelayMax = 180;
}
#endif // #if defined(TF_CLIENT_DLL) || defined(TF_DLL)
if ( iRestartDelay > iDelayMax )
{
iRestartDelay = iDelayMax;
}
if ( mp_restartgame.GetInt() > 0 || bRestartGameNow )
{
SetForceMapReset( true );
}
else
{
SetForceMapReset( false );
}
SetInStopWatch( false );
if ( bRestartGameNow )
{
iRestartDelay = 0;
}
m_flRestartRoundTime = gpGlobals->curtime + iRestartDelay;
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_restart_seconds" );
if ( event )
{
event->SetInt( "seconds", iRestartDelay );
gameeventmanager->FireEvent( event );
}
if ( IsInTournamentMode() == false )
{
// let the players know
const char *pFormat = NULL;
if ( mp_restartgame.GetInt() > 0 )
{
if ( ShouldSwitchTeams() )
{
pFormat = ( iRestartDelay > 1 ) ? "#game_switch_in_secs" : "#game_switch_in_sec";
}
else if ( ShouldScrambleTeams() )
{
pFormat = ( iRestartDelay > 1 ) ? "#game_scramble_in_secs" : "#game_scramble_in_sec";
#ifdef TF_DLL
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_alert" );
if ( event )
{
event->SetInt( "alert_type", HUD_ALERT_SCRAMBLE_TEAMS );
gameeventmanager->FireEvent( event );
}
pFormat = NULL;
#endif
}
}
else if ( mp_restartround.GetInt() > 0 )
{
pFormat = ( iRestartDelay > 1 ) ? "#round_restart_in_secs" : "#round_restart_in_sec";
}
if ( pFormat )
{
char strRestartDelay[64];
Q_snprintf( strRestartDelay, sizeof( strRestartDelay ), "%d", iRestartDelay );
UTIL_ClientPrintAll( HUD_PRINTCENTER, pFormat, strRestartDelay );
UTIL_ClientPrintAll( HUD_PRINTCONSOLE, pFormat, strRestartDelay );
}
}
mp_restartround.SetValue( 0 );
mp_restartgame.SetValue( 0 );
mp_restartgame_immediate.SetValue( 0 );
// cancel any ready restart in progress
m_bAwaitingReadyRestart = false;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::CheckTimeLimit( void )
{
if ( IsInPreMatch() == true )
return false;
if ( ( mp_timelimit.GetInt() > 0 && CanChangelevelBecauseOfTimeLimit() ) || m_bChangelevelAfterStalemate )
{
// If there's less than 5 minutes to go, just switch now. This avoids the problem
// of sudden death modes starting shortly after a new round starts.
const int iMinTime = 5;
bool bSwitchDueToTime = ( mp_timelimit.GetInt() > iMinTime && GetTimeLeft() < (iMinTime * 60) );
if ( IsInTournamentMode() == true )
{
if ( TournamentModeCanEndWithTimelimit() == false )
{
return false;
}
bSwitchDueToTime = false;
}
if ( IsInArenaMode() == true )
{
bSwitchDueToTime = false;
}
if( GetTimeLeft() <= 0 || m_bChangelevelAfterStalemate || bSwitchDueToTime )
{
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_game_over" );
if ( event )
{
event->SetString( "reason", "Reached Time Limit" );
gameeventmanager->FireEvent( event );
}
SendTeamScoresEvent();
GoToIntermission();
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::IsGameUnderTimeLimit( void )
{
return ( mp_timelimit.GetInt() > 0 );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTeamplayRoundBasedRules::GetTimeLeft( void )
{
float flTimeLimit = mp_timelimit.GetInt() * 60;
float flMapChangeTime = m_flMapResetTime + flTimeLimit;
// If the round timer is longer, let the round complete
// TFTODO: Do we need to worry about the timelimit running our during a round?
int iTime = (int)(flMapChangeTime - gpGlobals->curtime);
if ( iTime < 0 )
{
iTime = 0;
}
return ( iTime );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::CheckNextLevelCvar( void )
{
if ( m_bForceMapReset )
{
if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) )
{
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_game_over" );
if ( event )
{
event->SetString( "reason", "NextLevel CVAR" );
gameeventmanager->FireEvent( event );
}
GoToIntermission();
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::CheckWinLimit( void )
{
// has one team won the specified number of rounds?
int iWinLimit = mp_winlimit.GetInt();
if ( iWinLimit > 0 )
{
for ( int i = LAST_SHARED_TEAM+1; i < GetNumberOfTeams(); i++ )
{
CTeam *pTeam = GetGlobalTeam(i);
Assert( pTeam );
if ( pTeam->GetScore() >= iWinLimit )
{
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_game_over" );
if ( event )
{
event->SetString( "reason", "Reached Win Limit" );
gameeventmanager->FireEvent( event );
}
GoToIntermission();
return true;
}
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::CheckMaxRounds()
{
if ( mp_maxrounds.GetInt() > 0 && IsInPreMatch() == false )
{
if ( m_nRoundsPlayed >= mp_maxrounds.GetInt() )
{
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_game_over" );
if ( event )
{
event->SetString( "reason", "Reached Round Limit" );
gameeventmanager->FireEvent( event );
}
GoToIntermission();
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Transition( gamerules_roundstate_t newState )
{
m_prevState = State_Get();
State_Leave();
State_Enter( newState );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter( gamerules_roundstate_t newState )
{
m_iRoundState = newState;
m_pCurStateInfo = State_LookupInfo( newState );
m_flLastRoundStateChangeTime = gpGlobals->curtime;
if ( mp_showroundtransitions.GetInt() > 0 )
{
if ( m_pCurStateInfo )
Msg( "Gamerules: entering state '%s'\n", m_pCurStateInfo->m_pStateName );
else
Msg( "Gamerules: entering state #%d\n", newState );
}
// Initialize the new state.
if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState )
{
(this->*m_pCurStateInfo->pfnEnterState)();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave()
{
if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState )
{
(this->*m_pCurStateInfo->pfnLeaveState)();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think()
{
if ( m_pCurStateInfo && m_pCurStateInfo->pfnThink )
{
(this->*m_pCurStateInfo->pfnThink)();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CGameRulesRoundStateInfo* CTeamplayRoundBasedRules::State_LookupInfo( gamerules_roundstate_t state )
{
static CGameRulesRoundStateInfo playerStateInfos[] =
{
{ GR_STATE_INIT, "GR_STATE_INIT", &CTeamplayRoundBasedRules::State_Enter_INIT, NULL, &CTeamplayRoundBasedRules::State_Think_INIT },
{ GR_STATE_PREGAME, "GR_STATE_PREGAME", &CTeamplayRoundBasedRules::State_Enter_PREGAME, NULL, &CTeamplayRoundBasedRules::State_Think_PREGAME },
{ GR_STATE_STARTGAME, "GR_STATE_STARTGAME", &CTeamplayRoundBasedRules::State_Enter_STARTGAME, NULL, &CTeamplayRoundBasedRules::State_Think_STARTGAME },
{ GR_STATE_PREROUND, "GR_STATE_PREROUND", &CTeamplayRoundBasedRules::State_Enter_PREROUND, &CTeamplayRoundBasedRules::State_Leave_PREROUND, &CTeamplayRoundBasedRules::State_Think_PREROUND },
{ GR_STATE_RND_RUNNING, "GR_STATE_RND_RUNNING", &CTeamplayRoundBasedRules::State_Enter_RND_RUNNING, NULL, &CTeamplayRoundBasedRules::State_Think_RND_RUNNING },
{ GR_STATE_TEAM_WIN, "GR_STATE_TEAM_WIN", &CTeamplayRoundBasedRules::State_Enter_TEAM_WIN, NULL, &CTeamplayRoundBasedRules::State_Think_TEAM_WIN },
{ GR_STATE_RESTART, "GR_STATE_RESTART", &CTeamplayRoundBasedRules::State_Enter_RESTART, NULL, &CTeamplayRoundBasedRules::State_Think_RESTART },
{ GR_STATE_STALEMATE, "GR_STATE_STALEMATE", &CTeamplayRoundBasedRules::State_Enter_STALEMATE, &CTeamplayRoundBasedRules::State_Leave_STALEMATE, &CTeamplayRoundBasedRules::State_Think_STALEMATE },
{ GR_STATE_GAME_OVER, "GR_STATE_GAME_OVER", NULL, NULL, NULL },
{ GR_STATE_BONUS, "GR_STATE_BONUS", &CTeamplayRoundBasedRules::State_Enter_BONUS, &CTeamplayRoundBasedRules::State_Leave_BONUS, &CTeamplayRoundBasedRules::State_Think_BONUS },
{ GR_STATE_BETWEEN_RNDS, "GR_STATE_BETWEEN_RNDS", &CTeamplayRoundBasedRules::State_Enter_BETWEEN_RNDS, &CTeamplayRoundBasedRules::State_Leave_BETWEEN_RNDS, &CTeamplayRoundBasedRules::State_Think_BETWEEN_RNDS },
};
for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ )
{
if ( playerStateInfos[i].m_iRoundState == state )
return &playerStateInfos[i];
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_INIT( void )
{
InitTeams();
ResetMapTime();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_INIT( void )
{
State_Transition( GR_STATE_PREGAME );
}
//-----------------------------------------------------------------------------
// Purpose: The server is idle and waiting for enough players to start up again.
// When we find an active player go to GR_STATE_STARTGAME.
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_PREGAME( void )
{
m_flNextPeriodicThink = gpGlobals->curtime + 0.1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_PREGAME( void )
{
CheckRespawnWaves();
// we'll just stay in pregame for the bugbait reports
if ( IsLoadingBugBaitReport() || gpGlobals->eLoadType == MapLoad_Background )
return;
// Commentary stays in this mode too
if ( IsInCommentaryMode() )
return;
if( CountActivePlayers() > 0 || (IsInArenaMode() == true && m_flWaitingForPlayersTimeEnds == 0.0f) )
{
State_Transition( GR_STATE_STARTGAME );
}
}
//-----------------------------------------------------------------------------
// Purpose: Wait a bit and then spawn everyone into the preround
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_STARTGAME( void )
{
m_flStateTransitionTime = gpGlobals->curtime;
m_bInitialSpawn = true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_STARTGAME()
{
if( gpGlobals->curtime > m_flStateTransitionTime )
{
if ( !IsInTraining() && !IsInItemTestingMode() )
{
ConVarRef tf_bot_offline_practice( "tf_bot_offline_practice" );
if ( mp_waitingforplayers_time.GetFloat() > 0 && tf_bot_offline_practice.GetInt() == 0 )
{
// go into waitingforplayers, reset at end of it
SetInWaitingForPlayers( true );
}
}
State_Transition( GR_STATE_PREROUND );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_PREROUND( void )
{
BalanceTeams( false );
m_flStartBalancingTeamsAt = gpGlobals->curtime + 60.0;
RoundRespawn();
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_start" );
if ( event )
{
event->SetBool( "full_reset", m_bForceMapReset );
gameeventmanager->FireEvent( event );
}
if ( IsInArenaMode() == true )
{
if ( CountActivePlayers() > 0 )
{
#ifndef CSTRIKE_DLL
variant_t sVariant;
if ( !m_hStalemateTimer )
{
m_hStalemateTimer = (CTeamRoundTimer*)CBaseEntity::Create( "team_round_timer", vec3_origin, vec3_angle );
}
m_hStalemateTimer->KeyValue( "show_in_hud", "1" );
sVariant.SetInt( tf_arena_preround_time.GetInt() );
m_hStalemateTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
m_hStalemateTimer->AcceptInput( "Resume", NULL, NULL, sVariant, 0 );
m_hStalemateTimer->AcceptInput( "Enable", NULL, NULL, sVariant, 0 );
#endif
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_update_timer" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
}
m_flStateTransitionTime = gpGlobals->curtime + tf_arena_preround_time.GetInt();
}
#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
else if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
State_Transition( GR_STATE_BETWEEN_RNDS );
TFObjectiveResource()->SetMannVsMachineBetweenWaves( true );
}
#endif // #if defined(TF_CLIENT_DLL) || defined(TF_DLL)
else
{
m_flStateTransitionTime = gpGlobals->curtime + 5 * mp_enableroundwaittime.GetFloat();
}
StopWatchModeThink();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave_PREROUND( void )
{
PreRound_End();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_PREROUND( void )
{
if( gpGlobals->curtime > m_flStateTransitionTime )
{
if ( IsInArenaMode() == true )
{
if ( IsInWaitingForPlayers() == true )
{
if ( IsInTournamentMode() == true )
{
// check round restart
CheckReadyRestart();
State_Transition( GR_STATE_STALEMATE );
}
return;
}
State_Transition( GR_STATE_STALEMATE );
// hide the class composition panel
}
else
{
State_Transition( GR_STATE_RND_RUNNING );
}
}
CheckRespawnWaves();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_RND_RUNNING( void )
{
SetupOnRoundRunning();
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_active" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
if( !IsInWaitingForPlayers() )
{
PlayStartRoundVoice();
}
m_bChangeLevelOnRoundEnd = false;
m_bPrevRoundWasWaitingForPlayers = false;
m_flNextBalanceTeamsTime = gpGlobals->curtime + 1.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckReadyRestart( void )
{
// check round restart
if( m_flRestartRoundTime > 0 && m_flRestartRoundTime <= gpGlobals->curtime && !g_pServerBenchmark->IsBenchmarkRunning() )
{
m_flRestartRoundTime = -1;
#ifdef TF_DLL
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && g_pPopulationManager )
{
if ( TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() )
{
g_pPopulationManager->StartCurrentWave();
}
return;
}
#endif // TF_DLL
// time to restart!
State_Transition( GR_STATE_RESTART );
}
// check ready restart
if( m_bAwaitingReadyRestart )
{
int nTime = 5;
bool bTeamReady = false;
#ifdef TF_DLL
if ( TFGameRules() )
{
if ( TFGameRules()->IsMannVsMachineMode() )
{
bTeamReady = AreDefendingPlayersReady();
if ( bTeamReady )
{
nTime = 10;
}
}
else
{
bTeamReady = m_bTeamReady[TF_TEAM_BLUE] && m_bTeamReady[TF_TEAM_RED];
}
}
#endif // TF_DLL
if ( bTeamReady )
{
//State_Transition( GR_STATE_RESTART );
mp_restartgame.SetValue( nTime );
m_bAwaitingReadyRestart = false;
ShouldResetScores( true, true );
ShouldResetRoundsPlayed( true );
}
}
}
#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::AreDefendingPlayersReady()
{
// Get list of defenders
CUtlVector<LobbyPlayerInfo_t> vecMvMDefenders;
GetMvMPotentialDefendersLobbyPlayerInfo( vecMvMDefenders );
// Scan all the players, and bail as soon as we find one person
// worth waiting for
bool bAtLeastOnePersonReady = false;
for ( int i = 0; i < vecMvMDefenders.Count(); i++ )
{
// Are they on the red team?
const LobbyPlayerInfo_t &p = vecMvMDefenders[i];
if ( !p.m_bConnected || p.m_iTeam == TEAM_UNASSIGNED || p.m_nEntNum <= 0 || p.m_nEntNum >= MAX_PLAYERS )
{
// They're still getting set up. We'll wait for them,
// but only if they are in the lobby
if ( p.m_bInLobby )
return false;
}
else if ( p.m_iTeam == TF_TEAM_PVE_DEFENDERS )
{
// If he isn't ready, then we aren't ready
if ( !m_bPlayerReady[ p.m_nEntNum ] )
return false;
// He's totally ready
bAtLeastOnePersonReady = true;
}
else
{
// And you may ask yourself, "How did I get here?"
Assert( p.m_iTeam == TF_TEAM_PVE_DEFENDERS );
}
}
// We didn't find anybody who we should wait for, so
// if at least one person is ready, then we're ready
return bAtLeastOnePersonReady;
}
#endif // #if defined(TF_CLIENT_DLL) || defined(TF_DLL)
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_RND_RUNNING( void )
{
//if we don't find any active players, return to GR_STATE_PREGAME
if( CountActivePlayers() <= 0 )
{
#if defined( REPLAY_ENABLED )
if ( g_pReplay )
{
// Write replay and stop recording if appropriate
g_pReplay->SV_EndRecordingSession();
}
#endif
State_Transition( GR_STATE_PREGAME );
return;
}
if ( m_flNextBalanceTeamsTime < gpGlobals->curtime )
{
BalanceTeams( true );
m_flNextBalanceTeamsTime = gpGlobals->curtime + 1.0f;
}
CheckRespawnWaves();
// check round restart
CheckReadyRestart();
// See if we're coming up to the server timelimit, in which case force a stalemate immediately.
if ( State_Get() == GR_STATE_RND_RUNNING && mp_timelimit.GetInt() > 0 && IsInPreMatch() == false && GetTimeLeft() <= 0 )
{
if ( m_bAllowStalemateAtTimelimit || ( mp_match_end_at_timelimit.GetBool() && !IsValveMap() ) )
{
int iDrawScoreCheck = -1;
int iWinningTeam = 0;
bool bTeamsAreDrawn = true;
for ( int i = FIRST_GAME_TEAM; (i < GetNumberOfTeams()) && bTeamsAreDrawn; i++ )
{
int iTeamScore = GetGlobalTeam(i)->GetScore();
if ( iTeamScore > iDrawScoreCheck )
{
iWinningTeam = i;
}
if ( iTeamScore != iDrawScoreCheck )
{
if ( iDrawScoreCheck == -1 )
{
iDrawScoreCheck = iTeamScore;
}
else
{
bTeamsAreDrawn = false;
}
}
}
if ( bTeamsAreDrawn )
{
if ( CanGoToStalemate() )
{
m_bChangelevelAfterStalemate = true;
SetStalemate( STALEMATE_SERVER_TIMELIMIT, m_bForceMapReset );
}
else
{
SetOvertime( true );
}
}
else
{
SetWinningTeam( iWinningTeam, WINREASON_TIMELIMIT, true, false, true );
}
}
}
StopWatchModeThink();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_TEAM_WIN( void )
{
float flTime = GetBonusRoundTime();
m_flStateTransitionTime = gpGlobals->curtime + flTime;
// if we're forcing the map to reset it must be the end of a "full" round not a mini-round
if ( m_bForceMapReset )
{
m_nRoundsPlayed++;
}
InternalHandleTeamWin( m_iWinningTeam );
SendWinPanelInfo();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_TEAM_WIN( void )
{
if( gpGlobals->curtime > m_flStateTransitionTime )
{
bool bDone = !(!CheckTimeLimit() && !CheckWinLimit() && !CheckMaxRounds() && !CheckNextLevelCvar());
// check the win limit, max rounds, time limit and nextlevel cvar before starting the next round
if ( bDone == false )
{
PreviousRoundEnd();
if ( ShouldGoToBonusRound() )
{
State_Transition( GR_STATE_BONUS );
}
else
{
#if defined( REPLAY_ENABLED )
if ( g_pReplay )
{
// Write replay and stop recording if appropriate
g_pReplay->SV_EndRecordingSession();
}
#endif
State_Transition( GR_STATE_PREROUND );
}
}
else if ( IsInTournamentMode() == true )
{
for ( int i = 1; i <= MAX_PLAYERS; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD );
}
RestartTournament();
if ( IsInArenaMode() == true )
{
#if defined( REPLAY_ENABLED )
if ( g_pReplay )
{
// Write replay and stop recording if appropriate
g_pReplay->SV_EndRecordingSession();
}
#endif
State_Transition( GR_STATE_PREROUND );
}
else
{
#ifdef TF_DLL
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
// one of the convars mp_timelimit, mp_winlimit, mp_maxrounds, or nextlevel has been triggered
if ( g_pPopulationManager )
{
for ( int i = 1; i <= MAX_PLAYERS; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
pPlayer->AddFlag( FL_FROZEN );
pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD );
}
g_fGameOver = true;
g_pPopulationManager->SetMapRestartTime( gpGlobals->curtime + 10.0f );
State_Enter( GR_STATE_GAME_OVER );
return;
}
}
#endif // TF_DLL
State_Transition( GR_STATE_RND_RUNNING );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_STALEMATE( void )
{
m_flStalemateStartTime = gpGlobals->curtime;
SetupOnStalemateStart();
// Stop any timers, and bring up a new one
HideActiveTimer();
if ( m_hStalemateTimer )
{
UTIL_Remove( m_hStalemateTimer );
m_hStalemateTimer = NULL;
}
int iTimeLimit = mp_stalemate_timelimit.GetInt();
if ( IsInArenaMode() == true )
{
iTimeLimit = tf_arena_round_time.GetInt();
}
if ( iTimeLimit > 0 )
{
#ifndef CSTRIKE_DLL
variant_t sVariant;
if ( !m_hStalemateTimer )
{
m_hStalemateTimer = (CTeamRoundTimer*)CBaseEntity::Create( "team_round_timer", vec3_origin, vec3_angle );
}
m_hStalemateTimer->KeyValue( "show_in_hud", "1" );
sVariant.SetInt( iTimeLimit );
m_hStalemateTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
m_hStalemateTimer->AcceptInput( "Resume", NULL, NULL, sVariant, 0 );
m_hStalemateTimer->AcceptInput( "Enable", NULL, NULL, sVariant, 0 );
#endif
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_update_timer" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave_STALEMATE( void )
{
SetupOnStalemateEnd();
if ( m_hStalemateTimer )
{
UTIL_Remove( m_hStalemateTimer );
}
if ( IsInArenaMode() == false )
{
RestoreActiveTimer();
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_update_timer" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_BONUS( void )
{
SetupOnBonusStart();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave_BONUS( void )
{
SetupOnBonusEnd();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_BONUS( void )
{
BonusStateThink();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_BETWEEN_RNDS( void )
{
BetweenRounds_Start();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Leave_BETWEEN_RNDS( void )
{
BetweenRounds_End();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_BETWEEN_RNDS( void )
{
BetweenRounds_Think();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::HideActiveTimer( void )
{
// We can't handle this, because we won't be able to restore multiple timers
Assert( m_hPreviousActiveTimer.Get() == NULL );
m_hPreviousActiveTimer = NULL;
#ifndef CSTRIKE_DLL
CBaseEntity *pEntity = NULL;
variant_t sVariant;
sVariant.SetInt( false );
while ((pEntity = gEntList.FindEntityByClassname( pEntity, "team_round_timer" )) != NULL)
{
CTeamRoundTimer *pTimer = assert_cast<CTeamRoundTimer*>(pEntity);
if ( pTimer && pTimer->ShowInHud() )
{
Assert( !m_hPreviousActiveTimer );
m_hPreviousActiveTimer = pTimer;
pEntity->AcceptInput( "ShowInHUD", NULL, NULL, sVariant, 0 );
}
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::RestoreActiveTimer( void )
{
if ( m_hPreviousActiveTimer )
{
variant_t sVariant;
sVariant.SetInt( true );
m_hPreviousActiveTimer->AcceptInput( "ShowInHUD", NULL, NULL, sVariant, 0 );
m_hPreviousActiveTimer = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_STALEMATE( void )
{
//if we don't find any active players, return to GR_STATE_PREGAME
if( CountActivePlayers() <= 0 && IsInArenaMode() == false )
{
#if defined( REPLAY_ENABLED )
if ( g_pReplay )
{
// Write replay and stop recording if appropriate
g_pReplay->SV_EndRecordingSession();
}
#endif
State_Transition( GR_STATE_PREGAME );
return;
}
if ( IsInTournamentMode() == true && IsInWaitingForPlayers() == true )
{
CheckReadyRestart();
CheckRespawnWaves();
return;
}
int iDeadTeam = TEAM_UNASSIGNED;
int iAliveTeam = TEAM_UNASSIGNED;
// If a team is fully killed, the other team has won
for ( int i = LAST_SHARED_TEAM+1; i < GetNumberOfTeams(); i++ )
{
CTeam *pTeam = GetGlobalTeam(i);
Assert( pTeam );
int iPlayers = pTeam->GetNumPlayers();
if ( iPlayers )
{
bool bFoundLiveOne = false;
for ( int player = 0; player < iPlayers; player++ )
{
if ( pTeam->GetPlayer(player) && pTeam->GetPlayer(player)->IsAlive() )
{
bFoundLiveOne = true;
break;
}
}
if ( bFoundLiveOne )
{
iAliveTeam = i;
}
else
{
iDeadTeam = i;
}
}
else
{
iDeadTeam = i;
}
}
if ( iDeadTeam && iAliveTeam )
{
// The live team has won.
bool bMasterHandled = false;
if ( !m_bForceMapReset )
{
// We're not resetting the map, so give the winners control
// of all the points that were in play this round.
// Find the control point master.
CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
if ( pMaster )
{
variant_t sVariant;
sVariant.SetInt( iAliveTeam );
pMaster->AcceptInput( "SetWinnerAndForceCaps", NULL, NULL, sVariant, 0 );
bMasterHandled = true;
}
}
if ( !bMasterHandled )
{
SetWinningTeam( iAliveTeam, WINREASON_OPPONENTS_DEAD, m_bForceMapReset );
}
}
else if ( ( iDeadTeam && iAliveTeam == TEAM_UNASSIGNED ) ||
( m_hStalemateTimer && TimerMayExpire() && m_hStalemateTimer->GetTimeRemaining() <= 0 ) )
{
bool bFullReset = true;
CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
if ( pMaster && pMaster->PlayingMiniRounds() )
{
// we don't need to do a full map reset for maps with mini-rounds
bFullReset = false;
}
// Both teams are dead. Pure stalemate.
SetWinningTeam( TEAM_UNASSIGNED, WINREASON_STALEMATE, bFullReset, false );
}
}
//-----------------------------------------------------------------------------
// Purpose: manual restart
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Enter_RESTART( void )
{
// send scores
SendTeamScoresEvent();
// send restart event
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_restart_round" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
m_bPrevRoundWasWaitingForPlayers = m_bInWaitingForPlayers;
SetInWaitingForPlayers( false );
ResetScores();
// reset the round time
ResetMapTime();
State_Transition( GR_STATE_PREROUND );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::State_Think_RESTART( void )
{
// should never get here, State_Enter_RESTART sets us into a different state
Assert( 0 );
}
//-----------------------------------------------------------------------------
// Purpose: Sorts teams by score
//-----------------------------------------------------------------------------
int TeamScoreSort( CTeam* const *pTeam1, CTeam* const *pTeam2 )
{
if ( !*pTeam1 )
return -1;
if ( !*pTeam2 )
return -1;
if ( (*pTeam1)->GetScore() > (*pTeam2)->GetScore() )
{
return 1;
}
return -1;
}
//-----------------------------------------------------------------------------
// Purpose: Input for other entities to declare a round winner.
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetWinningTeam( int team, int iWinReason, bool bForceMapReset /* = true */, bool bSwitchTeams /* = false*/, bool bDontAddScore /* = false*/ )
{
// Commentary doesn't let anyone win
if ( IsInCommentaryMode() )
return;
if ( ( team != TEAM_UNASSIGNED ) && ( team <= LAST_SHARED_TEAM || team >= GetNumberOfTeams() ) )
{
Assert( !"SetWinningTeam() called with invalid team." );
return;
}
// are we already in this state?
if ( State_Get() == GR_STATE_TEAM_WIN )
return;
SetForceMapReset( bForceMapReset );
SetSwitchTeams( bSwitchTeams );
m_iWinningTeam = team;
m_iWinReason = iWinReason;
PlayWinSong( team );
// only reward the team if they have won the map and we're going to do a full reset or the time has run out and we're changing maps
bool bRewardTeam = bForceMapReset || ( IsGameUnderTimeLimit() && ( GetTimeLeft() <= 0 ) );
if ( bDontAddScore == true )
{
bRewardTeam = false;
}
m_bUseAddScoreAnim = false;
if ( bRewardTeam && ( team != TEAM_UNASSIGNED ) && ShouldScorePerRound() )
{
GetGlobalTeam( team )->AddScore( TEAMPLAY_ROUND_WIN_SCORE );
m_bUseAddScoreAnim = true;
}
// this was a sudden death win if we were in stalemate then a team won it
bool bWasSuddenDeath = ( InStalemate() && m_iWinningTeam >= FIRST_GAME_TEAM );
State_Transition( GR_STATE_TEAM_WIN );
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_win" );
if ( event )
{
event->SetInt( "team", team );
event->SetInt( "winreason", iWinReason );
event->SetBool( "full_round", bForceMapReset );
event->SetFloat( "round_time", gpGlobals->curtime - m_flRoundStartTime );
event->SetBool( "was_sudden_death", bWasSuddenDeath );
// let derived classes add more fields to the event
FillOutTeamplayRoundWinEvent( event );
gameeventmanager->FireEvent( event );
}
// send team scores
SendTeamScoresEvent();
if ( team == TEAM_UNASSIGNED )
{
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBaseMultiplayerPlayer *pPlayer = ToBaseMultiplayerPlayer( UTIL_PlayerByIndex( i ) );
if ( !pPlayer )
continue;
pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_STALEMATE );
}
}
// Auto scramble teams?
if ( bForceMapReset && mp_scrambleteams_auto.GetBool() )
{
if ( IsInArenaMode() || IsInTournamentMode() || ShouldSkipAutoScramble() )
return;
#ifndef DEBUG
// Don't bother on a listen server - usually not desirable
if ( !engine->IsDedicatedServer() )
return;
#endif // DEBUG
// Skip if we have a nextlevel set
if ( !FStrEq( nextlevel.GetString(), "" ) )
return;
// Track the team scores
if ( m_iWinningTeam != TEAM_UNASSIGNED )
{
// m_GameTeams differs from g_Teams by storing only "Real" teams
if ( m_GameTeams.Count() == 0 )
{
int iTeamIndex = FIRST_GAME_TEAM;
CTeam *pTeam;
for ( pTeam = GetGlobalTeam(iTeamIndex); pTeam != NULL; pTeam = GetGlobalTeam(++iTeamIndex) )
{
m_GameTeams.Insert( iTeamIndex, 0 );
}
}
// Safety net hack - we assume there are only two "Real" teams
// driller: need to make this work in all cases
if ( m_GameTeams.Count() != 2 )
return;
}
// Look for impending level change
if ( ( ( mp_timelimit.GetInt() > 0 && CanChangelevelBecauseOfTimeLimit() ) || m_bChangelevelAfterStalemate ) && GetTimeLeft() <= 300 )
return;
if ( mp_winlimit.GetInt() || mp_maxrounds.GetInt() )
{
int nRoundsPlayed = GetRoundsPlayed();
if ( ( mp_maxrounds.GetInt() - nRoundsPlayed ) == 1 )
{
return;
}
int nWinLimit = mp_winlimit.GetInt();
for ( int iIndex = m_GameTeams.FirstInorder(); iIndex != m_GameTeams.InvalidIndex(); iIndex = m_GameTeams.NextInorder( iIndex ) )
{
int nTeamScore = GetGlobalTeam( m_GameTeams.Key( iIndex ) )->GetScore();
if ( nWinLimit - nTeamScore == 1 )
{
return;
}
}
}
// Increment win counters
int iWinningTeamIndex = m_GameTeams.Find( m_iWinningTeam );
if ( iWinningTeamIndex != m_GameTeams.InvalidIndex() )
{
m_GameTeams[iWinningTeamIndex]++;
}
else
{
Assert( iWinningTeamIndex == m_GameTeams.InvalidIndex() );
return;
}
// Did we hit our win delta?
int nWinDelta = abs( m_GameTeams[1] - m_GameTeams[0] );
if ( nWinDelta >= mp_scrambleteams_auto_windifference.GetInt() )
{
// Let the server know we're going to scramble on round restart
#ifdef TF_DLL
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_alert" );
if ( event )
{
event->SetInt( "alert_type", HUD_ALERT_SCRAMBLE_TEAMS );
gameeventmanager->FireEvent( event );
}
#else
const char *pszMessage = "#game_scramble_onrestart";
if ( pszMessage )
{
UTIL_ClientPrintAll( HUD_PRINTCENTER, pszMessage );
UTIL_ClientPrintAll( HUD_PRINTCONSOLE, pszMessage );
}
#endif
UTIL_LogPrintf( "World triggered \"ScrambleTeams_Auto\"\n" );
SetScrambleTeams( true );
ShouldResetScores( true, false );
ShouldResetRoundsPlayed( false );
}
// If we switch teams after this win, swap scores
if ( ShouldSwitchTeams() )
{
int nTempScore = m_GameTeams[0];
m_GameTeams[0] = m_GameTeams[1];
m_GameTeams[1] = nTempScore;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Input for other entities to declare a stalemate
// Most often a team_control_point_master saying that the
// round timer expired
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetStalemate( int iReason, bool bForceMapReset /* = true */, bool bSwitchTeams /* = false */ )
{
if ( IsInTournamentMode() == true && IsInPreMatch() == true )
return;
if ( !mp_stalemate_enable.GetBool() )
{
SetWinningTeam( TEAM_UNASSIGNED, WINREASON_STALEMATE, bForceMapReset, bSwitchTeams );
return;
}
if ( InStalemate() )
return;
SetForceMapReset( bForceMapReset );
m_iWinningTeam = TEAM_UNASSIGNED;
PlaySuddenDeathSong();
State_Transition( GR_STATE_STALEMATE );
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_stalemate" );
if ( event )
{
event->SetInt( "reason", iReason );
gameeventmanager->FireEvent( event );
}
}
#ifdef GAME_DLL
void CC_CH_ForceRespawn( void )
{
CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
if ( pRules )
{
pRules->RespawnPlayers( true );
}
}
static ConCommand mp_forcerespawnplayers("mp_forcerespawnplayers", CC_CH_ForceRespawn, "Force all players to respawn.", FCVAR_CHEAT );
static ConVar mp_tournament_allow_non_admin_restart( "mp_tournament_allow_non_admin_restart", "1", FCVAR_NONE, "Allow mp_tournament_restart command to be issued by players other than admin.");
void CC_CH_TournamentRestart( void )
{
if ( mp_tournament_allow_non_admin_restart.GetBool() == false )
{
if ( !UTIL_IsCommandIssuedByServerAdmin() )
return;
}
#ifdef TF_DLL
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
return;
#endif // TF_DLL
CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
if ( pRules )
{
pRules->RestartTournament();
}
}
static ConCommand mp_tournament_restart("mp_tournament_restart", CC_CH_TournamentRestart, "Restart Tournament Mode on the current level." );
void CTeamplayRoundBasedRules::RestartTournament( void )
{
if ( IsInTournamentMode() == false )
return;
SetInWaitingForPlayers( true );
m_bAwaitingReadyRestart = true;
m_flStopWatchTotalTime = -1.0f;
m_bStopWatch = false;
for ( int i = 0; i < MAX_TEAMS; i++ )
{
m_bTeamReady.Set( i, false );
}
for ( int i = 0; i < MAX_PLAYERS; i++ )
{
m_bPlayerReady.Set( i, false );
}
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
// Input : bForceRespawn - respawn player even if dead or dying
// bTeam - if true, only respawn the passed team
// iTeam - team to respawn
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::RespawnPlayers( bool bForceRespawn, bool bTeam /* = false */, int iTeam/* = TEAM_UNASSIGNED */ )
{
if ( bTeam )
{
Assert( iTeam > LAST_SHARED_TEAM && iTeam < GetNumberOfTeams() );
}
int iPlayersSpawned = 0;
CBasePlayer *pPlayer;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
pPlayer = ToBasePlayer( UTIL_PlayerByIndex( i ) );
if ( !pPlayer )
continue;
// Check for team specific spawn
if ( bTeam && pPlayer->GetTeamNumber() != iTeam )
continue;
// players that haven't chosen a team/class can never spawn
if ( !pPlayer->IsReadyToPlay() )
{
// Let the player spawn immediately when they do pick a class
if ( pPlayer->ShouldGainInstantSpawn() )
{
pPlayer->AllowInstantSpawn();
}
continue;
}
// If we aren't force respawning, don't respawn players that:
// - are alive
// - are still in the death anim stage of dying
if ( !bForceRespawn )
{
if ( pPlayer->IsAlive() )
continue;
if ( m_iRoundState != GR_STATE_PREROUND )
{
// If the player hasn't been dead the minimum respawn time, he
// waits until the next wave.
if ( bTeam && !HasPassedMinRespawnTime( pPlayer ) )
continue;
if ( !pPlayer->IsReadyToSpawn() )
{
// Let the player spawn immediately when they do pick a class
if ( pPlayer->ShouldGainInstantSpawn() )
{
pPlayer->AllowInstantSpawn();
}
continue;
}
}
}
// Respawn this player
pPlayer->ForceRespawn();
iPlayersSpawned++;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::InitTeams( void )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTeamplayRoundBasedRules::CountActivePlayers( void )
{
int i;
int count = 0;
CBasePlayer *pPlayer;
for (i = 1; i <= gpGlobals->maxClients; i++ )
{
pPlayer = ToBasePlayer( UTIL_PlayerByIndex( i ) );
if ( pPlayer )
{
if( pPlayer->IsReadyToPlay() )
{
count++;
}
}
}
return count;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::HandleTimeLimitChange( void )
{
// check that we have an active timer in the HUD and use mp_timelimit if we don't
if ( !MapHasActiveTimer() && ( mp_timelimit.GetInt() > 0 && GetTimeLeft() > 0 ) )
{
CreateTimeLimitTimer();
}
else
{
if ( m_hTimeLimitTimer )
{
UTIL_Remove( m_hTimeLimitTimer );
m_hTimeLimitTimer = NULL;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::MapHasActiveTimer( void )
{
#ifndef CSTRIKE_DLL
CBaseEntity *pEntity = NULL;
while ( ( pEntity = gEntList.FindEntityByClassname( pEntity, "team_round_timer" ) ) != NULL )
{
CTeamRoundTimer *pTimer = assert_cast<CTeamRoundTimer*>( pEntity );
if ( pTimer && pTimer->ShowInHud() && ( Q_stricmp( STRING( pTimer->GetEntityName() ), "zz_teamplay_timelimit_timer" ) != 0 ) )
{
return true;
}
}
#endif
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CreateTimeLimitTimer( void )
{
if ( IsInArenaMode () == true || IsInKothMode() == true )
return;
#ifndef CSTRIKE_DLL
if ( !m_hTimeLimitTimer )
{
m_hTimeLimitTimer = (CTeamRoundTimer*)CBaseEntity::Create( "team_round_timer", vec3_origin, vec3_angle );
m_hTimeLimitTimer->SetName( MAKE_STRING( "zz_teamplay_timelimit_timer" ) );
}
variant_t sVariant;
m_hTimeLimitTimer->KeyValue( "show_in_hud", "1" );
sVariant.SetInt( GetTimeLeft() );
m_hTimeLimitTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
m_hTimeLimitTimer->AcceptInput( "Resume", NULL, NULL, sVariant, 0 );
m_hTimeLimitTimer->AcceptInput( "Enable", NULL, NULL, sVariant, 0 );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::RoundRespawn( void )
{
m_flRoundStartTime = gpGlobals->curtime;
if ( m_bForceMapReset || m_bPrevRoundWasWaitingForPlayers )
{
CleanUpMap();
// clear out the previously played rounds
m_iszPreviousRounds.RemoveAll();
if ( mp_timelimit.GetInt() > 0 && GetTimeLeft() > 0 )
{
// check that we have an active timer in the HUD and use mp_timelimit if we don't
if ( !MapHasActiveTimer() )
{
CreateTimeLimitTimer();
}
}
m_iLastCapPointChanged = 0;
}
// reset our spawn times to the original values
for ( int i = 0; i < MAX_TEAMS; i++ )
{
if ( m_flOriginalTeamRespawnWaveTime[i] >= 0 )
{
m_TeamRespawnWaveTimes.Set( i, m_flOriginalTeamRespawnWaveTime[i] );
}
}
if ( !IsInWaitingForPlayers() )
{
if ( m_bForceMapReset )
{
UTIL_LogPrintf( "World triggered \"Round_Start\"\n" );
}
}
// Setup before respawning players, so we can mess with spawnpoints
SetupOnRoundStart();
// Do we need to switch the teams?
m_bSwitchedTeamsThisRound = false;
if ( ShouldSwitchTeams() )
{
m_bSwitchedTeamsThisRound = true;
HandleSwitchTeams();
SetSwitchTeams( false );
}
// Do we need to switch the teams?
if ( ShouldScrambleTeams() )
{
HandleScrambleTeams();
SetScrambleTeams( false );
}
#if defined( REPLAY_ENABLED )
bool bShouldWaitToStartRecording = ShouldWaitToStartRecording();
if ( g_pReplay && g_pReplay->SV_ShouldBeginRecording( bShouldWaitToStartRecording ) )
{
// Tell the replay manager that it should begin recording the new round as soon as possible
g_pReplay->SV_GetContext()->GetSessionRecorder()->StartRecording();
}
#endif
RespawnPlayers( true );
// reset per-round scores for each player
for ( int i = 1; i <= MAX_PLAYERS; i++ )
{
CBasePlayer *pPlayer = ToBasePlayer( UTIL_PlayerByIndex( i ) );
if ( pPlayer )
{
pPlayer->ResetPerRoundStats();
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Recreate all the map entities from the map data (preserving their indices),
// then remove everything else except the players.
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CleanUpMap()
{
if( mp_showcleanedupents.GetInt() )
{
Msg( "CleanUpMap\n===============\n" );
Msg( " Entities: %d (%d edicts)\n", gEntList.NumberOfEntities(), gEntList.NumberOfEdicts() );
}
// Get rid of all entities except players.
CBaseEntity *pCur = gEntList.FirstEnt();
while ( pCur )
{
if ( !RoundCleanupShouldIgnore( pCur ) )
{
if( mp_showcleanedupents.GetInt() & 1 )
{
Msg( "Removed Entity: %s\n", pCur->GetClassname() );
}
UTIL_Remove( pCur );
}
pCur = gEntList.NextEnt( pCur );
}
// Clear out the event queue
g_EventQueue.Clear();
// Really remove the entities so we can have access to their slots below.
gEntList.CleanupDeleteList();
engine->AllowImmediateEdictReuse();
if ( mp_showcleanedupents.GetInt() & 2 )
{
Msg( " Entities Left:\n" );
pCur = gEntList.FirstEnt();
while ( pCur )
{
Msg( " %s (%d)\n", pCur->GetClassname(), pCur->entindex() );
pCur = gEntList.NextEnt( pCur );
}
}
// Now reload the map entities.
class CTeamplayMapEntityFilter : public IMapEntityFilter
{
public:
CTeamplayMapEntityFilter()
{
m_pRules = assert_cast<CTeamplayRoundBasedRules*>( GameRules() );
}
virtual bool ShouldCreateEntity( const char *pClassname )
{
// Don't recreate the preserved entities.
if ( m_pRules->ShouldCreateEntity( pClassname ) )
return true;
// Increment our iterator since it's not going to call CreateNextEntity for this ent.
if ( m_iIterator != g_MapEntityRefs.InvalidIndex() )
{
m_iIterator = g_MapEntityRefs.Next( m_iIterator );
}
return false;
}
virtual CBaseEntity* CreateNextEntity( const char *pClassname )
{
if ( m_iIterator == g_MapEntityRefs.InvalidIndex() )
{
// This shouldn't be possible. When we loaded the map, it should have used
// CTeamplayMapEntityFilter, which should have built the g_MapEntityRefs list
// with the same list of entities we're referring to here.
Assert( false );
return NULL;
}
else
{
CMapEntityRef &ref = g_MapEntityRefs[m_iIterator];
m_iIterator = g_MapEntityRefs.Next( m_iIterator ); // Seek to the next entity.
if ( ref.m_iEdict == -1 || engine->PEntityOfEntIndex( ref.m_iEdict ) )
{
// Doh! The entity was delete and its slot was reused.
// Just use any old edict slot. This case sucks because we lose the baseline.
return CreateEntityByName( pClassname );
}
else
{
// Cool, the slot where this entity was is free again (most likely, the entity was
// freed above). Now create an entity with this specific index.
return CreateEntityByName( pClassname, ref.m_iEdict );
}
}
}
public:
int m_iIterator; // Iterator into g_MapEntityRefs.
CTeamplayRoundBasedRules *m_pRules;
};
CTeamplayMapEntityFilter filter;
filter.m_iIterator = g_MapEntityRefs.Head();
// DO NOT CALL SPAWN ON info_node ENTITIES!
MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::ShouldCreateEntity( const char *pszClassName )
{
return !FindInList( s_PreserveEnts, pszClassName );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::RoundCleanupShouldIgnore( CBaseEntity *pEnt )
{
return FindInList( s_PreserveEnts, pEnt->GetClassname() );
}
//-----------------------------------------------------------------------------
// Purpose: Sort function for sorting players by time spent connected ( user ID )
//-----------------------------------------------------------------------------
static int SwitchPlayersSort( CBaseMultiplayerPlayer * const *p1, CBaseMultiplayerPlayer * const *p2 )
{
// sort by score
return ( (*p2)->GetTeamBalanceScore() - (*p1)->GetTeamBalanceScore() );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::CheckRespawnWaves( void )
{
for ( int team = LAST_SHARED_TEAM+1; team < GetNumberOfTeams(); team++ )
{
if ( m_flNextRespawnWave[team] && m_flNextRespawnWave[team] > gpGlobals->curtime )
continue;
RespawnTeam( team );
// Set m_flNextRespawnWave to 0 when we don't have a respawn time to reduce networking
float flNextRespawnLength = GetRespawnWaveMaxLength( team );
if ( flNextRespawnLength )
{
m_flNextRespawnWave.Set( team, gpGlobals->curtime + flNextRespawnLength );
}
else
{
m_flNextRespawnWave.Set( team, 0.0f );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Return true if the teams are balanced after this function
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::BalanceTeams( bool bRequireSwitcheesToBeDead )
{
if ( mp_autoteambalance.GetBool() == false || ( IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true ) )
{
return;
}
if ( IsInTraining() || IsInItemTestingMode() )
{
return;
}
// we don't balance for a period of time at the start of the game
if ( gpGlobals->curtime < m_flStartBalancingTeamsAt )
{
return;
}
// wrap with this bool, indicates it's a round running switch and not a between rounds insta-switch
if ( bRequireSwitcheesToBeDead )
{
#ifndef CSTRIKE_DLL
// we don't balance if there is less than 60 seconds on the active timer
CTeamRoundTimer *pActiveTimer = GetActiveRoundTimer();
if ( pActiveTimer && pActiveTimer->GetTimeRemaining() < 60 )
{
return;
}
#endif
}
int iHeaviestTeam = TEAM_UNASSIGNED, iLightestTeam = TEAM_UNASSIGNED;
// Figure out if we're unbalanced
if ( !AreTeamsUnbalanced( iHeaviestTeam, iLightestTeam ) )
{
m_flFoundUnbalancedTeamsTime = -1;
m_bPrintedUnbalanceWarning = false;
return;
}
if ( m_flFoundUnbalancedTeamsTime < 0 )
{
m_flFoundUnbalancedTeamsTime = gpGlobals->curtime;
}
// if teams have been unbalanced for X seconds, play a warning
if ( !m_bPrintedUnbalanceWarning && ( ( gpGlobals->curtime - m_flFoundUnbalancedTeamsTime ) > 1.0 ) )
{
// print unbalance warning
UTIL_ClientPrintAll( HUD_PRINTTALK, "#game_auto_team_balance_in", "5" );
m_bPrintedUnbalanceWarning = true;
}
// teams are unblanced, figure out some players that need to be switched
CTeam *pHeavyTeam = GetGlobalTeam( iHeaviestTeam );
CTeam *pLightTeam = GetGlobalTeam( iLightestTeam );
Assert( pHeavyTeam && pLightTeam );
int iNumSwitchesRequired = ( pHeavyTeam->GetNumPlayers() - pLightTeam->GetNumPlayers() ) / 2;
// sort the eligible players and switch the n best candidates
CUtlVector<CBaseMultiplayerPlayer *> vecPlayers;
CBaseMultiplayerPlayer *pPlayer;
int iScore;
int i;
for ( i = 0; i < pHeavyTeam->GetNumPlayers(); i++ )
{
pPlayer = ToBaseMultiplayerPlayer( pHeavyTeam->GetPlayer(i) );
if ( !pPlayer )
continue;
if ( !pPlayer->CanBeAutobalanced() )
continue;
// calculate a score for this player. higher is more likely to be switched
iScore = pPlayer->CalculateTeamBalanceScore();
pPlayer->SetTeamBalanceScore( iScore );
vecPlayers.AddToTail( pPlayer );
}
// sort the vector
vecPlayers.Sort( SwitchPlayersSort );
int iNumEligibleSwitchees = iNumSwitchesRequired + 2;
for ( int i=0; i<vecPlayers.Count() && iNumSwitchesRequired > 0 && i < iNumEligibleSwitchees; i++ )
{
pPlayer = vecPlayers.Element(i);
Assert( pPlayer );
if ( !pPlayer )
continue;
if ( bRequireSwitcheesToBeDead == false || !pPlayer->IsAlive() )
{
// We're trying to avoid picking a player that's recently
// been auto-balanced by delaying their selection in the hope
// that a better candidate comes along.
if ( bRequireSwitcheesToBeDead )
{
int nPlayerTeamBalanceScore = pPlayer->CalculateTeamBalanceScore();
// Do we already have someone in the queue?
if ( m_nAutoBalanceQueuePlayerIndex > 0 )
{
// Is this player's score worse?
if ( nPlayerTeamBalanceScore < m_nAutoBalanceQueuePlayerScore )
{
m_nAutoBalanceQueuePlayerIndex = pPlayer->entindex();
m_nAutoBalanceQueuePlayerScore = nPlayerTeamBalanceScore;
}
}
// Has this person been switched recently?
else if ( nPlayerTeamBalanceScore < -10000 )
{
// Put them in the queue
m_nAutoBalanceQueuePlayerIndex = pPlayer->entindex();
m_nAutoBalanceQueuePlayerScore = nPlayerTeamBalanceScore;
m_flAutoBalanceQueueTimeEnd = gpGlobals->curtime + 3.0f;
continue;
}
// If this is the player in the queue...
if ( m_nAutoBalanceQueuePlayerIndex == pPlayer->entindex() )
{
// Pass until their timer is up
if ( m_flAutoBalanceQueueTimeEnd > gpGlobals->curtime )
continue;
}
}
pPlayer->ChangeTeam( iLightestTeam );
pPlayer->SetLastForcedChangeTeamTimeToNow();
m_nAutoBalanceQueuePlayerScore = -1;
m_nAutoBalanceQueuePlayerIndex = -1;
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_teambalanced_player" );
if ( event )
{
event->SetInt( "player", pPlayer->entindex() );
event->SetInt( "team", iLightestTeam );
gameeventmanager->FireEvent( event );
}
// tell people that we've switched this player
UTIL_ClientPrintAll( HUD_PRINTTALK, "#game_player_was_team_balanced", pPlayer->GetPlayerName() );
iNumSwitchesRequired--;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::ResetScores( void )
{
if ( m_bResetTeamScores )
{
for ( int i = 0; i < GetNumberOfTeams(); i++ )
{
GetGlobalTeam( i )->ResetScores();
}
}
if ( m_bResetPlayerScores )
{
CBasePlayer *pPlayer;
for( int i = 1; i <= gpGlobals->maxClients; i++ )
{
pPlayer = ToBasePlayer( UTIL_PlayerByIndex( i ) );
if (pPlayer == NULL)
continue;
if (FNullEnt( pPlayer->edict() ))
continue;
pPlayer->ResetScores();
}
}
if ( m_bResetRoundsPlayed )
{
m_nRoundsPlayed = 0;
}
// assume we always want to reset the scores
// unless someone tells us not to for the next reset
m_bResetTeamScores = true;
m_bResetPlayerScores = true;
m_bResetRoundsPlayed = true;
//m_flStopWatchTime = -1.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::ResetMapTime( void )
{
m_flMapResetTime = gpGlobals->curtime;
// send an event with the time remaining until map change
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_map_time_remaining" );
if ( event )
{
event->SetInt( "seconds", GetTimeLeft() );
gameeventmanager->FireEvent( event );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::PlayStartRoundVoice( void )
{
for ( int i = LAST_SHARED_TEAM+1; i < GetNumberOfTeams(); i++ )
{
BroadcastSound( i, UTIL_VarArgs("Game.TeamRoundStart%d", i ) );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::PlayWinSong( int team )
{
if ( team == TEAM_UNASSIGNED )
{
PlayStalemateSong();
}
else
{
#if defined (TF_DLL) || defined (TF_CLIENT_DLL)
if ( TFGameRules() && TFGameRules()->IsPlayingSpecialDeliveryMode() )
return;
#endif // TF_DLL
BroadcastSound( TEAM_UNASSIGNED, UTIL_VarArgs("Game.TeamWin%d", team ) );
for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ )
{
if ( i == team )
{
BroadcastSound( i, WinSongName( i ) );
}
else
{
const char *pchLoseSong = LoseSongName( i );
if ( pchLoseSong )
{
BroadcastSound( i, pchLoseSong );
}
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::PlaySuddenDeathSong( void )
{
BroadcastSound( TEAM_UNASSIGNED, "Game.SuddenDeath" );
for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ )
{
BroadcastSound( i, "Game.SuddenDeath" );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::PlayStalemateSong( void )
{
BroadcastSound( TEAM_UNASSIGNED, GetStalemateSong( TEAM_UNASSIGNED ) );
for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ )
{
BroadcastSound( i, GetStalemateSong( i ) );
}
}
bool CTeamplayRoundBasedRules::PlayThrottledAlert( int iTeam, const char *sound, float fDelayBeforeNext )
{
if ( m_flNewThrottledAlertTime <= gpGlobals->curtime )
{
BroadcastSound( iTeam, sound );
m_flNewThrottledAlertTime = gpGlobals->curtime + fDelayBeforeNext;
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::BroadcastSound( int iTeam, const char *sound, int iAdditionalSoundFlags )
{
//send it to everyone
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_broadcast_audio" );
if ( event )
{
event->SetInt( "team", iTeam );
event->SetString( "sound", sound );
event->SetInt( "additional_flags", iAdditionalSoundFlags );
gameeventmanager->FireEvent( event );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::AddPlayedRound( string_t strName )
{
if ( strName != NULL_STRING )
{
m_iszPreviousRounds.AddToHead( strName );
// we only need to store the last two rounds that we've played
if ( m_iszPreviousRounds.Count() > 2 )
{
// remove all but two of the entries (should only ever have to remove 1 when we're at 3)
for ( int i = m_iszPreviousRounds.Count() - 1 ; i > 1 ; i-- )
{
m_iszPreviousRounds.Remove( i );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::IsPreviouslyPlayedRound( string_t strName )
{
return ( m_iszPreviousRounds.Find( strName ) != m_iszPreviousRounds.InvalidIndex() );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
string_t CTeamplayRoundBasedRules::GetLastPlayedRound( void )
{
return ( m_iszPreviousRounds.Count() ? m_iszPreviousRounds[0] : NULL_STRING );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTeamRoundTimer *CTeamplayRoundBasedRules::GetActiveRoundTimer( void )
{
#ifdef TF_DLL
int iTimerEntIndex = ObjectiveResource()->GetTimerInHUD();
return ( dynamic_cast<CTeamRoundTimer *>( UTIL_EntityByIndex( iTimerEntIndex ) ) );
#else
return NULL;
#endif
}
#endif // GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: How long are the respawn waves for this team currently?
//-----------------------------------------------------------------------------
float CTeamplayRoundBasedRules::GetRespawnWaveMaxLength( int iTeam, bool bScaleWithNumPlayers /* = true */ )
{
if ( State_Get() != GR_STATE_RND_RUNNING )
return 0;
if ( mp_disable_respawn_times.GetBool() == true )
return 0.0f;
//Let's just turn off respawn times while players are messing around waiting for the tournament to start
if ( IsInTournamentMode() == true && IsInPreMatch() == true )
return 0.0f;
float flTime = ( ( m_TeamRespawnWaveTimes[iTeam] >= 0 ) ? m_TeamRespawnWaveTimes[iTeam] : mp_respawnwavetime.GetFloat() );
// For long respawn times, scale the time as the number of players drops
if ( bScaleWithNumPlayers && flTime > 5 )
{
flTime = MAX( 5, flTime * GetRespawnTimeScalar(iTeam) );
}
return flTime;
}
//-----------------------------------------------------------------------------
// Purpose: returns true if we are running tournament mode
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::IsInTournamentMode( void )
{
return mp_tournament.GetBool();
}
//-----------------------------------------------------------------------------
// Purpose: returns true if we are running highlander mode
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::IsInHighlanderMode( void )
{
#if defined( TF_CLIENT_DLL ) || defined( TF_DLL )
// can't use highlander mode and the queue system
if ( IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
return false;
return mp_highlander.GetBool();
#else
return false;
#endif
}
int CTeamplayRoundBasedRules::GetBonusRoundTime( void )
{
return MAX( 5, mp_bonusroundtime.GetFloat() );
}
//-----------------------------------------------------------------------------
// Purpose: returns true if we should even bother to do balancing stuff
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::ShouldBalanceTeams( void )
{
if ( IsInTournamentMode() == true )
return false;
if ( IsInTraining() == true || IsInItemTestingMode() )
return false;
if ( mp_teams_unbalance_limit.GetInt() <= 0 )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: returns true if the passed team change would cause unbalanced teams
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::WouldChangeUnbalanceTeams( int iNewTeam, int iCurrentTeam )
{
// players are allowed to change to their own team
if( iNewTeam == iCurrentTeam )
return false;
// if mp_teams_unbalance_limit is 0, don't check
if ( ShouldBalanceTeams() == false )
return false;
// if they are joining a non-playing team, allow
if ( iNewTeam < FIRST_GAME_TEAM )
return false;
CTeam *pNewTeam = GetGlobalTeam( iNewTeam );
if ( !pNewTeam )
{
Assert( 0 );
return true;
}
// add one because we're joining this team
int iNewTeamPlayers = pNewTeam->GetNumPlayers() + 1;
// for each game team
int i = FIRST_GAME_TEAM;
CTeam *pTeam;
for ( pTeam = GetGlobalTeam(i); pTeam != NULL; pTeam = GetGlobalTeam(++i) )
{
if ( pTeam == pNewTeam )
continue;
int iNumPlayers = pTeam->GetNumPlayers();
if ( i == iCurrentTeam )
{
iNumPlayers = MAX( 0, iNumPlayers-1 );
}
if ( ( iNewTeamPlayers - iNumPlayers ) > mp_teams_unbalance_limit.GetInt() )
{
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTeamplayRoundBasedRules::AreTeamsUnbalanced( int &iHeaviestTeam, int &iLightestTeam )
{
if ( IsInArenaMode() == false || (IsInArenaMode() && tf_arena_use_queue.GetBool() == false) )
{
if ( ShouldBalanceTeams() == false )
{
return false;
}
}
#ifndef CLIENT_DLL
if ( IsInCommentaryMode() )
return false;
#endif
int iMostPlayers = 0;
int iLeastPlayers = MAX_PLAYERS + 1;
int i = FIRST_GAME_TEAM;
for ( CTeam *pTeam = GetGlobalTeam(i); pTeam != NULL; pTeam = GetGlobalTeam(++i) )
{
int iNumPlayers = pTeam->GetNumPlayers();
if ( iNumPlayers < iLeastPlayers )
{
iLeastPlayers = iNumPlayers;
iLightestTeam = i;
}
if ( iNumPlayers > iMostPlayers )
{
iMostPlayers = iNumPlayers;
iHeaviestTeam = i;
}
}
if ( IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
{
if ( iMostPlayers == 0 && iMostPlayers == iLeastPlayers )
return true;
if ( iMostPlayers != iLeastPlayers )
return true;
return false;
}
if ( ( iMostPlayers - iLeastPlayers ) > mp_teams_unbalance_limit.GetInt() )
{
return true;
}
return false;
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::SetRoundState( int iRoundState )
{
m_iRoundState = iRoundState;
m_flLastRoundStateChangeTime = gpGlobals->curtime;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::OnPreDataChanged( DataUpdateType_t updateType )
{
m_bOldInWaitingForPlayers = m_bInWaitingForPlayers;
m_bOldInOvertime = m_bInOvertime;
m_bOldInSetup = m_bInSetup;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::OnDataChanged( DataUpdateType_t updateType )
{
if ( updateType == DATA_UPDATE_CREATED ||
m_bOldInWaitingForPlayers != m_bInWaitingForPlayers ||
m_bOldInOvertime != m_bInOvertime ||
m_bOldInSetup != m_bInSetup )
{
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_update_timer" );
if ( event )
{
gameeventmanager->FireEventClientSide( event );
}
}
if ( updateType == DATA_UPDATE_CREATED )
{
if ( State_Get() == GR_STATE_STALEMATE )
{
IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_round_stalemate" );
if ( event )
{
event->SetInt( "reason", STALEMATE_JOIN_MID );
gameeventmanager->FireEventClientSide( event );
}
}
}
if ( m_bInOvertime && ( m_bOldInOvertime != m_bInOvertime ) )
{
HandleOvertimeBegin();
}
}
#endif // CLIENT_DLL
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::ResetTeamsRoundWinTracking( void )
{
if ( m_GameTeams.Count() != 2 )
return;
m_GameTeams[0] = 0;
m_GameTeams[1] = 0;
}
#endif // GAME_DLL
#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
//-----------------------------------------------------------------------------
// Purpose: Are you now, or are you ever going to be, a member of the defending party?
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::GetMvMPotentialDefendersLobbyPlayerInfo( CUtlVector<LobbyPlayerInfo_t> &vecMvMDefenders, bool bIncludeBots /*= false*/ )
{
GetAllPlayersLobbyInfo( vecMvMDefenders, bIncludeBots );
// Now scan through and remove the spectators
for (int i = vecMvMDefenders.Count() - 1 ; i >= 0 ; --i )
{
switch ( vecMvMDefenders[i].m_iTeam )
{
case TEAM_UNASSIGNED:
case TF_TEAM_PVE_DEFENDERS:
break;
default:
AssertMsg1( false, "Bogus team %d", vecMvMDefenders[i].m_iTeam );
case TF_TEAM_PVE_INVADERS:
case TEAM_SPECTATOR:
vecMvMDefenders.FastRemove( i );
break;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTeamplayRoundBasedRules::GetAllPlayersLobbyInfo( CUtlVector<LobbyPlayerInfo_t> &vecPlayers, bool bIncludeBots )
{
vecPlayers.RemoveAll();
// Locate the lobby
CTFLobby *pLobby = GTFGCClientSystem()->GetLobby();
if ( pLobby )
{
for ( int i = 0 ; i < pLobby->GetNumMembers() ; ++i )
{
LobbyPlayerInfo_t &mbr = vecPlayers[vecPlayers.AddToTail()];
mbr.m_nEntNum = 0; // assume he isn't in the game yet
mbr.m_sPlayerName = pLobby->GetMemberDetails( i )->name().c_str();
mbr.m_steamID = pLobby->GetMember( i );
mbr.m_iTeam = TEAM_UNASSIGNED;
mbr.m_bConnected = false;
mbr.m_bBot = false;
mbr.m_bInLobby = true;
mbr.m_bSquadSurplus = pLobby->GetMemberDetails( i )->squad_surplus();
}
}
// Scan all players
for ( int i = 1; i <= MAX_PLAYERS; i++ )
{
// Locate the info for this player, depending on whether
// we're on the server or client
#ifdef CLIENT_DLL
player_info_t pi;
if ( !engine->GetPlayerInfo( i, &pi ) )
continue;
if ( pi.ishltv || pi.isreplay )
continue;
bool bBot = pi.fakeplayer;
#else
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
if ( pPlayer->IsHLTV() || pPlayer->IsReplay() )
continue;
bool bBot = pPlayer->IsBot();
#endif
// Discard bots?
if ( bBot && !bIncludeBots )
continue;
// See if we already found him in the lobby
CSteamID steamID = GetSteamIDForPlayerIndex( i );
#ifdef GAME_DLL
CSteamID steamID2;
if ( pPlayer->GetSteamID( &steamID2 ) )
{
Assert( steamID == steamID2 );
}
#endif
LobbyPlayerInfo_t *mbr = NULL;
if ( steamID.IsValid() )
{
for ( int j = 0 ; j < vecPlayers.Count() ; ++j )
{
if ( vecPlayers[j].m_steamID == steamID )
{
Assert( mbr == NULL );
mbr = &vecPlayers[j];
#ifndef _DEBUG
break; // in debug, keep looking so the assert above can fire
#endif
}
}
}
// Create a new entry for him if we didn't already find one
if ( mbr == NULL )
{
mbr = &vecPlayers[vecPlayers.AddToTail()];
mbr->m_bInLobby = false;
mbr->m_steamID = steamID;
mbr->m_bSquadSurplus = false;
}
// Fill in the rest of the info
mbr->m_bBot = bBot;
mbr->m_nEntNum = i;
#ifdef CLIENT_DLL
mbr->m_sPlayerName = g_PR->GetPlayerName( i );
mbr->m_iTeam = g_PR->GetTeam( i );
mbr->m_bConnected = g_PR->IsConnected( i );
#else
mbr->m_sPlayerName = pPlayer->GetPlayerName();
mbr->m_iTeam = pPlayer->GetTeamNumber();
mbr->m_bConnected = pPlayer->IsConnected();
#endif
}
}
#endif // #if defined(TF_CLIENT_DLL) || defined(TF_DLL)