summaryrefslogtreecommitdiff
path: root/game/server/tf/player_vs_environment/tf_population_manager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/player_vs_environment/tf_population_manager.cpp')
-rw-r--r--game/server/tf/player_vs_environment/tf_population_manager.cpp2734
1 files changed, 2734 insertions, 0 deletions
diff --git a/game/server/tf/player_vs_environment/tf_population_manager.cpp b/game/server/tf/player_vs_environment/tf_population_manager.cpp
new file mode 100644
index 0000000..e5c2cc1
--- /dev/null
+++ b/game/server/tf/player_vs_environment/tf_population_manager.cpp
@@ -0,0 +1,2734 @@
+//========= 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();
+}