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