You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3577 lines
103 KiB
3577 lines
103 KiB
//========= 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)
|
|
|