mirror of
https://github.com/nillerusr/source-engine.git
synced 2024-12-23 14:46:53 +00:00
2735 lines
80 KiB
C++
2735 lines
80 KiB
C++
//========= 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();
|
|
}
|