Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.

2735 lines
80 KiB

5 years ago
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_populator.cpp
// KeyValues driven procedural population system
// Michael Booth, April 2011
#include "cbase.h"
#include "tf_population_manager.h"
#include "tf_team.h"
#include "tf_mann_vs_machine_stats.h"
#include "tf_shareddefs.h"
#include "filesystem.h"
#include "tf_obj_sentrygun.h"
#include "tf_objective_resource.h"
#include "econ_entity_creation.h"
#include "econ_wearable.h"
#include "tf_upgrades.h"
#include "tf_item_powerup_bottle.h"
#include "tf_gc_server.h"
#include "vote_controller.h"
#include "tf_gamestats.h"
#include "tf_gamerules.h"
#include "econ_item_schema.h"
#include "tf_upgrades_shared.h"
#include "etwprof.h"
extern ConVar tf_mvm_skill;
extern ConVar tf_mm_trusted;
extern ConVar tf_mvm_respec_limit;
extern ConVar tf_mvm_respec_credit_goal;
extern ConVar tf_mvm_buybacks_method;
extern ConVar tf_mvm_buybacks_per_wave;
void MvMMissionCycleFileChangedCallback( IConVar *var, const char *pOldString, float flOldValue )
{
if ( g_pPopulationManager )
{
g_pPopulationManager->LoadMissionCycleFile();
}
}
ConVar tf_mvm_missioncyclefile( "tf_mvm_missioncyclefile", "tf_mvm_missioncycle.res", FCVAR_NONE, "Name of the .res file used to cycle mvm misisons", MvMMissionCycleFileChangedCallback );
ConVar tf_populator_debug( "tf_populator_debug", "0", TF_MVM_FCVAR_CHEAT );
ConVar tf_populator_active_buffer_range( "tf_populator_active_buffer_range", "3000", FCVAR_CHEAT, "Populate the world this far ahead of lead raider, and this far behind last raider" );
ConVar tf_mvm_default_sentry_buster_damage_dealt_threshold( "tf_mvm_default_sentry_buster_damage_dealt_threshold", "3000", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_mvm_default_sentry_buster_kill_threshold( "tf_mvm_default_sentry_buster_kill_threshold", "15", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
#ifdef STAGING_ONLY
ConVar tf_mvm_mm_bonus( "tf_mvm_mm_bonus", "0.2" );
#endif // STAGING_ONLY
void MinibossScaleChangedCallBack( IConVar *pVar, const char *pOldString, float flOldValue )
{
ConVarRef cVarRef( pVar );
// Change the scale of all the minibosses
for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
{
CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
if ( pPlayer && pPlayer->IsMiniBoss() )
{
pPlayer->SetModelScale( cVarRef.GetFloat(), 1.0f );
}
}
}
ConVar tf_mvm_miniboss_scale( "tf_mvm_miniboss_scale", "1.75", FCVAR_REPLICATED | TF_MVM_FCVAR_CHEAT, "Full body scale for minibosses.", MinibossScaleChangedCallBack );
ConVar tf_mvm_disconnect_on_victory( "tf_mvm_disconnect_on_victory", "0", FCVAR_REPLICATED, "Enable to Disconnect Players after completing MvM" );
ConVar tf_mvm_victory_reset_time( "tf_mvm_victory_reset_time", "60.0", FCVAR_REPLICATED, "Seconds to wait after MvM victory before cycling to the next mission. (Only used if tf_mvm_disconnect_on_victory is false.)" );
ConVar tf_mvm_victory_disconnect_time( "tf_mvm_victory_disconnect_time", "180.0", FCVAR_REPLICATED, "Seconds to wait after MvM victory before kicking players. (Only used if tf_mvm_disconnect_on_victory is true.)" );
ConVar tf_mvm_endless_force_on( "tf_mvm_endless_force_on", "0", FCVAR_DONTRECORD | FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Force MvM Endless mode on" );
ConVar tf_mvm_endless_wait_time( "tf_mvm_endless_wait_time", "5.0f", FCVAR_DONTRECORD | FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY);
ConVar tf_mvm_endless_bomb_reset( "tf_mvm_endless_bomb_reset", "5", FCVAR_DONTRECORD | FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Number of Waves to Complete before bomb reset" );
ConVar tf_mvm_endless_bot_cash( "tf_mvm_endless_bot_cash", "120", FCVAR_DONTRECORD | FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "In Endless, number of credits bots get per wave" );
ConVar tf_mvm_endless_tank_boost( "tf_mvm_endless_tank_boost", "0.2", FCVAR_DONTRECORD | FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "In Endless, amount of extra health for the tank per wave" );
ConVar tf_populator_health_multiplier( "tf_populator_health_multiplier", "1.0", FCVAR_DONTRECORD | FCVAR_REPLICATED | FCVAR_CHEAT );
ConVar tf_populator_damage_multiplier( "tf_populator_damage_multiplier", "1.0", FCVAR_DONTRECORD | FCVAR_REPLICATED | FCVAR_CHEAT );
static bool HaveMap( const char *pszMapName )
{
char szCanonName[64] = { 0 };
V_strncpy( szCanonName, pszMapName, sizeof( szCanonName ) );
IVEngineServer::eFindMapResult eResult = engine->FindMap( szCanonName, sizeof( szCanonName ) );
switch ( eResult )
{
case IVEngineServer::eFindMap_Found:
case IVEngineServer::eFindMap_NonCanonical:
return true;
case IVEngineServer::eFindMap_NotFound:
case IVEngineServer::eFindMap_FuzzyMatch:
// Maps that are contingent on just-in-time preparation should probably not be baked into cycle files... yet?
case IVEngineServer::eFindMap_PossiblyAvailable:
return false;
}
AssertMsg( false, "Unhandled engine->FindMap return value\n" );
return false;
}
void MVMSkillChangedCallback( IConVar *pVar, const char *pOldString, float flOldValue )
{
ConVarRef cVarRef( pVar );
// Testing the effects of skill setting
float flHealth = 1.f; // Health modifier for bots
float flDamage = 1.f; // Damage modifier in bot vs player
switch ( cVarRef.GetInt() )
{
case 1:
{
flHealth = 0.75f;
flDamage = 0.75f;
break;
}
case 2:
{
flHealth = 0.9f;
flDamage = 0.9f;
break;
}
// "Normal"
case 3:
{
flHealth = 1.f;
flDamage = 1.f;
break;
}
case 4:
{
flHealth = 1.10f;
flDamage = 1.10f;
break;
}
case 5:
{
flHealth = 1.25f;
flDamage = 1.25f;
break;
}
}
tf_populator_health_multiplier.SetValue( flHealth );
tf_populator_damage_multiplier.SetValue( flDamage );
}
ConVar tf_mvm_skill( "tf_mvm_skill", "3", FCVAR_DONTRECORD | FCVAR_REPLICATED | TF_MVM_FCVAR_CHEAT, "Sets the challenge level of the invading bot army. 1 = easiest, 3 = normal, 5 = hardest", true, 1, true, 5, MVMSkillChangedCallback );
//-------------------------------------------------------------------------
// Console command to cheat and force victory. (To test econ work, etc)
CON_COMMAND_F( tf_mvm_nextmission, "Load the next mission", FCVAR_CHEAT )
{
if ( g_pPopulationManager )
{
g_pPopulationManager->CycleMission();
}
}
// Console command to cheat and force victory. (To test econ work, etc)
CON_COMMAND_F( tf_mvm_force_victory, "Force immediate victory.", FCVAR_CHEAT )
{
if ( g_pPopulationManager )
{
g_pPopulationManager->JumpToWave( g_pPopulationManager->GetTotalWaveCount() - 1 );
g_pPopulationManager->WaveEnd( true );
g_pPopulationManager->MvMVictory();
}
}
//-------------------------------------------------------------------------
CON_COMMAND_F( tf_mvm_checkpoint, "Save a checkpoint snapshot", FCVAR_CHEAT )
{
if ( g_pPopulationManager )
{
g_pPopulationManager->SetCheckpoint( -1 );
}
}
//-------------------------------------------------------------------------
CON_COMMAND_F( tf_mvm_checkpoint_clear, "Clear the saved checkpoint", FCVAR_CHEAT )
{
if ( g_pPopulationManager )
{
g_pPopulationManager->ClearCheckpoint();
}
}
//-------------------------------------------------------------------------
CON_COMMAND_F( tf_mvm_jump_to_wave, "Jumps directly to the given Mann Vs Machine wave number", FCVAR_CHEAT )
{
if ( args.ArgC() <= 1 )
{
Msg( "Missing wave number\n" );
return;
}
float fCleanMoneyPercent = -1.0f;
if ( args.ArgC() >= 3 )
{
fCleanMoneyPercent = atof( args.Arg(2) );
}
// find the population manager
CPopulationManager *manager = (CPopulationManager *)gEntList.FindEntityByClassname( NULL, "info_populator" );
if ( !manager )
{
Msg( "No Population Manager found in the map\n" );
return;
}
uint32 desiredWave = (uint32)Max( atoi( args.Arg(1) ) - 1, 0) ;
manager->JumpToWave( desiredWave, fCleanMoneyPercent );
}
//-------------------------------------------------------------------------
CON_COMMAND_F( tf_mvm_debugstats, "Dumpout MvM Data", FCVAR_CHEAT )
{
if ( !UTIL_IsCommandIssuedByServerAdmin() )
return;
if ( g_pPopulationManager )
{
g_pPopulationManager->DebugWaveStats();
}
}
//-------------------------------------------------------------------------
// CPopulationManager
//-------------------------------------------------------------------------
BEGIN_DATADESC( CPopulationManager )
DEFINE_THINKFUNC( Update ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( info_populator, CPopulationManager );
PRECACHE_REGISTER( info_populator );
CPopulationManager *g_pPopulationManager = NULL;
// initialized to zero (1st wave), and not reset unless game won or map change event received.
int CPopulationManager::m_checkpointWaveIndex = 0;
CUtlVector< CPopulationManager::CheckpointSnapshotInfo * > CPopulationManager::m_checkpointSnapshot;
int CPopulationManager::m_nNumConsecutiveWipes = 0;
// Mission Cycle Vars
static int s_iLastKnownMissionCategory = 1;
static int s_iLastKnownMission = 1;
//-------------------------------------------------------------------------
// CPopulationManager
//-------------------------------------------------------------------------
CPopulationManager::CPopulationManager( void )
{
m_bIsInitialized = false;
m_bAllocatedBots = false;
m_popfileFull[ 0 ] = '\0';
m_popfileShort[ 0 ] = '\0';
m_nStartingCurrency = 0;
m_nLobbyBonusCurrency = 0;
m_canBotsAttackWhileInSpawnRoom = true;
m_pTemplates = NULL;
m_isRestoringCheckpoint = false;
m_nRespawnWaveTime = 10;
m_bFixedRespawnWaveTime = false;
m_sentryBusterDamageDealtThreshold = tf_mvm_default_sentry_buster_damage_dealt_threshold.GetInt();
m_sentryBusterKillThreshold = tf_mvm_default_sentry_buster_kill_threshold.GetInt();
m_bCheckForCurrencyAchievement = true;
m_bEndlessOn = false;
m_bIsWaveJumping = false;
m_bSpawningPaused = false;
m_iCurrentWaveIndex = 0;
m_nNumConsecutiveWipes = 0;
m_nMvMEventPopfileType = MVM_EVENT_POPFILE_NONE;
SetThink( &CPopulationManager::Update );
SetNextThink( gpGlobals->curtime );
g_pPopulationManager = this;
m_pMVMStats = MannVsMachineStats_GetInstance();
m_pKvpMvMMapCycle = NULL;
ListenForGameEvent( "pve_win_panel" );
// Endless
m_randomizer.SetSeed( 0 );
m_EndlessSeeds.Purge();
for ( int i = 0; i < 27; i++ )
{
m_EndlessSeeds.AddToTail( m_randomizer.RandomInt( 0, INT_MAX ) );
}
EndlessParseBotUpgrades();
m_bShouldResetFlag = false;
m_bBonusRound = false;
m_hBonusBoss = NULL;
m_nRespecsAwarded = 0;
m_nRespecsAwardedInWave = 0;
m_nCurrencyCollectedForRespec = 0;
m_PlayerRespecPoints.SetLessFunc( DefLessFunc (uint64) );
m_PlayerRespecPoints.EnsureCapacity( MAX_PLAYERS );
m_PlayerBuybackPoints.SetLessFunc( DefLessFunc( uint64 ) );
m_PlayerBuybackPoints.EnsureCapacity( MAX_PLAYERS );
}
//-------------------------------------------------------------------------
CPopulationManager::~CPopulationManager()
{
Reset();
m_populatorVector.PurgeAndDeleteElements();
m_waveVector.RemoveAll();
if ( m_pTemplates )
{
m_pTemplates->deleteThis();
m_pTemplates = NULL;
}
g_pPopulationManager = NULL;
}
//-------------------------------------------------------------------------
// Purpose : CPointEntity Override
//-------------------------------------------------------------------------
void CPopulationManager::Spawn( void )
{
BaseClass::Spawn();
Initialize();
}
//-------------------------------------------------------------------------
// Purpose : CGameEventListener
//-------------------------------------------------------------------------
void CPopulationManager::FireGameEvent( IGameEvent *event )
{
const char *pEventName = event->GetName();
if ( V_strcmp( "pve_win_panel", pEventName ) == 0 )
{
// Always release people even if the match isn't ending
// XXX(JohnS): This is just how the code was, but why wouldn't the match be ending?
MarkAllCurrentPlayersSafeToLeave();
}
}
//-------------------------------------------------------------------------
// Purpose :
//-------------------------------------------------------------------------
void CPopulationManager::PlayerDoneViewingLoot( const CTFPlayer* pPlayer )
{
CUtlVector< const CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_PVE_DEFENDERS );
if ( m_donePlayers.Find( pPlayer ) == m_donePlayers.InvalidIndex()
&& playerVector.Find( pPlayer ) != playerVector.InvalidIndex() )
{
m_donePlayers.AddToTail( pPlayer );
float flTimeRemaining = m_flMapRestartTime - gpGlobals->curtime;
const float flMinTime = 15.f;
if ( flTimeRemaining > flMinTime )
{
// Figure out if this is restart or kick to lobby time
float flReduceTimeBy = ( tf_mm_trusted.GetBool() == true || tf_mvm_disconnect_on_victory.GetBool() == true )
? tf_mvm_victory_disconnect_time.GetFloat()
: tf_mvm_victory_reset_time.GetFloat();
// Each player can reduce the clock by a certain amount, based on how
// many players there are
flReduceTimeBy = ( flReduceTimeBy * 0.8 ) / playerVector.Count();
flTimeRemaining -= flReduceTimeBy ;
flTimeRemaining = Max( flTimeRemaining, flMinTime );
m_flMapRestartTime = gpGlobals->curtime + flTimeRemaining;
// Notify Users of new remaining time
CBroadcastRecipientFilter filter;
filter.MakeReliable();
UserMessageBegin( filter, "MVMServerKickTimeUpdate" );
WRITE_BYTE((uint8)flTimeRemaining);
MessageEnd();
}
}
}
//-------------------------------------------------------------------------
// Purpose : Full Clear of Population Manager State and Data
//-------------------------------------------------------------------------
void CPopulationManager::Reset( void )
{
m_nStartingCurrency = 0;
m_canBotsAttackWhileInSpawnRoom = true;
m_nRespawnWaveTime = 10;
m_bFixedRespawnWaveTime = false;
m_sentryBusterDamageDealtThreshold = tf_mvm_default_sentry_buster_damage_dealt_threshold.GetInt();
m_sentryBusterKillThreshold = tf_mvm_default_sentry_buster_kill_threshold.GetInt();
m_bAdvancedPopFile = false;
m_nMvMEventPopfileType = MVM_EVENT_POPFILE_NONE;
m_bSpawningPaused = false;
m_donePlayers.Purge();
m_nRespecsAwardedInWave = 0;
// don't clobber this value if we're wave jumping
if ( !m_bIsWaveJumping )
{
m_iCurrentWaveIndex = 0;
}
m_defaultEventChangeAttributesName = "Default";
}
//-------------------------------------------------------------------------
// Purpose : Restart Population Manager at Current Wave
//-------------------------------------------------------------------------
bool CPopulationManager::Initialize( void )
{
if ( ( TheNavMesh == NULL ) || ( TheNavMesh->GetNavAreaCount() <= 0 ) )
{
Warning( "No Nav Mesh CPopulationManager::Initialize for %s", m_popfileFull );
return false;
}
Reset();
if ( !Parse() )
{
Warning( "Parse Failed in CPopulationManager::Initialize for %s", m_popfileFull );
return false;
}
#ifdef STAGING_ONLY
// only calculate lobby bonus one time when the lobby first match to the server
CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
if ( pMatch && GetWaveNumber() == 0 && m_nLobbyBonusCurrency == 0 )
{
// Count unique parties
CUtlVector<uint64> vecPartyIDs;
int total = pMatch->GetNumTotalMatchPlayers();
for ( int idx = 0; idx < total; idx++ )
{
uint64 uPartyID = pMatch->GetMatchDataForPlayer( idx )->uPartyID;
if ( vecPartyIDs.Find( uPartyID ) == vecPartyIDs.InvalidIndex() )
{
vecPartyIDs.AddToTail( uPartyID );
}
}
int nUniqueParties = vecPartyIDs.Count();
// give some bonus currency for each extra unique party in the lobby
float flBonusScale = tf_mvm_mm_bonus.GetFloat() * ( nUniqueParties - 1 );
m_nLobbyBonusCurrency = flBonusScale * m_nStartingCurrency;
}
#endif // STAGING_ONLY
if ( TFGameRules()->State_Get() == GR_STATE_PREGAME )
{
// new game
ClearCheckpoint();
m_iCurrentWaveIndex = 0;
m_nNumConsecutiveWipes = 0;
m_pMVMStats->SetCurrentWave( m_iCurrentWaveIndex );
}
else
{
RestoreCheckpoint();
// Restore Check Point is being called on RoundStart so this check is currently needed
// Report loss to Stats
if ( m_bIsInitialized && ( m_iCurrentWaveIndex > 0 || m_nNumConsecutiveWipes > 1 ) )
{
m_pMVMStats->RoundEvent_WaveEnd( false );
}
IGameEvent *event = gameeventmanager->CreateEvent( "mvm_wave_failed" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
}
if ( IsInEndlessWaves() )
{
EndlessRollEscalation();
}
m_bIsInitialized = true;
UpdateObjectiveResource();
DebugWaveStats();
PostInitialize();
// Space respecs based on the number allowed
if ( tf_mvm_respec_limit.GetBool() )
{
int nAmount = tf_mvm_respec_limit.GetInt() + 1;
tf_mvm_respec_credit_goal.SetValue( GetTotalPopFileCurrency() / nAmount );
}
m_nRespecsAwardedInWave = 0;
return true;
}
//-------------------------------------------------------------------------
// Purpose : Precache data for PopulationManager, typically sounds
//-------------------------------------------------------------------------
void CPopulationManager::Precache( void )
{
PrecacheScriptSound( "music.mvm_end_wave" );
PrecacheScriptSound( "music.mvm_end_tank_wave" );
PrecacheScriptSound( "music.mvm_end_mid_wave" );
PrecacheScriptSound( "music.mvm_end_last_wave" );
PrecacheScriptSound( "MVM.PlayerUpgraded" );
PrecacheScriptSound( "MVM.PlayerBoughtIn" );
PrecacheScriptSound( "MVM.PlayerUsedPowerup" );
PrecacheScriptSound( "MVM.PlayerDied" );
PrecacheScriptSound( "MVM.PlayerDiedScout" );
PrecacheScriptSound( "MVM.PlayerDiedSniper" );
PrecacheScriptSound( "MVM.PlayerDiedSoldier" );
PrecacheScriptSound( "MVM.PlayerDiedDemoman" );
PrecacheScriptSound( "MVM.PlayerDiedMedic" );
PrecacheScriptSound( "MVM.PlayerDiedHeavy" );
PrecacheScriptSound( "MVM.PlayerDiedPyro" );
PrecacheScriptSound( "MVM.PlayerDiedSpy" );
PrecacheScriptSound( "MVM.PlayerDiedEngineer" );
BaseClass::Precache();
}
//-------------------------------------------------------------------------
bool CPopulationManager::FindPopulationFileByShortName( const char *pShortName, CUtlString &outFullName )
{
// Form full path
char szFullPath[MAX_PATH] = { 0 };
V_sprintf_safe( szFullPath, MVM_POP_FILE_PATH "/%s.pop", pShortName );
if ( g_pFullFileSystem->FileExists( szFullPath, "GAME" ) )
{
outFullName = szFullPath;
return true;
}
// Check mapname_shorthand.pop
V_sprintf_safe( szFullPath, MVM_POP_FILE_PATH "/%s_%s.pop", STRING( gpGlobals->mapname ), pShortName );
if ( g_pFullFileSystem->FileExists( szFullPath, "GAME" ) )
{
outFullName = szFullPath;
return true;
}
// If using special name "normal", check just scripts/population/mapname.pop as last resort
V_sprintf_safe( szFullPath, MVM_POP_FILE_PATH "/%s.pop", STRING( gpGlobals->mapname ) );
if ( g_pFullFileSystem->FileExists( szFullPath, "GAME" ) )
{
outFullName = szFullPath;
return true;
}
return false;
}
//-------------------------------------------------------------------------
void CPopulationManager::FindDefaultPopulationFileShortNames( CUtlVector< CUtlString > &outVecShortNames )
{
// Search for all loose pop files that are prefixed with the current map name
char szBaseName[MAX_PATH] = { 0 };
V_snprintf( szBaseName, sizeof( szBaseName ), MVM_POP_FILE_PATH "/%s*.pop", STRING(gpGlobals->mapname) );
FileFindHandle_t popHandle;
const char *pPopFileName = filesystem->FindFirstEx( szBaseName, "GAME", &popHandle );
while ( pPopFileName && pPopFileName[ 0 ] != '\0' )
{
// Skip it if it's a directory or is the folder info
if ( filesystem->FindIsDirectory( popHandle ) )
{
pPopFileName = filesystem->FindNext( popHandle );
continue;
}
const char *pchPopPostfix = StringAfterPrefix( pPopFileName, STRING(gpGlobals->mapname) );
if ( pchPopPostfix )
{
char szShortName[MAX_PATH] = { 0 };
V_strncpy( szShortName, ( ( pchPopPostfix[ 0 ] == '_' ) ? ( pchPopPostfix + 1 ) : "normal" ), sizeof( szShortName ) ); // skip the '_'
V_StripExtension( szShortName, szShortName, sizeof( szShortName ) );
if ( outVecShortNames.Find( szShortName ) == outVecShortNames.InvalidIndex() )
{
outVecShortNames.AddToTail( szShortName );
}
}
pPopFileName = filesystem->FindNext( popHandle );
}
filesystem->FindClose( popHandle );
// Search for all pop files in the BSP next. Note that loose files override these (by short name)
FileFindHandle_t popHandleBSP;
const char *pPopFileNameBSP = filesystem->FindFirstEx( MVM_POP_FILE_PATH "/*.pop", "BSP", &popHandleBSP );
while ( pPopFileNameBSP && pPopFileNameBSP[ 0 ] != '\0' )
{
// Skip it if it's a directory or is the folder info
if ( filesystem->FindIsDirectory( popHandleBSP ) )
{
pPopFileNameBSP = filesystem->FindNext( popHandleBSP );
continue;
}
char szShortName[MAX_PATH] = { 0 };
V_strncpy( szShortName, pPopFileNameBSP, sizeof( szShortName ) );
V_StripExtension( szShortName, szShortName, sizeof( szShortName ) );
// Legacy: Prior to proper support for in-BSP pop files, maps could jankily match their popfile to their exact
// map name in the BSP. Map this to "normal"
if ( V_stricmp( szShortName, STRING(gpGlobals->mapname) ) == 0 )
{
V_strncpy( szShortName, "normal", sizeof( szShortName ) );
}
if ( outVecShortNames.Find( szShortName ) == outVecShortNames.InvalidIndex() )
{
outVecShortNames.AddToTail( szShortName );
}
pPopFileNameBSP = filesystem->FindNext( popHandleBSP );
}
filesystem->FindClose( popHandleBSP );
// Always treat "normal" as the default pop-file
int normalIdx = outVecShortNames.Find( "normal" );
if ( normalIdx != outVecShortNames.InvalidIndex() && normalIdx != 0 )
{
outVecShortNames.Remove( normalIdx );
outVecShortNames.AddToHead( "normal" );
}
}
//-------------------------------------------------------------------------
const char *CPopulationManager::GetPopulationFilename( void )
{
return m_popfileFull;
}
//-------------------------------------------------------------------------
const char *CPopulationManager::GetPopulationFilenameShort( void )
{
return m_popfileShort;
}
//-------------------------------------------------------------------------
void CPopulationManager::SetPopulationFilename( const char *populationFile )
{
m_bIsInitialized = false;
V_strcpy_safe( m_popfileFull, populationFile );
V_FileBase( m_popfileFull, m_popfileShort, sizeof( m_popfileShort ) );
MannVsMachineStats_SetPopulationFile( m_popfileFull );
ResetMap();
if ( TFObjectiveResource() )
{
TFObjectiveResource()->SetMannVsMachineChallengeIndex( GetItemSchema()->FindMvmMissionByName( m_popfileFull ) );
TFObjectiveResource()->SetMvMPopfileName( MAKE_STRING( m_popfileFull ) );
}
}
//-------------------------------------------------------------------------
// Invoked when we are spawned at round (re)start
void CPopulationManager::SetupOnRoundStart( void )
{
Initialize();
}
//-------------------------------------------------------------------------
// Continuously invoked to modify population over time
//-------------------------------------------------------------------------
void CPopulationManager::Update( void )
{
VPROF_BUDGET( "CMissionPopulator::Update", "NextBot" );
SetNextThink( gpGlobals->curtime );
m_isRestoringCheckpoint = false;
// update populators
for( int i=0; i<m_populatorVector.Count(); ++i )
{
m_populatorVector[i]->Update();
}
// Update Current Wave
CWave * pWave = GetCurrentWave();
if ( pWave )
{
pWave->Update();
}
// Check for GAMEOVER for MapReset
if ( TFGameRules()->State_Get() == GR_STATE_GAME_OVER )
{
if ( m_flMapRestartTime < gpGlobals->curtime )
{
if ( tf_mvm_disconnect_on_victory.GetBool() )
{
// Shut down the managed match now, ask GC to return players to lobbies.
if ( !TFGameRules()->IsManagedMatchEnded() )
{
TFGameRules()->EndManagedMvMMatch( /* bKickPlayersToParties */ true );
}
}
else
{
CycleMission();
}
}
// If players haven't left via the GC returning them to parties by now, due to connection issues/GC down, etc,
// kick them with a thanks-for-playing.
if ( tf_mvm_disconnect_on_victory.GetBool() == true && m_flMapRestartTime + 5.0f < gpGlobals->curtime )
{
Log( "Kicking all players\n" );
engine->ServerCommand( "kickall #TF_PVE_Disconnect\n" );
CycleMission();
}
// See if the team got the bonus on every wave
if ( m_bCheckForCurrencyAchievement )
{
if ( ( MannVsMachineStats_GetDroppedCredits() > 0 ) && ( MannVsMachineStats_GetMissedCredits() == 0 ) )
{
const char *pszName = IsAdvancedPopFile() ? "mvm_creditbonus_all_advanced" : "mvm_creditbonus_all";
IGameEvent *event = gameeventmanager->CreateEvent( pszName );
if ( event )
{
gameeventmanager->FireEvent( event );
}
m_bCheckForCurrencyAchievement = false;
}
}
}
else if ( TFGameRules()->State_Get() == GR_STATE_STARTGAME )
{
AllocateBots();
}
}
//-------------------------------------------------------------------------
// Purpose: Invoked by the gamerules think to give us a chance to behave
// like a sub-gamerules-thing. This will always run when
// gamerules thinks, unlike Update which runs in the entity-update
// phase and doesn't run when we're hibernating etc..
//-------------------------------------------------------------------------
void CPopulationManager::GameRulesThink( void )
{
// If we reach zero players in managed match mode, drop the match (but otherwise just hang out in our current state,
// in bootcamp servers ad-hoc players may want to rejoin and keep playing/etc.., server hibernation will handle
// shutting down the game if desired)
CMatchInfo *pLiveMatch = GTFGCClientSystem()->GetLiveMatch();
if ( pLiveMatch && !TFGameRules()->IsManagedMatchEnded() && pLiveMatch->GetNumActiveMatchPlayers() == 0 )
{
Log( "No players remaining, ending managed MvM\n" );
TFGameRules()->EndManagedMvMMatch( /* bSendVictory */ false );
}
}
//-------------------------------------------------------------------------
// Purpose :
//-------------------------------------------------------------------------
void CPopulationManager::UpdateObjectiveResource( void )
{
if ( m_waveVector.Count() == 0 || !TFObjectiveResource() )
{
return;
}
TFObjectiveResource()->SetMannVsMachineEventPopfileType( m_nMvMEventPopfileType );
if ( IsInEndlessWaves() )
{
TFObjectiveResource()->SetMannVsMachineMaxWaveCount( 0 );
}
else
{
TFObjectiveResource()->SetMannVsMachineMaxWaveCount( m_waveVector.Count() );
}
TFObjectiveResource()->SetMannVsMachineWaveCount( m_iCurrentWaveIndex + 1 );
const CWave *wave = GetCurrentWave();
if ( wave )
{
TFObjectiveResource()->SetMannVsMachineWaveEnemyCount( wave->GetEnemyCount() );
TFObjectiveResource()->ClearMannVsMachineWaveClassFlags();
int i = 0;
bool bHasEngineer = false;
for ( i; i < MVM_CLASS_TYPES_PER_WAVE_MAX_NEW && i < wave->GetNumClassTypes(); ++i )
{
if ( !bHasEngineer )
{
const char* pszClassIconName = wave->GetClassIconName( i ).ToCStr();
bHasEngineer |= FStrEq( pszClassIconName, "engineer" );
}
TFObjectiveResource()->SetMannVsMachineWaveClassName( i, wave->GetClassIconName( i ) );
TFObjectiveResource()->SetMannVsMachineWaveClassCount( i, wave->GetClassCount( i ) );
TFObjectiveResource()->AddMannVsMachineWaveClassFlags( i, wave->GetClassFlags( i ) );
}
if ( bHasEngineer )
{
if ( i < MVM_CLASS_TYPES_PER_WAVE_MAX_NEW )
{
TFObjectiveResource()->SetMannVsMachineWaveClassName( i, TFObjectiveResource()->GetTeleporterString() );
TFObjectiveResource()->SetMannVsMachineWaveClassCount( i, 0 );
TFObjectiveResource()->AddMannVsMachineWaveClassFlags( i, MVM_CLASS_FLAG_MISSION ); // only mission will flash
i++;
}
else
{
AssertMsg( 0, "Failed to add teleporter icon to TFObjectiveResource" );
}
}
for ( i; i < MVM_CLASS_TYPES_PER_WAVE_MAX_NEW; ++i )
{
TFObjectiveResource()->SetMannVsMachineWaveClassCount( i, 0 );
TFObjectiveResource()->SetMannVsMachineWaveClassName( i, NULL_STRING );
TFObjectiveResource()->AddMannVsMachineWaveClassFlags( i, MVM_CLASS_FLAG_NONE );
}
}
}
//-------------------------------------------------------------------------
// Purpose : Reset Players, Stats, CheckPoint
//-------------------------------------------------------------------------
void CPopulationManager::ResetMap( void )
{
// Reset Scores
for( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = ToBasePlayer( UTIL_PlayerByIndex( i ) );
if ( !pPlayer )
continue;
if ( FNullEnt( pPlayer->edict() ) )
continue;
if ( pPlayer->GetTeamNumber() != TF_TEAM_PVE_DEFENDERS )
continue;
pPlayer->ResetScores();
}
// Reset Stats and go to wave 0 clean
m_pMVMStats->ResetStats( );
ResetRespecPoints();
ClearCheckpoint();
for ( int i = 1; i <= MAX_PLAYERS; ++i )
{
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
if ( !pTFPlayer )
continue;
if ( pTFPlayer->IsBot() )
continue;
pTFPlayer->ResetRefundableUpgrades();
}
JumpToWave( 0, 0 );
}
//-------------------------------------------------------------------------
void CPopulationManager::CycleMission ( void )
{
bool isLoaded = true;
if ( !m_pKvpMvMMapCycle )
{
isLoaded = LoadMissionCycleFile();
}
const char * pCurrentMap = STRING( gpGlobals->mapname );
char szCurrentPopfile[MAX_PATH];
V_FileBase( m_popfileFull, szCurrentPopfile, sizeof( szCurrentPopfile ) );
//engine->GetMap
if ( isLoaded )
{
int iMaxCat = m_pKvpMvMMapCycle->GetInt( "categories", 0 );
for ( int iCat = 1; iCat <= iMaxCat; iCat++ )
{
KeyValues *pCategory = m_pKvpMvMMapCycle->FindKey( UTIL_VarArgs( "%d", iCat ), false );
if ( pCategory )
{
int iMapCount = pCategory->GetInt( "count", 0 );
for ( int iMap = 1; iMap <= iMapCount; ++iMap )
{
KeyValues *pMission = pCategory->FindKey( UTIL_VarArgs( "%d", iMap ), false );
if ( pMission )
{
const char * pMap = pMission->GetString( "map", "" );
const char * pPopfile = pMission->GetString( "popfile", "" );
if ( !Q_strcmp( pCurrentMap, pMap ) && !Q_strcmp( szCurrentPopfile, pPopfile ) )
{
// match, advance to the next entry and use those values
int nextMap = (iMap % iMapCount) + 1;
KeyValues *pNextMission = pCategory->FindKey( UTIL_VarArgs( "%d", nextMap ), false );
if ( LoadMvMMission( pNextMission ) )
{
s_iLastKnownMission = nextMap;
s_iLastKnownMissionCategory = iCat;
return;
}
// Next map is invalid, load last known
LoadLastKnownMission();
return;
}
}
} // for ( int iMap = 1; iMap <= iMapCount; ++iMap )
}
} // for ( int iCat = 1; iCat <= iMaxCat; iCat++ )
}
// Unable to load mvm mapcycle, load last known
LoadLastKnownMission();
}
//-------------------------------------------------------------------------
bool CPopulationManager::LoadMissionCycleFile( void )
{
if ( m_pKvpMvMMapCycle )
{
m_pKvpMvMMapCycle->deleteThis();
}
m_pKvpMvMMapCycle = new KeyValues( tf_mvm_missioncyclefile.GetString() );
return m_pKvpMvMMapCycle->LoadFromFile( g_pFullFileSystem, tf_mvm_missioncyclefile.GetString(), "MOD" );
}
//-------------------------------------------------------------------------
void CPopulationManager::LoadLastKnownMission( void )
{
//
//
bool isLoaded = true;
if ( !m_pKvpMvMMapCycle )
{
isLoaded = LoadMissionCycleFile();
}
if ( !isLoaded )
{
ResetMap();
return;
}
// Grab the category
KeyValues *pCategory = m_pKvpMvMMapCycle->FindKey( UTIL_VarArgs( "%d", s_iLastKnownMissionCategory ), false );
if ( pCategory )
{
KeyValues *pNextMission = pCategory->FindKey( UTIL_VarArgs( "%d", s_iLastKnownMission ), false );
if ( LoadMvMMission( pNextMission ) )
{
return;
}
}
// Did not succeed
// Attempt to load the first Category / Mission instead
pCategory = m_pKvpMvMMapCycle->FindKey( UTIL_VarArgs( "%d", 1 ), false );
if ( pCategory )
{
KeyValues *pNextMission = pCategory->FindKey( UTIL_VarArgs( "%d", 1 ), false );
if ( LoadMvMMission( pNextMission ) )
{
s_iLastKnownMissionCategory = 1;
s_iLastKnownMission = 1;
return;
}
}
// if 1,1 does not exist (likely due to a bad .res file) just reset map instead
ResetMap();
}
//-------------------------------------------------------------------------
// Returns True if the mission was successfully found and loaded
//
bool CPopulationManager::LoadMvMMission ( KeyValues *pNextMission )
{
if ( !pNextMission )
return false;
const char * pNextMap = pNextMission->GetString( "map", NULL );
const char * pNextPopfile = pNextMission->GetString( "popfile", NULL );
if ( pNextMap && pNextPopfile )
{
char szPopFileName[MAX_PATH];
Q_snprintf( szPopFileName, sizeof( szPopFileName ), MVM_POP_FILE_PATH "/%s.pop", pNextPopfile );
if ( g_pFullFileSystem->FileExists( szPopFileName, "MOD" ) && HaveMap( pNextMap ) )
{
engine->ChangeLevel( pNextMap, NULL );
TFGameRules()->SetNextMvMPopfile( pNextPopfile );
return true;
}
}
return false;
}
//-------------------------------------------------------------------------
bool CPopulationManager::IsValidMvMMap( const char *pszMapName )
{
if ( !m_pKvpMvMMapCycle )
{
LoadMissionCycleFile();
}
if ( pszMapName && m_pKvpMvMMapCycle )
{
int iMaxCat = m_pKvpMvMMapCycle->GetInt( "categories", 0 );
for ( int iCat = 1; iCat <= iMaxCat; iCat++ )
{
KeyValues *pCategory = m_pKvpMvMMapCycle->FindKey( UTIL_VarArgs( "%d", iCat ), false );
if ( pCategory )
{
int iMapCount = pCategory->GetInt( "count", 0 );
for ( int iMap = 1; iMap <= iMapCount; ++iMap )
{
KeyValues *pMission = pCategory->FindKey( UTIL_VarArgs( "%d", iMap ), false );
if ( pMission )
{
const char *pszMap = pMission->GetString( "map", "" );
if ( Q_strcmp( pszMapName, pszMap ) == 0 )
{
// Valid?
return ( HaveMap( pszMapName ) ? true : false );
}
}
}
}
}
}
return false;
}
//-------------------------------------------------------------------------
void CPopulationManager::ShowNextWaveDescription( void )
{
UpdateObjectiveResource();
}
//-------------------------------------------------------------------------
#ifdef STAGING_ONLY
ConVar tf_mvm_bonus( "tf_mvm_bonus", "0" );
#endif
void CPopulationManager::StartCurrentWave( void )
{
if ( TFObjectiveResource() )
{
TFObjectiveResource()->SetMannVsMachineNextWaveTime( 0 );
TFObjectiveResource()->SetMannVsMachineBetweenWaves( false );
}
UpdateObjectiveResource();
m_pMVMStats->RoundEvent_WaveStart();
TFGameRules()->State_Transition( GR_STATE_RND_RUNNING );
#ifdef STAGING_ONLY
m_bBonusRound = tf_mvm_bonus.GetBool();
if ( m_bBonusRound )
{
Assert( m_hBonusBoss == NULL );
m_hBonusBoss = dynamic_cast< CBaseCombatCharacter * >( CreateEntityByName( "eyeball_boss" ) );
if ( m_hBonusBoss )
{
bool bFoundSpawnPoint = false;
CBaseEntity *spawnPoint = NULL;
while( ( spawnPoint = gEntList.FindEntityByClassname( spawnPoint, "info_target" ) ) != NULL )
{
if ( FStrEq( STRING( spawnPoint->GetEntityName() ), "spawn_boss_startpoint" ) )
{
bFoundSpawnPoint = true;
break;
}
}
if ( bFoundSpawnPoint )
{
m_hBonusBoss->SetAbsOrigin( spawnPoint->GetAbsOrigin() );
DispatchSpawn( m_hBonusBoss );
}
else
{
AssertMsg( 0, "CPopulationManager::StartCurrentWave trying to spawn a bonus boss, but cannot find spawn_boss_startpoint info_target in the map" );
UTIL_Remove( m_hBonusBoss );
m_hBonusBoss = NULL;
m_bBonusRound = false;
}
}
}
#endif
m_nRespecsAwardedInWave = 0;
FOR_EACH_MAP( m_PlayerBuybackPoints, i )
{
m_PlayerBuybackPoints[i] = tf_mvm_buybacks_per_wave.GetInt();
}
}
//-------------------------------------------------------------------------
CWave * CPopulationManager::GetCurrentWave( void )
{
if ( !m_bIsInitialized || m_waveVector.Count() == 0 )
return NULL;
// Wrap for Infinite MVM
if ( IsInEndlessWaves() )
{
return m_waveVector[m_iCurrentWaveIndex % m_waveVector.Count() ];
}
else if ( (int)m_iCurrentWaveIndex < m_waveVector.Count() )
{
return m_waveVector[m_iCurrentWaveIndex];
}
return NULL;
}
//-------------------------------------------------------------------------
void CPopulationManager::JumpToWave( uint32 waveNumber, float fCleanMoneyPercent /*= -1.0f*/ )
{
if ( !IsInEndlessWaves() && (waveNumber >= (uint32)m_waveVector.Count() ) )
{
if ( m_waveVector.Count() > 0 )
{
Warning( "Invalid wave number\n" );
}
return;
}
CWave * pWave = GetCurrentWave();
if ( pWave )
{
pWave->ForceFinish();
}
m_bIsWaveJumping = true;
m_iCurrentWaveIndex = waveNumber;
// Set Money for New Wave
if ( fCleanMoneyPercent != -1.0f )
{
ClearCheckpoint();
Initialize();
m_pMVMStats->ResetStats( );
for ( m_iCurrentWaveIndex = 0; m_iCurrentWaveIndex < waveNumber; ++m_iCurrentWaveIndex )
{
pWave = GetCurrentWave();
if ( pWave )
{
int nCurrency = pWave->GetTotalCurrency();
m_pMVMStats->SetCurrentWave( m_iCurrentWaveIndex );
if ( m_iCurrentWaveIndex < waveNumber )
{
m_pMVMStats->RoundEvent_CreditsDropped( m_iCurrentWaveIndex, nCurrency );
m_pMVMStats->RoundEvent_AcquiredCredits( m_iCurrentWaveIndex, nCurrency * fCleanMoneyPercent, false );
}
}
}
}
// Reset the new wave
m_iCurrentWaveIndex = waveNumber;
pWave = GetCurrentWave();
if ( pWave )
{
pWave->ForceReset();
}
m_pMVMStats->SetCurrentWave( m_iCurrentWaveIndex );
if ( IsInEndlessWaves() )
{
EndlessRollEscalation();
}
UpdateObjectiveResource();
SetCheckpoint( -1 );
TFGameRules()->SetAllowBetweenRounds( true );
TFGameRules()->State_Transition( GR_STATE_PREROUND );
TFGameRules()->PlayerReadyStatus_ResetState();
TFObjectiveResource()->SetMannVsMachineBetweenWaves( true );
RestorePlayerCurrency();
m_bIsWaveJumping = false;
CTF_GameStats.ResetRoundStats();
IGameEvent *event = gameeventmanager->CreateEvent( "mvm_reset_stats" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
ResetRespecPoints();
}
//-------------------------------------------------------------------------
// Report that a wave has been completed
void CPopulationManager::WaveEnd( bool bSuccess )
{
m_pMVMStats->RoundEvent_WaveEnd( bSuccess );
// Save off round stats before we reset them
IGameEvent *event = gameeventmanager->CreateEvent( "scorestats_accumulated_update" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
// Treat completed waves as rounds for the purposes of TF stats
CTF_GameStats.ResetRoundStats();
// Completing any wave removes everyone's obligation to stay in MvM matches. Because that's how it was when I got
// here.
MarkAllCurrentPlayersSafeToLeave();
if ( bSuccess )
{
if ( m_bBonusRound )
{
if ( m_hBonusBoss )
{
UTIL_Remove( m_hBonusBoss );
m_hBonusBoss = NULL;
}
m_bBonusRound = false;
}
else
{
m_iCurrentWaveIndex++;
}
m_pMVMStats->SetCurrentWave( m_iCurrentWaveIndex );
// get Current Wave
CWave *nextWave = GetCurrentWave();
if ( nextWave )
{
// we've reached a checkpoint
SetCheckpoint( -1 );
// display the upcoming wave's description
ShowNextWaveDescription();
nextWave->StartUpgradesAlertTimer( 3.0f );
if ( IsInEndlessWaves() )
{
EndlessRollEscalation();
nextWave->ForceReset();
// (Double time between waves on reset waves)
float flTime = gpGlobals->curtime + tf_mvm_endless_wait_time.GetFloat();
if ( m_iCurrentWaveIndex % tf_mvm_endless_bomb_reset.GetInt() == 0 )
{
flTime += tf_mvm_endless_wait_time.GetFloat();
m_bShouldResetFlag = true;
}
nextWave->SetStartTime( flTime );
}
}
if ( (int)m_iCurrentWaveIndex >= m_waveVector.Count() && !IsInEndlessWaves() )
{
// Restart the Map after a time delay
if ( tf_mm_trusted.GetBool() == true || tf_mvm_disconnect_on_victory.GetBool() == true )
{
m_flMapRestartTime = gpGlobals->curtime + tf_mvm_victory_disconnect_time.GetFloat();
}
else
{
m_flMapRestartTime = gpGlobals->curtime + tf_mvm_victory_reset_time.GetFloat();
}
TFObjectiveResource()->SetMannVsMachineBetweenWaves( true );
TFGameRules()->State_Transition( GR_STATE_GAME_OVER );
return;
}
}
if ( !IsInEndlessWaves() )
{
TFGameRules()->State_Transition( GR_STATE_BETWEEN_RNDS );
TFObjectiveResource()->SetMannVsMachineBetweenWaves( true );
}
}
//-------------------------------------------------------------------------
// Save the current wave as a checkpoint.
// When the scenario restarts from a loss, it will restart at the checkpoint.
void CPopulationManager::SetCheckpoint( int waveNumber )
{
// No checkpoints for Endless
if ( IsInEndlessWaves() )
return;
// Auto SetCheckPoint from ConsoleCommand
if ( waveNumber < 0 )
{
waveNumber = m_iCurrentWaveIndex;
}
if ( waveNumber < 0 || waveNumber >= m_waveVector.Count() )
{
Warning( "Warning: SetCheckpoint() called with invalid wave number %d\n", waveNumber );
return;
}
m_nNumConsecutiveWipes = 0;
m_checkpointWaveIndex = waveNumber;
DevMsg( "Checkpoint Saved\n" );
// snapshot each player's state
// Save off all upgrades and purge it, copy back in existing players
for( int i=0; i<m_playerUpgrades.Count(); ++i )
{
// Get this players check point
CheckpointSnapshotInfo *snapshot = FindCheckpointSnapshot( m_playerUpgrades[i]->m_steamId );
if ( snapshot == NULL )
{
// New SnapshotInfo, save the player id
snapshot = new CheckpointSnapshotInfo;
snapshot->m_steamId = m_playerUpgrades[i]->m_steamId;
m_checkpointSnapshot.AddToTail( snapshot );
}
// Save the Player upgrade history
snapshot->m_currencySpent = m_playerUpgrades[i]->m_currencySpent;
snapshot->m_upgradeVector.RemoveAll();
// copy in to upgrade history
for( int j = 0; j < m_playerUpgrades[i]->m_upgradeVector.Count(); ++j )
{
snapshot->m_upgradeVector.AddToTail( m_playerUpgrades[i]->m_upgradeVector[j]);
}
}
}
//-------------------------------------------------------------------------
void CPopulationManager::RestoreItemToCheckpointState( CTFPlayer *player, CEconItemView *item )
{
CheckpointSnapshotInfo *snapshot = FindCheckpointSnapshot( player );
if ( !snapshot )
return;
if ( !player->Inventory() )
return;
if ( !item || !item->IsValid() )
return;
player->BeginPurchasableUpgrades();
// restore the item's upgrade(s)
for( int u=0; u<snapshot->m_upgradeVector.Count(); ++u )
{
if ( item->GetItemDefIndex() == snapshot->m_upgradeVector[u].m_itemDefIndex )
{
if ( player->GetPlayerClass()->GetClassIndex() == snapshot->m_upgradeVector[u].m_iPlayerClass )
{
if ( g_hUpgradeEntity->ApplyUpgradeToItem( player, item, snapshot->m_upgradeVector[u].m_upgrade, snapshot->m_upgradeVector[u].m_nCost ) )
{
if ( tf_populator_debug.GetBool() )
{
const char *upgradeName = g_hUpgradeEntity->GetUpgradeAttributeName( snapshot->m_upgradeVector[u].m_upgrade );
DevMsg( "%3.2f: CHECKPOINT_RESTORE_ITEM: Player '%s', item '%s', upgrade '%s'\n",
gpGlobals->curtime,
player->GetPlayerName(),
item->GetStaticData()->GetItemBaseName(),
upgradeName ? upgradeName : "<self>" );
}
}
}
}
}
player->EndPurchasableUpgrades();
}
//-------------------------------------------------------------------------
void CPopulationManager::ForgetOtherBottleUpgrades ( CTFPlayer *player, CEconItemView *pItem, int upgradeToKeep )
{
PlayerUpgradeHistory *history = FindOrAddPlayerUpgradeHistory( player );
// This only applies to the current class, skip bottle upgrades for other classes
int iClass = player->GetPlayerClass()->GetClassIndex();
for( int i = 0; i < history->m_upgradeVector.Count(); ++i )
{
if ( iClass != history->m_upgradeVector[i].m_iPlayerClass )
{
continue;
}
// remove upgrades that do NOT match the target
if ( history->m_upgradeVector[i].m_itemDefIndex == pItem->GetItemDefIndex() && history->m_upgradeVector[i].m_upgrade != upgradeToKeep ) // item upgrade
{
history->m_upgradeVector.FastRemove( i );
--i;
}
}
}
// ----------------------------------------------------------------------------------
void CPopulationManager::RestorePlayerCurrency ()
{
// Set the players money on round start
int nRoundCurrency = m_pMVMStats->GetAcquiredCredits( -1 );
nRoundCurrency += GetStartingCurrency();
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_PVE_DEFENDERS );
for( int i=0; i<playerVector.Count(); ++i )
{
// deduct any cash that has already been spent
int spentCurrency = GetPlayerCurrencySpent( playerVector[i] );
playerVector[i]->SetCurrency( nRoundCurrency - spentCurrency );
}
}
// ----------------------------------------------------------------------------------
// Purpose : Get the player's upgrade list
// ----------------------------------------------------------------------------------
CUtlVector< CUpgradeInfo > *CPopulationManager::GetPlayerUpgradeHistory ( CTFPlayer *player )
{
PlayerUpgradeHistory *history = FindOrAddPlayerUpgradeHistory ( player );
if ( !history )
return NULL;
return &(history->m_upgradeVector);
}
// ----------------------------------------------------------------------------------
// Purpose : Get the player's currency history
// ----------------------------------------------------------------------------------
int CPopulationManager::GetPlayerCurrencySpent ( CTFPlayer *player )
{
PlayerUpgradeHistory *history = FindOrAddPlayerUpgradeHistory ( player );
if ( !history )
return 0;
return history->m_currencySpent;
}
// ----------------------------------------------------------------------------------
// Purpose : Get the player's currency history
// ----------------------------------------------------------------------------------
void CPopulationManager::AddPlayerCurrencySpent ( CTFPlayer *player, int cost )
{
PlayerUpgradeHistory *history = FindOrAddPlayerUpgradeHistory ( player );
if ( !history )
return;
history->m_currencySpent += cost;
}
// ----------------------------------------------------------------------------------
// Purpose : Send the player their upgrades
void CPopulationManager::SendUpgradesToPlayer ( CTFPlayer *player )
{
CUtlVector< CUpgradeInfo > *upgrades = GetPlayerUpgradeHistory ( player );
m_pMVMStats->SendUpgradesToPlayer( player, upgrades );
}
//-----------------------------------------------------------------------------------
void CPopulationManager::RestoreCheckpoint( void )
{
// No checkpoints for Endless
m_isRestoringCheckpoint = true;
if ( !IsInEndlessWaves() )
{
m_iCurrentWaveIndex = m_checkpointWaveIndex;
}
// Purge all player upgrades
// Set them to the checkpoint state
m_playerUpgrades.PurgeAndDeleteElements();
// We must clear each player's upgrade history to get rid of upgrades they
// purchased since the last checkpoint. The history will be rebuilt
// as the checkpoint snapshot is restored.
for( int i=0; i<m_checkpointSnapshot.Count(); ++i )
{
CheckpointSnapshotInfo *snapshot = m_checkpointSnapshot[i];
// Create new Entry since we Purged the list
PlayerUpgradeHistory *history = FindOrAddPlayerUpgradeHistory( snapshot->m_steamId );
history->m_currencySpent = snapshot->m_currencySpent;
for (int j = 0; j < snapshot->m_upgradeVector.Count(); ++j )
{
history->m_upgradeVector.AddToTail( snapshot->m_upgradeVector[j] );
}
}
// Iterate over play
// clear Bottles and Sentry danger and send their upgrades
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_PVE_DEFENDERS );
for( int i=0; i<playerVector.Count(); ++i )
{
CTFPlayer *player = playerVector[i];
// Clear sentry danger
player->ResetAccumulatedSentryGunDamageDealt();
player->ResetAccumulatedSentryGunKillCount();
// Bottles must be purged separately, charges will be restored with other items
CTFWearable *pWearable = player->GetEquippedWearableForLoadoutSlot( LOADOUT_POSITION_ACTION );
CTFPowerupBottle *pPowerupBottle = dynamic_cast< CTFPowerupBottle* >( pWearable );
if ( pPowerupBottle )
{
pPowerupBottle->Reset();
}
SendUpgradesToPlayer( player );
}
m_nNumConsecutiveWipes++;
// players are restored to their checkpoint state after they spawn
m_pMVMStats->SetCurrentWave( m_iCurrentWaveIndex );
UpdateObjectiveResource();
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Get_To_Upgrade" );
m_nRespecsAwardedInWave = 0;
}
//-------------------------------------------------------------------------
void CPopulationManager::ClearCheckpoint( void )
{
if ( tf_populator_debug.GetBool() )
{
DevMsg( "%3.2f: CHECKPOINT_CLEAR\n", gpGlobals->curtime );
}
m_nNumConsecutiveWipes = 0;
m_checkpointWaveIndex = 0;
m_checkpointSnapshot.PurgeAndDeleteElements();
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_PVE_DEFENDERS );
for( int i=0; i<playerVector.Count(); ++i )
{
CTFPlayer *player = playerVector[i];
player->ClearUpgradeHistory();
}
}
//-------------------------------------------------------------------------
void CPopulationManager::OnPlayerKilled( CTFPlayer *corpse )
{
for( int i=0; i<m_populatorVector.Count(); ++i )
{
m_populatorVector[i]->OnPlayerKilled( corpse );
}
CWave * pWave = GetCurrentWave();
if ( pWave )
{
pWave->OnPlayerKilled( corpse );
}
}
//-------------------------------------------------------------------------
void CPopulationManager::OnCurrencyPackFade( void )
{
}
//-------------------------------------------------------------------------
void CPopulationManager::OnCurrencyCollected( int nAmount, bool bCountAsDropped, bool bIsBonus )
{
// Store how much money players collect between waves so we can update the checkpoint
int iWaveNumber = GetWaveNumber();
if ( TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() )
{
// Decrement WaveNumber if between waves, money was for previous wave
iWaveNumber--;
}
if ( bCountAsDropped )
{
m_pMVMStats->RoundEvent_CreditsDropped( iWaveNumber, nAmount );
}
m_pMVMStats->RoundEvent_AcquiredCredits( iWaveNumber, nAmount, bIsBonus );
// Respec
int nRespecLimit = tf_mvm_respec_limit.GetInt();
if ( nRespecLimit )
{
bool bAtLimit = m_nRespecsAwarded >= nRespecLimit;
if ( !bAtLimit )
{
m_nCurrencyCollectedForRespec += nAmount;
// It's possible to earn multiple respecs from a large cash award
int nCreditGoal = tf_mvm_respec_credit_goal.GetInt();
while ( m_nCurrencyCollectedForRespec >= nCreditGoal && !bAtLimit )
{
++m_nRespecsAwarded;
++m_nRespecsAwardedInWave;
// Award each player a respec
CUtlVector< CTFPlayer* > playerVector;
CollectPlayers( &playerVector, TF_TEAM_PVE_DEFENDERS );
FOR_EACH_VEC( playerVector, i )
{
AddRespecToPlayer( playerVector[i] );
}
// Allowed to earn another?
bAtLimit = m_nRespecsAwarded >= nRespecLimit;
if ( !bAtLimit )
{
m_nCurrencyCollectedForRespec -= nCreditGoal;
}
else
{
// If we're at the limit, peg this value for client UI.
// i.e. "Respec Goal: 100 of 100"
m_nCurrencyCollectedForRespec = nCreditGoal;
}
}
// Send down to clients
CMannVsMachineStats *pStats = MannVsMachineStats_GetInstance();
if ( pStats )
{
pStats->SetNumRespecsEarnedInWave( m_nRespecsAwardedInWave );
pStats->SetAcquiredCreditsForRespec( m_nCurrencyCollectedForRespec );
}
}
}
}
//-------------------------------------------------------------------------
int CPopulationManager::GetTotalPopFileCurrency( void )
{
uint32 nTotalPopCurrency = 0;
FOR_EACH_VEC( m_waveVector, i )
{
nTotalPopCurrency += m_waveVector[i]->GetTotalCurrency();
}
return nTotalPopCurrency;
}
//-------------------------------------------------------------------------
void CPopulationManager::AdjustMinPlayerSpawnTime( void )
{
enum { kMvMRespawnTimeAddPerWave = 2, };
int iWaveNum = GetWaveNumber() + 1;
float flTime = 1.0f;
if ( IsInEndlessWaves() )
{
flTime = iWaveNum / 3.0f;
}
else
{
flTime = m_bFixedRespawnWaveTime ? m_nRespawnWaveTime :
MIN( m_nRespawnWaveTime, float( iWaveNum * kMvMRespawnTimeAddPerWave ) );
}
TFGameRules()->SetTeamRespawnWaveTime( TF_TEAM_PVE_DEFENDERS, flTime );
}
//-------------------------------------------------------------------------
void CPopulationManager::MarkAllCurrentPlayersSafeToLeave()
{
// If we have a match, mark everyone currently in the match safe to leave.
CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
if ( !pMatch )
{ return; }
// Mark everyone that was meant to be in the match safe to leave now, not just those who were actually present,
// mirroring old behavior (we are usually using this to release every current player from obligations to stay)
int total = pMatch->GetNumTotalMatchPlayers();
for ( int idx = 0; idx < total; idx++ )
{
pMatch->GetMatchDataForPlayer( idx )->MarkAlwaysSafeToLeave();
}
}
//-------------------------------------------------------------------------
void CPopulationManager::MvMVictory()
{
// Give "Bonus_Time Buff"
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
if ( !pTFPlayer || !pTFPlayer->IsPlayer() )
continue;
if ( pTFPlayer->IsAlive() )
{
pTFPlayer->m_Shared.AddCond( TF_COND_CRITBOOSTED_BONUS_TIME, 10.0 );
}
}
// Set Game state
TFGameRules()->BroadcastSound( 255, "Game.YourTeamWon" );
m_pMVMStats->RoundOver( true );
ClearCheckpoint();
// Notify Players of Victory
CBroadcastRecipientFilter filter;
filter.MakeReliable();
UserMessageBegin( filter, "MVMVictory" );
bool bIsKicking = tf_mvm_disconnect_on_victory.GetBool();
WRITE_BYTE( (uint8)bIsKicking );
if ( bIsKicking )
{
WRITE_BYTE((uint8)tf_mvm_victory_disconnect_time.GetFloat());
}
else
{
WRITE_BYTE((uint8)tf_mvm_victory_reset_time.GetFloat());
}
MessageEnd();
// Note that because MvM is weird, we can have multiple victories per one match, as players can keep going
GTFGCClientSystem()->SendMvMVictoryResult();
}
//-------------------------------------------------------------------------
void CPopulationManager::GetSentryBusterDamageAndKillThreshold( int &nDamage, int &nKills ) const
{
const int nSentryThreshold = 2;
int nSentries = 0;
for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
{
CBaseObject* pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
if ( pObj->ObjectType() == OBJ_SENTRYGUN )
{
// Disposable sentries are not valid targets
if ( pObj->IsDisposableBuilding() )
continue;
if ( pObj->GetTeamNumber() == TF_TEAM_PVE_DEFENDERS )
{
nSentries++;
}
}
}
// Adjust damage and kill threshold based on number of sentries in the world
// otherwise players trivially handle the spawn rate with raw damage
float flScale = RemapValClamped( nSentries, 1, 6, 1.f, 0.5f );
nDamage = ( nSentries >= nSentryThreshold ) ? m_sentryBusterDamageDealtThreshold * flScale : m_sentryBusterDamageDealtThreshold;
nKills = ( nSentries >= nSentryThreshold ) ? m_sentryBusterKillThreshold * flScale : m_sentryBusterKillThreshold;
}
//-------------------------------------------------------------------------
bool CPopulationManager::IsInEndlessWaves ( void )
{
return (m_bEndlessOn || tf_mvm_endless_force_on.GetBool() ) && m_waveVector.Count() > 0;
}
//-------------------------------------------------------------------------
float CPopulationManager::GetHealthMultiplier ( bool bIsTank /*= false*/ )
{
if ( !IsInEndlessWaves() || !bIsTank )
return tf_populator_health_multiplier.GetFloat();
// Calculate how much health the tank should get per wave
return tf_populator_health_multiplier.GetFloat() + m_iCurrentWaveIndex * tf_mvm_endless_tank_boost.GetFloat();
}
//-------------------------------------------------------------------------
float CPopulationManager::GetDamageMultiplier ()
{
//if ( !IsInEndlessWaves() )
return tf_populator_damage_multiplier.GetFloat();
// Find out how many times over t
// Floor of the result, ie 9 / 7 returns 1, 15 / 7 returns 2;
//int nRepeatCount = m_iCurrentWaveIndex / tf_mvm_endless_scale_rate.GetInt();
//return tf_populator_damage_multiplier.GetFloat() + tf_mvm_endless_damage_boost_rate.GetFloat() * nRepeatCount;
}
//-------------------------------------------------------------------------
void CPopulationManager::EndlessParseBotUpgrades ()
{
// Don't do anything if we don't have raid mode.
m_BotUpgradesList.RemoveAll();
KeyValues *pKV = new KeyValues( "Upgrades" );
if ( !pKV->LoadFromFile( filesystem, "scripts/items/mvm_botupgrades.txt", "MOD" ) )
{
Warning( "Can't open scripts/items/mvm_botupgrades.txt\n" );
pKV->deleteThis();
return;
}
for ( KeyValues *pData = pKV->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() )
{
const char *pszAttrib = pData->GetString( "attribute" );
int iAttribIndex = 0;
bool bIsBotAttr = pData->GetBool( "IsBotAttr" );
bool bIsSkillAttr = pData->GetBool( "IsSkillAttr" );
float flValue = pData->GetFloat( "value" );
float flMax = pData->GetFloat( "max" );
int nCost = pData->GetFloat( "cost", 100 );
int nWeight = pData->GetInt( "weight", 1 );
// Normal econ attr
if ( !bIsBotAttr && !bIsSkillAttr )
{
CEconItemSchema *pSchema = ItemSystem()->GetItemSchema();
if ( pSchema )
{
// If we can't find a matching attribute, continue
const CEconItemAttributeDefinition *pAttr = pSchema->GetAttributeDefinitionByName( pszAttrib );
if ( !pAttr )
{
DevMsg( "Unable to Find Attribute %s when parsing EndlessParseBotUpgrades \n", pszAttrib );
continue;
}
iAttribIndex = pAttr->GetDefinitionIndex();
}
}
int index = m_BotUpgradesList.AddToTail();
for ( int i = 0; i < nWeight; ++i )
{
CMvMBotUpgrade *pUpgrade = &( m_BotUpgradesList[ index ] );
// load her up
V_strncpy( pUpgrade->szAttrib, pszAttrib, sizeof( pUpgrade->szAttrib ) );
pUpgrade->flValue = flValue;
pUpgrade->flMax = flMax;
pUpgrade->nCost = nCost;
pUpgrade->bIsBotAttr = bIsBotAttr;
pUpgrade->bIsSkillAttr = bIsSkillAttr;
pUpgrade->iAttribIndex = iAttribIndex;
}
}
pKV->deleteThis();
}
//-------------------------------------------------------------------------
void CPopulationManager::EndlessRollEscalation( void )
{
// Get the wave and calculate the amount of "money" the bots have
// for now
int nBotCash = ( m_iCurrentWaveIndex * tf_mvm_endless_bot_cash.GetFloat() );
m_EndlessActiveBotUpgrades.Purge();
// Create a list of items that can be purchased
CUtlVector< CMvMBotUpgrade > vecAvailableUpgrades;
FOR_EACH_VEC( m_BotUpgradesList, i )
{
if ( m_BotUpgradesList[i].nCost <= nBotCash )
{
vecAvailableUpgrades.AddToTail( m_BotUpgradesList[i] );
}
}
CUniformRandomStream rRandom;
rRandom.SetSeed( m_EndlessSeeds[ m_iCurrentWaveIndex % m_EndlessSeeds.Count() ] );
while ( nBotCash >= 100 && vecAvailableUpgrades.Count() > 0 )
{
int index = rRandom.RandomInt( 0, vecAvailableUpgrades.Count() - 1);
CMvMBotUpgrade upgrade = vecAvailableUpgrades[index];
// Scan the existing list and append the value if it does, otherwise add new entry
bool bUpgradeFound = false;
FOR_EACH_VEC( m_EndlessActiveBotUpgrades, iUpgrade )
{
if ( !V_strcmp( m_EndlessActiveBotUpgrades[iUpgrade].szAttrib, upgrade.szAttrib) )
{
bUpgradeFound = true;
// increment the value
m_EndlessActiveBotUpgrades[iUpgrade].flValue += upgrade.flValue;
nBotCash -= upgrade.nCost;
// remove this upgrade if its been max
if ( ( upgrade.flMax > 0 && upgrade.flMax <= m_EndlessActiveBotUpgrades[iUpgrade].flValue )
|| ( upgrade.flMax < 0 && upgrade.flMax >= m_EndlessActiveBotUpgrades[iUpgrade].flValue ) )
{
vecAvailableUpgrades.FastRemove( index );
}
break;
}
}
if ( !bUpgradeFound )
{
m_EndlessActiveBotUpgrades.AddToTail( upgrade );
nBotCash -= upgrade.nCost;
// remove this upgrade if its been max
if ( ( upgrade.flMax > 0 && upgrade.flMax <= upgrade.flValue )
|| ( upgrade.flMax < 0 && upgrade.flMax >= upgrade.flValue ) )
{
vecAvailableUpgrades.FastRemove( index );
}
}
// Scan available upgrades and remove any that we can't cover
if ( nBotCash > 0 )
{
FOR_EACH_VEC_BACK( vecAvailableUpgrades, iUpgrade )
{
if ( vecAvailableUpgrades[iUpgrade].nCost > nBotCash )
{
vecAvailableUpgrades.FastRemove( iUpgrade );
}
}
}
}
char msg[255];
V_strcpy_safe( msg, "*** Bot Upgrades\n" );
FOR_EACH_VEC( m_EndlessActiveBotUpgrades, iUpgrade )
{
char line[255];
V_sprintf_safe( line, "-%s %.1f\n", m_EndlessActiveBotUpgrades[iUpgrade].szAttrib, m_EndlessActiveBotUpgrades[iUpgrade].flValue );
V_strcat_safe( msg, line );
}
UTIL_CenterPrintAll( msg );
UTIL_ClientPrintAll( HUD_PRINTCONSOLE, msg );
}
//-------------------------------------------------------------------------
void CPopulationManager::EndlessSetAttributesForBot( CTFBot *pBot )
{
FOR_EACH_VEC( m_EndlessActiveBotUpgrades, i )
{
//DevMsg( " - %s %d ", m_EndlessActiveBotUpgrades[iUpgrade].szAttrib, m_EndlessActiveBotUpgrades[iUpgrade].flValue );
//pBot->m_AttributeManager
if ( m_EndlessActiveBotUpgrades[i].bIsBotAttr == true )
{
pBot->SetAttribute( (int)m_EndlessActiveBotUpgrades[i].flValue );
}
else if ( m_EndlessActiveBotUpgrades[i].bIsSkillAttr == true )
{
//switch ( (int)m_EndlessActiveBotUpgrades[i].flValue )
pBot->SetDifficulty( (CTFBot::DifficultyType)(int)m_EndlessActiveBotUpgrades[i].flValue );
}
else
{
CEconItemAttributeDefinition *pDef = ItemSystem()->GetItemSchema()->GetAttributeDefinition( m_EndlessActiveBotUpgrades[i].iAttribIndex );
if ( pDef )
{
int iFormat = pDef->GetDescriptionFormat();
float flValue = m_EndlessActiveBotUpgrades[i].flValue;
if ( iFormat == ATTDESCFORM_VALUE_IS_PERCENTAGE || iFormat == ATTDESCFORM_VALUE_IS_INVERTED_PERCENTAGE )
{
flValue += 1.0;
}
Assert( pBot->GetAttributeList() );
pBot->GetAttributeList()->SetRuntimeAttributeValue( pDef, flValue );
}
}
}
}
//-------------------------------------------------------------------------
bool CPopulationManager::EndlessShouldResetFlag ()
{
return m_bShouldResetFlag;
}
//-------------------------------------------------------------------------
void CPopulationManager::EndlessFlagHasReset ()
{
m_bShouldResetFlag = false;
}
//-------------------------------------------------------------------------
// PRIVATE
//-------------------------------------------------------------------------
// -------------------------------------------------------------------------
// Purpose : PostInit
// -------------------------------------------------------------------------
void CPopulationManager::PostInitialize( void )
{
if ( TheNavMesh->GetNavAreaCount() <= 0 )
{
Warning( "Cannot populate - no Navigation Mesh exists.\n" );
return;
}
FOR_EACH_VEC ( m_populatorVector, i )
{
m_populatorVector[i]->PostInitialize();
}
FOR_EACH_VEC ( m_waveVector, i )
{
m_waveVector[i]->PostInitialize();
}
}
//-------------------------------------------------------------------------
// Purpose : Read the target file (m_filename) and populate initial data fields
//-------------------------------------------------------------------------
bool CPopulationManager::Parse( void )
{
if ( m_popfileFull[ 0 ] == '\0' )
{
Warning( "No population file specified.\n" );
return false;
}
//if ( m_bIsInitialized )
// return true;
KeyValues *values = new KeyValues( "Population" );
if ( !values->LoadFromFile( filesystem, m_popfileFull, "GAME" ) )
{
Warning( "Can't open %s.\n", m_popfileFull );
values->deleteThis();
return false;
}
// Clear out existing Data structures
m_populatorVector.PurgeAndDeleteElements();
m_waveVector.RemoveAll();
m_bEndlessOn = false;
if ( m_pTemplates )
{
m_pTemplates->deleteThis();
m_pTemplates = NULL;
}
// find templates first
KeyValues *pTemplates = values->FindKey( "Templates" );
if ( pTemplates )
{
m_pTemplates = pTemplates->MakeCopy();
}
for ( KeyValues *data = values->GetFirstSubKey(); data != NULL; data = data->GetNextKey() )
{
const char *name = data->GetName();
if ( Q_strlen( name ) <= 0 )
{
continue;
}
if ( !Q_stricmp( name, "StartingCurrency" ) )
{
m_nStartingCurrency = data->GetInt();
}
else if ( !Q_stricmp( name, "RespawnWaveTime" ) )
{
m_nRespawnWaveTime = data->GetInt();
}
else if ( !Q_stricmp( name, "EventPopfile" ) )
{
if ( !Q_stricmp( data->GetString(), "Halloween" ) )
{
m_nMvMEventPopfileType = MVM_EVENT_POPFILE_HALLOWEEN;
}
else
{
m_nMvMEventPopfileType = MVM_EVENT_POPFILE_NONE;
}
}
else if ( !Q_stricmp( name, "FixedRespawnWaveTime" ) )
{
m_bFixedRespawnWaveTime = true;
}
else if ( !Q_stricmp( name, "AddSentryBusterWhenDamageDealtExceeds" ) )
{
m_sentryBusterDamageDealtThreshold = data->GetInt();
}
else if ( !Q_stricmp( name, "AddSentryBusterWhenKillCountExceeds" ) )
{
m_sentryBusterKillThreshold = data->GetInt();
}
else if ( !Q_stricmp( name, "CanBotsAttackWhileInSpawnRoom" ) )
{
if ( !Q_stricmp( data->GetString(), "no" ) || !Q_stricmp( data->GetString(), "false" ) )
{
m_canBotsAttackWhileInSpawnRoom = false;
}
else
{
m_canBotsAttackWhileInSpawnRoom = true;
}
}
else if ( !Q_stricmp( name, "RandomPlacement" ) )
{
CRandomPlacementPopulator *randomPopulator = new CRandomPlacementPopulator( this );
if ( randomPopulator->Parse( data ) == false )
{
Warning( "Error reading RandomPlacement definition\n" );
return false;
}
m_populatorVector.AddToTail( randomPopulator );
}
else if ( !Q_stricmp( name, "PeriodicSpawn" ) )
{
CPeriodicSpawnPopulator *periodicPopulator = new CPeriodicSpawnPopulator( this );
if ( periodicPopulator->Parse( data ) == false )
{
Warning( "Error reading PeriodicSpawn definition\n" );
return false;
}
m_populatorVector.AddToTail( periodicPopulator );
}
else if ( !Q_stricmp( name, "Wave" ) )
{
CWave *wave = new CWave( this );
if ( !wave->Parse( data ) )
{
Warning( "Error reading Wave definition\n" );
return false;
}
// also keep vector of wave pointers for convenience
m_waveVector.AddToTail( wave );
}
else if ( !Q_stricmp( name, "Mission" ) )
{
CMissionPopulator *missionPopulator = new CMissionPopulator( this );
if ( missionPopulator->Parse( data ) == false )
{
Warning( "Error reading Mission definition\n" );
return false;
}
m_populatorVector.AddToTail( missionPopulator );
}
else if ( !Q_stricmp( name, "Templates" ) )
{
// handled above
}
else if ( !Q_stricmp( name, "Advanced" ) )
{
m_bAdvancedPopFile = true;
}
else if ( !Q_stricmp( name, "IsEndless" ) )
{
m_bEndlessOn = true;
}
else
{
Warning( "Invalid populator '%s'\n", name );
return false;
}
}
for ( int nPopulator = 0; nPopulator < m_populatorVector.Count(); ++nPopulator )
{
CMissionPopulator *pMission = dynamic_cast< CMissionPopulator* >( m_populatorVector[ nPopulator ] );
if ( pMission )
{
// FIXME: Need a way to handle missions that spawn multiple types
int nStartWave = pMission->BeginAtWave();
int nStopWave = pMission->StopAtWave();
if ( pMission->m_spawner && !pMission->m_spawner->IsVarious() )
{
for ( int i = nStartWave; i < nStopWave; ++i )
{
if ( m_waveVector.IsValidIndex( i ) )
{
CWave *pWave = m_waveVector[ i ];
unsigned int iFlags = MVM_CLASS_FLAG_MISSION;
if ( pMission->m_spawner->IsMiniBoss() )
{
iFlags |= MVM_CLASS_FLAG_MINIBOSS;
}
if ( pMission->m_spawner->HasAttribute( CTFBot::ALWAYS_CRIT ) )
{
iFlags |= MVM_CLASS_FLAG_ALWAYSCRIT;
}
pWave->AddClassType( pMission->m_spawner->GetClassIcon(), 0, iFlags );
}
}
}
}
}
values->deleteThis();
return true;
}
// ----------------------------------------------------------------------------------
// Purpose : Find a Checkpoint info
//-------------------------------------------------------------------------
CPopulationManager::CheckpointSnapshotInfo *CPopulationManager::FindCheckpointSnapshot( CTFPlayer *player ) const
{
CSteamID steamId;
if (!player->GetSteamID( &steamId ))
return NULL;
return FindCheckpointSnapshot( steamId );
}
// ----------------------------------------------------------------------------------
// Purpose : Find a Checkpoint info
//-------------------------------------------------------------------------
CPopulationManager::CheckpointSnapshotInfo *CPopulationManager::FindCheckpointSnapshot( CSteamID id ) const
{
for( int i=0; i<m_checkpointSnapshot.Count(); ++i )
{
CheckpointSnapshotInfo *snapshot = m_checkpointSnapshot[i];
if ( id == snapshot->m_steamId )
return snapshot;
}
return NULL;
}
// ----------------------------------------------------------------------------------
// Purpose : Returns the Player's Upgrade History Struct, Adds a new entry if not present
// ----------------------------------------------------------------------------------
CPopulationManager::PlayerUpgradeHistory *CPopulationManager::FindOrAddPlayerUpgradeHistory ( CTFPlayer *player )
{
CSteamID steamId;
if (!player->GetSteamID( &steamId ))
{
Log( "MvM : Unable to Find SteamID for player %s, unable to locate their upgrade history!", player->GetPlayerName() );
return NULL;
}
return FindOrAddPlayerUpgradeHistory( steamId );
}
// ----------------------------------------------------------------------------------
// Purpose : Returns the Player's Upgrade History Struct, Adds a new entry if not present
// ----------------------------------------------------------------------------------
CPopulationManager::PlayerUpgradeHistory *CPopulationManager::FindOrAddPlayerUpgradeHistory ( CSteamID steamId )
{
FOR_EACH_VEC( m_playerUpgrades, i )
{
if ( steamId == m_playerUpgrades[i]->m_steamId )
{
return m_playerUpgrades[i];
}
}
PlayerUpgradeHistory *history = new PlayerUpgradeHistory;
history->m_steamId = steamId;
history->m_currencySpent = 0;
m_playerUpgrades.AddToTail( history );
return history;
}
// ----------------------------------------------------------------------------------
// Purpose : Remove upgrade tracking tied to the player and their items (ignores bottles, buybacks)
// ----------------------------------------------------------------------------------
void CPopulationManager::RemovePlayerAndItemUpgradesFromHistory( CTFPlayer *pPlayer )
{
CSteamID steamId;
if ( !pPlayer->GetSteamID( &steamId ) )
return;
// Remove player and item upgrades from snapshots
FOR_EACH_VEC_BACK( m_checkpointSnapshot, i )
{
CheckpointSnapshotInfo *pSnapshot = m_checkpointSnapshot[i];
if ( steamId != pSnapshot->m_steamId )
continue;
FOR_EACH_VEC_BACK( pSnapshot->m_upgradeVector, j )
{
int iUpgrade = pSnapshot->m_upgradeVector[j].m_upgrade;
CMannVsMachineUpgrades *pUpgrade = &(g_MannVsMachineUpgrades.m_Upgrades[ iUpgrade ]);
if ( pUpgrade && ( pUpgrade->nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_ITEM || pUpgrade->nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_PLAYER ) )
{
pSnapshot->m_currencySpent -= pSnapshot->m_upgradeVector[j].m_nCost;
pSnapshot->m_upgradeVector.Remove( j );
}
}
}
// Remove player and item upgrades from current
FOR_EACH_VEC_BACK( m_playerUpgrades, i )
{
if ( steamId != m_playerUpgrades[i]->m_steamId )
continue;
FOR_EACH_VEC_BACK( m_playerUpgrades[i]->m_upgradeVector, j )
{
int iUpgrade = m_playerUpgrades[i]->m_upgradeVector[j].m_upgrade;
CMannVsMachineUpgrades *pUpgrade = &(g_MannVsMachineUpgrades.m_Upgrades[ iUpgrade ]);
if ( pUpgrade && ( pUpgrade->nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_ITEM || pUpgrade->nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_PLAYER ) )
{
m_playerUpgrades[i]->m_currencySpent -= m_playerUpgrades[i]->m_upgradeVector[j].m_nCost;
m_playerUpgrades[i]->m_upgradeVector.Remove( j );
}
}
}
// Only do this step in MvM
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && m_pMVMStats )
{
// This should put us at the right currency, given that we've removed item and player upgrade tracking by this point
int nTotalAcquiredCurrency = m_pMVMStats->GetAcquiredCredits( -1 ) + GetStartingCurrency();
int nSpentCurrency = GetPlayerCurrencySpent( pPlayer );
pPlayer->SetCurrency( nTotalAcquiredCurrency - nSpentCurrency );
// Reset the stat that tracks upgrade purchases
m_pMVMStats->ResetUpgradeSpending( pPlayer );
}
};
//-----------------------------------------------------------------------------
// Purpose: This adds one per call
//-----------------------------------------------------------------------------
void CPopulationManager::AddRespecToPlayer( CTFPlayer *pPlayer )
{
if ( !pPlayer )
return;
CSteamID steamID;
if ( pPlayer->GetSteamID( &steamID ) )
{
int iIndex = m_PlayerRespecPoints.Find( steamID.ConvertToUint64() );
if ( iIndex != m_PlayerRespecPoints.InvalidIndex() )
{
int nCount = m_PlayerRespecPoints[iIndex];
if ( nCount >= tf_mvm_respec_limit.GetInt() )
return;
m_PlayerRespecPoints[iIndex]++;
}
else
{
m_PlayerRespecPoints.Insert( steamID.ConvertToUint64(), 1 );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: This removes one per call
//-----------------------------------------------------------------------------
void CPopulationManager::RemoveRespecFromPlayer( CTFPlayer *pPlayer )
{
if ( !pPlayer )
return;
// Unlimited
if ( !tf_mvm_respec_limit.GetInt() )
return;
CSteamID steamID;
if ( pPlayer->GetSteamID( &steamID ) )
{
int iIndex = m_PlayerRespecPoints.Find( steamID.ConvertToUint64() );
if ( iIndex != m_PlayerRespecPoints.InvalidIndex() )
{
Assert( m_PlayerRespecPoints[iIndex] );
m_PlayerRespecPoints[iIndex]--;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: This stomps whatever value we have
//-----------------------------------------------------------------------------
void CPopulationManager::SetNumRespecsForPlayer( CTFPlayer *pPlayer, int nCount )
{
if ( !pPlayer )
return;
CSteamID steamID;
if ( pPlayer->GetSteamID( &steamID ) )
{
int iIndex = m_PlayerRespecPoints.Find( steamID.ConvertToUint64() );
if ( iIndex != m_PlayerRespecPoints.InvalidIndex() )
{
m_PlayerRespecPoints[iIndex] = nCount;
}
else
{
m_PlayerRespecPoints.Insert( steamID.ConvertToUint64(), nCount );
}
}
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int CPopulationManager::GetNumRespecsAvailableForPlayer( CTFPlayer *pPlayer )
{
// Unlimited
if ( !tf_mvm_respec_limit.GetBool() )
return 1;
CSteamID steamID;
if ( pPlayer && pPlayer->GetSteamID( &steamID ) )
{
int iIndex = m_PlayerRespecPoints.Find( steamID.ConvertToUint64() );
if ( iIndex != m_PlayerRespecPoints.InvalidIndex() )
{
return m_PlayerRespecPoints[iIndex];
}
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Slam the value to nCount
//-----------------------------------------------------------------------------
void CPopulationManager::SetBuybackCreditsForPlayer( CTFPlayer *pPlayer, int nCount )
{
if ( !pPlayer )
return;
CSteamID steamID;
if ( pPlayer->GetSteamID( &steamID ) )
{
int iIndex = m_PlayerBuybackPoints.Find( steamID.ConvertToUint64() );
if ( iIndex != m_PlayerBuybackPoints.InvalidIndex() )
{
m_PlayerBuybackPoints[iIndex] = nCount;
}
else
{
m_PlayerBuybackPoints.Insert( steamID.ConvertToUint64(), nCount );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: This removes one per call
//-----------------------------------------------------------------------------
void CPopulationManager::RemoveBuybackCreditFromPlayer( CTFPlayer *pPlayer )
{
if ( !pPlayer )
return;
// Unlimited
if ( !tf_mvm_buybacks_method.GetInt() )
return;
CSteamID steamID;
if ( pPlayer->GetSteamID( &steamID ) )
{
int iIndex = m_PlayerBuybackPoints.Find( steamID.ConvertToUint64() );
if ( iIndex != m_PlayerBuybackPoints.InvalidIndex() )
{
Assert( m_PlayerBuybackPoints[iIndex] );
m_PlayerBuybackPoints[iIndex]--;
}
}
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int CPopulationManager::GetNumBuybackCreditsForPlayer( CTFPlayer *pPlayer )
{
if ( !tf_mvm_buybacks_method.GetBool() )
return 1;
CSteamID steamID;
if ( pPlayer && pPlayer->GetSteamID( &steamID ) )
{
int iIndex = m_PlayerBuybackPoints.Find( steamID.ConvertToUint64() );
if ( iIndex != m_PlayerBuybackPoints.InvalidIndex() )
{
return m_PlayerBuybackPoints[iIndex];
}
}
return 0;
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool CPopulationManager::IsPlayerBeingTrackedForBuybacks( CTFPlayer *pPlayer )
{
int iIndex = m_PlayerBuybackPoints.InvalidIndex();
CSteamID steamID;
if ( pPlayer && pPlayer->GetSteamID( &steamID ) )
{
iIndex = m_PlayerBuybackPoints.Find( steamID.ConvertToUint64() );
}
return ( iIndex != m_PlayerBuybackPoints.InvalidIndex() );
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CPopulationManager::ResetRespecPoints( void )
{
m_PlayerRespecPoints.RemoveAll();
m_nRespecsAwarded = 0;
m_nRespecsAwardedInWave = 0;
m_nCurrencyCollectedForRespec = 0;
// Send down to clients
if ( tf_mvm_respec_limit.GetBool() )
{
CMannVsMachineStats *pStats = MannVsMachineStats_GetInstance();
if ( pStats )
{
pStats->SetNumRespecsEarnedInWave( m_nRespecsAwardedInWave );
pStats->SetAcquiredCreditsForRespec( m_nCurrencyCollectedForRespec );
}
}
}
// ----------------------------------------------------------------------------------
// Purpose : Output useful info to console - Remove before ship
// ----------------------------------------------------------------------------------
void CPopulationManager::DebugWaveStats ()
{
// map economy stats to spit to the console
// remove before shipping
if ( m_waveVector.Count() )
{
uint32 nTotalPopCurrency = GetTotalPopFileCurrency();
uint32 nTotalWaves = GetTotalWaveCount();
DevMsg( "---\n" );
DevMsg( "Credits: %d\n", nTotalPopCurrency );
DevMsg( "Waves: %d ( %3.2f credits per wave )\n", nTotalWaves, float( (float)nTotalPopCurrency / (float)nTotalWaves ) );
DevMsg( "---\n" );
}
if ( m_EndlessActiveBotUpgrades.Count() > 0)
{
DevMsg( "*** Endless Bot Upgrades - %.0f Cash *** \n", m_iCurrentWaveIndex * tf_mvm_endless_bot_cash.GetFloat() );
FOR_EACH_VEC( m_EndlessActiveBotUpgrades, iUpgrade )
{
DevMsg( " - %s %.2f\n", m_EndlessActiveBotUpgrades[iUpgrade].szAttrib, m_EndlessActiveBotUpgrades[iUpgrade].flValue );
}
char msg[255];
V_strcpy_safe( msg, "*** Bot Upgrades\n" );
FOR_EACH_VEC( m_EndlessActiveBotUpgrades, iUpgrade )
{
char line[255];
V_sprintf_safe( line, "-%s %.1f\n", m_EndlessActiveBotUpgrades[iUpgrade].szAttrib, m_EndlessActiveBotUpgrades[iUpgrade].flValue );
V_strcat_safe( msg, line );
}
UTIL_CenterPrintAll( msg );
UTIL_ClientPrintAll( HUD_PRINTCONSOLE, msg );
}
DevMsg( "Popfile: %s\n", GetPopulationFilename() );
}
void CPopulationManager::AllocateBots()
{
if ( m_bAllocatedBots )
{
return;
}
int nNumEnemyBots = 0;
CUtlVector<CTFPlayer *> botVector;
nNumEnemyBots = CollectMvMBots( &botVector );
if ( botVector.Count() > 0 )
{
Assert( botVector.Count() == 0 );
Warning( "%d bots were already allocated some how before CPopulationManager::AllocateBots was called\n", botVector.Count() );
}
for ( int i = nNumEnemyBots; i < MVM_INVADERS_TEAM_SIZE; ++i )
{
CTFBot* newBot = NextBotCreatePlayerBot< CTFBot >( "TFBot", false );
if ( newBot )
{
newBot->ChangeTeam( TEAM_SPECTATOR, false, true );
}
}
m_bAllocatedBots = true;
}
void CPopulationManager::PauseSpawning()
{
DevMsg( "Wave paused\n" );
m_bSpawningPaused = true;
}
void CPopulationManager::UnpauseSpawning()
{
DevMsg( "Wave unpaused\n" );
m_bSpawningPaused = false;
// Some populators need to reset their timers or do other things when we unpause.
// Go through and let them know we've un-paused.
FOR_EACH_VEC( m_populatorVector, i )
{
m_populatorVector[i]->UnpauseSpawning();
}
}
bool CPopulationManager::HasEventChangeAttributes( const char* pszEventName ) const
{
for ( int i=0; i<m_waveVector.Count(); ++i )
{
if ( m_waveVector[i]->HasEventChangeAttributes( pszEventName ) )
{
return true;
}
}
for ( int i=0; i<m_populatorVector.Count(); ++i )
{
if ( m_populatorVector[i]->HasEventChangeAttributes( pszEventName ) )
{
return true;
}
}
return false;
}
/*static*/ int CPopulationManager::CollectMvMBots ( CUtlVector< CTFPlayer *> *pBots )
{
pBots->RemoveAll();
for( int i=1; i<=gpGlobals->maxClients; ++i )
{
CTFPlayer *player = ToTFPlayer( UTIL_PlayerByIndex( i ) );
if ( player == NULL )
continue;
if ( FNullEnt( player->edict() ) )
continue;
if ( !player->IsPlayer() )
continue;
if ( !player->IsBot() )
continue;
if ( !player->IsConnected() )
continue;
if ( player->GetTeamNumber() == TF_TEAM_PVE_DEFENDERS ) // Want everything but defenders
continue;
pBots->AddToTail( player );
}
return pBots->Count();
}