//========= 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 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; iUpdate(); } // 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; im_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; um_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 : "" ); } } } } } 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; iSetCurrency( 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; im_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; iResetAccumulatedSentryGunDamageDealt(); 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; iClearUpgradeHistory(); } } //------------------------------------------------------------------------- void CPopulationManager::OnPlayerKilled( CTFPlayer *corpse ) { for( int i=0; iOnPlayerKilled( 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()[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; im_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 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; iHasEventChangeAttributes( pszEventName ) ) { return true; } } for ( int i=0; iHasEventChangeAttributes( 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(); }