Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2324 lines
60 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: tf_populator_spawners
// Implementations of NPC Spawning Code for PvE related game modes (MvM)
//=============================================================================//
#include "cbase.h"
#include "tf_populators.h"
#include "tf_populator_spawners.h"
#include "tf_team.h"
#include "tf_obj_sentrygun.h"
#include "tf_objective_resource.h"
#include "eventqueue.h"
#include "tf_tank_boss.h"
#include "tf_gc_server.h"
#include "tf_gamerules.h"
#include "etwprof.h"
#include "team_control_point_master.h"
extern ConVar tf_populator_debug;
extern ConVar tf_populator_active_buffer_range;
ConVar tf_mvm_engineer_teleporter_uber_duration( "tf_mvm_engineer_teleporter_uber_duration", "5.f", FCVAR_CHEAT );
ConVar tf_mvm_currency_bonus_ratio_min( "tf_mvm_currency_bonus_ratio_min", "0.95f", FCVAR_HIDDEN, "The minimum percentage of wave money players must collect in order to qualify for min bonus - 0.1 to 1.0. Half the bonus amount will be awarded when reaching min ratio, and half when reaching max.", true, 0.1, true, 1.0 );
ConVar tf_mvm_currency_bonus_ratio_max( "tf_mvm_currency_bonus_ratio_max", "1.f", FCVAR_HIDDEN, "The highest percentage of wave money players must collect in order to qualify for max bonus - 0.1 to 1.0. Half the bonus amount will be awarded when reaching min ratio, and half when reaching max.", true, 0.1, true, 1.0 );
//-----------------------------------------------------------------------
static void FireEvent( EventInfo *eventInfo, const char *eventName )
{
if ( eventInfo )
{
CBaseEntity *targetEntity = gEntList.FindEntityByName( NULL, eventInfo->m_target );
if ( !targetEntity )
{
Warning( "WaveSpawnPopulator: Can't find target entity '%s' for %s\n", eventInfo->m_target.Access(), eventName );
}
else
{
g_EventQueue.AddEvent( targetEntity, eventInfo->m_action, 0.0f, NULL, NULL );
}
}
}
//-----------------------------------------------------------------------
static EventInfo *ParseEvent( KeyValues *values )
{
EventInfo *eventInfo = new EventInfo;
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, "Target" ) )
{
eventInfo->m_target.sprintf( "%s", data->GetString() );
}
else if ( !Q_stricmp( name, "Action" ) )
{
eventInfo->m_action.sprintf( "%s", data->GetString() );
}
else
{
Warning( "Unknown field '%s' in WaveSpawn event definition.\n", data->GetString() );
delete eventInfo;
return NULL;
}
}
return eventInfo;
}
static CHandle<CBaseEntity> s_lastTeleporter = NULL;
static float s_flLastTeleportTime = -1;
//-----------------------------------------------------------------------
// Given a named entity, select a random invader teleporter with the same name and
// return it's WorldSpaceCenter.
SpawnLocationResult DoTeleporterOverride( CBaseEntity *spawnEnt, Vector& vSpawnPosition, bool bClosestPointOnNav )
{
CUtlVector< CBaseEntity * > teleporterVector;
for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
{
CBaseObject *pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
if ( pObj->GetType() != OBJ_TELEPORTER )
continue;
if ( pObj->GetTeamNumber() != TF_TEAM_PVE_INVADERS )
continue;
if ( pObj->IsBuilding() )
continue;
if ( pObj->HasSapper() )
continue;
if ( pObj->IsPlasmaDisabled() )
continue;
CObjectTeleporter *teleporter = assert_cast< CObjectTeleporter* >( pObj );
const CUtlStringList& teleportWhereNames = teleporter->GetTeleportWhere();
const char* pszSpawnPointName = STRING( spawnEnt->GetEntityName() );
for ( int iTelePoints =0; iTelePoints<teleportWhereNames.Count(); ++iTelePoints )
{
// check if this teleporter can replace the original spawn point
if ( FStrEq( teleportWhereNames[iTelePoints], pszSpawnPointName ) )
{
teleporterVector.AddToTail( teleporter );
break;
}
}
}
if ( teleporterVector.Count() > 0 )
{
int which = RandomInt( 0, teleporterVector.Count()-1 );
vSpawnPosition = teleporterVector[ which ]->WorldSpaceCenter();
s_lastTeleporter = teleporterVector[ which ];
return SPAWN_LOCATION_TELEPORTER;
}
CTFNavArea *pNav = (CTFNavArea *)TheNavMesh->GetNearestNavArea( spawnEnt->WorldSpaceCenter() );
if ( !pNav )
return SPAWN_LOCATION_NOT_FOUND;
if ( bClosestPointOnNav )
{
pNav->GetClosestPointOnArea( spawnEnt->WorldSpaceCenter(), &vSpawnPosition );
}
else
{
vSpawnPosition = pNav->GetCenter();
}
return SPAWN_LOCATION_NAV;
}
//-----------------------------------------------------------------------
void OnBotTeleported( CTFBot* bot )
{
const Vector& origin = s_lastTeleporter->GetAbsOrigin();
// don't too many sound and effect when lots of bots teleporting in short time.
if ( gpGlobals->curtime - s_flLastTeleportTime > 0.1f )
{
CPVSFilter filter( origin );
#if 0
// These are pretty, but they're chewing into our particle budget (1000 particles each!)
// They're also basically invisible because bots spawn in ubered.
TE_TFParticleEffect( filter, 0.0, "teleported_blue", origin, vec3_angle );
TE_TFParticleEffect( filter, 0.0, "player_sparkles_blue", origin, vec3_angle );
#endif
s_lastTeleporter->EmitSound( "MVM.Robot_Teleporter_Deliver" );
s_flLastTeleportTime = gpGlobals->curtime;
}
// force bot to face in the direction specified by the teleporter
Vector vForward;
AngleVectors( s_lastTeleporter->GetAbsAngles(), &vForward, NULL, NULL );
bot->GetLocomotionInterface()->FaceTowards( bot->GetAbsOrigin() + 50 * vForward );
// spy shouldn't get any effect from the teleporter
if ( !bot->IsPlayerClass( TF_CLASS_SPY ) )
{
bot->TeleportEffect();
// invading bots get uber while they leave their spawn so they don't drop their cash where players can't pick it up
float flUberTime = tf_mvm_engineer_teleporter_uber_duration.GetFloat();
bot->m_Shared.AddCond( TF_COND_INVULNERABLE, flUberTime );
bot->m_Shared.AddCond( TF_COND_INVULNERABLE_WEARINGOFF, flUberTime );
}
}
//-----------------------------------------------------------------------
// CSpawnLocation
//-----------------------------------------------------------------------
CSpawnLocation::CSpawnLocation()
{
m_relative = UNDEFINED;
m_teamSpawnVector.RemoveAll();
m_nSpawnCount = 0;
m_nRandomSeed = RandomInt( 0, 9999 );
m_bClosestPointOnNav = false;
}
//-----------------------------------------------------------------------
// Return true if we successfully parse a "Where" clause
bool CSpawnLocation::Parse( KeyValues *data )
{
const char *name = data->GetName();
const char *value = data->GetString();
if ( Q_strlen( name ) <= 0 )
{
return false;
}
if ( FStrEq( name, "Where" ) || FStrEq( name, "ClosestPoint" ) )
{
if ( FStrEq( value, "Ahead" ) )
{
m_relative = AHEAD;
}
else if ( FStrEq( value, "Behind" ) )
{
m_relative = BEHIND;
}
else if ( FStrEq( value, "Anywhere" ) )
{
m_relative = ANYWHERE;
}
else
{
m_bClosestPointOnNav = FStrEq( name, "ClosestPoint" );
// collect entities with given name
bool bFound = false;
for ( int i=0; i<ITFTeamSpawnAutoList::AutoList().Count(); ++i )
{
CTFTeamSpawn* pTeamSpawn = static_cast< CTFTeamSpawn* >( ITFTeamSpawnAutoList::AutoList()[i] );
if ( FStrEq( STRING( pTeamSpawn->GetEntityName() ), value ) )
{
m_teamSpawnVector.AddToTail( pTeamSpawn );
bFound = true;
}
}
if ( !bFound )
{
Warning( "Invalid Where argument '%s'\n", value );
return false;
}
}
return true;
}
return false;
}
//-----------------------------------------------------------------------
SpawnLocationResult CSpawnLocation::FindSpawnLocation( Vector& vSpawnPosition )
{
TFTeamSpawnVector_t activeSpawn;
for ( int i=0; i<m_teamSpawnVector.Count(); ++i )
{
if ( m_teamSpawnVector[i]->IsDisabled() )
continue;
activeSpawn.AddToTail( m_teamSpawnVector[i] );
}
// treat spawn points as deck of cards. shuffle it when we run out
if ( m_nSpawnCount >= activeSpawn.Count() )
{
m_nRandomSeed = RandomInt( 0, 9999 );
m_nSpawnCount = 0;
}
CUniformRandomStream randomSpawn;
randomSpawn.SetSeed( m_nRandomSeed );
activeSpawn.Shuffle( &randomSpawn );
if ( activeSpawn.Count() > 0 )
{
// if any invading teleporters exist with this name, use them instead
SpawnLocationResult result = DoTeleporterOverride( activeSpawn[ m_nSpawnCount ], vSpawnPosition, m_bClosestPointOnNav );
if ( result != SPAWN_LOCATION_NOT_FOUND )
{
m_nSpawnCount++;
return result;
}
}
CTFNavArea *spawnArea = SelectSpawnArea();
if ( spawnArea )
{
vSpawnPosition = spawnArea->GetCenter();
return SPAWN_LOCATION_NAV;
}
return SPAWN_LOCATION_NOT_FOUND;
}
//-----------------------------------------------------------------------
CTFNavArea *CSpawnLocation::SelectSpawnArea( void ) const
{
VPROF_BUDGET( "CSpawnLocation::SelectSpawnArea", "NextBot" );
if ( m_relative == UNDEFINED )
{
return NULL;
}
#ifdef TF_RAID_MODE
CTFPlayer *farRaider = g_pRaidLogic->GetFarthestAlongRaider();
if ( !farRaider )
{
return NULL;
}
#endif // TF_RAID_MODE
//
// Collect all areas surrounding the invading team and
// build a vector sorted by increasing incursion distance
//
CUtlSortVector< CTFNavArea *, CTFNavAreaIncursionLess > theaterAreaVector;
CTFNavArea::MakeNewTFMarker();
CTeam *team = GetGlobalTeam( TF_TEAM_BLUE );
for( int t=0; t<team->GetNumPlayers(); ++t )
{
CTFPlayer *teamMember = (CTFPlayer *)team->GetPlayer(t);
if ( !teamMember->IsAlive() )
continue;
CTFBot *bot = ToTFBot( teamMember );
if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
continue;
if ( teamMember->GetLastKnownArea() == NULL )
continue;
// collect areas surrounding this invader
CUtlVector< CNavArea * > nearbyAreaVector;
CollectSurroundingAreas( &nearbyAreaVector, teamMember->GetLastKnownArea(), tf_populator_active_buffer_range.GetFloat() );
for( int i=0; i<nearbyAreaVector.Count(); ++i )
{
CTFNavArea *area = (CTFNavArea *)nearbyAreaVector[i];
if ( !area->IsTFMarked() )
{
area->TFMark();
if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
continue;
if ( !area->IsValidForWanderingPopulation() )
continue;
theaterAreaVector.Insert( area );
if ( tf_populator_debug.GetBool() )
{
TheTFNavMesh()->AddToSelectedSet( area );
}
}
}
}
if ( theaterAreaVector.Count() == 0 )
{
if ( tf_populator_debug.GetBool() )
{
DevMsg( "%3.2f: SelectSpawnArea: Empty theater!\n", gpGlobals->curtime );
}
return NULL;
}
const int maxRetries = 5;
CTFNavArea *spawnArea = NULL;
for( int r=0; r<maxRetries; ++r )
{
int which = 0;
switch( m_relative )
{
case AHEAD:
// areas are sorted from behind to ahead - weight the selection to choose ahead
which = SkewedRandomValue() * theaterAreaVector.Count();
break;
case BEHIND:
// areas are sorted from behind to ahead - weight the selection to choose behind
which = ( 1.0f - SkewedRandomValue() ) * theaterAreaVector.Count();
break;
case ANYWHERE:
// choose any valid area at random
which = RandomFloat( 0.0f, 1.0f ) * theaterAreaVector.Count();
break;
}
if ( which >= theaterAreaVector.Count() )
which = theaterAreaVector.Count()-1;
spawnArea = theaterAreaVector[ which ];
// well behaved spawn area
return spawnArea;
}
return NULL;
}
//-----------------------------------------------------------------------
// CMissionPopulator
//-----------------------------------------------------------------------
CMissionPopulator::CMissionPopulator( CPopulationManager *manager ) : IPopulator( manager )
{
m_mission = CTFBot::NO_MISSION;
m_initialCooldown = 0.0f;
m_cooldownDuration = 0.0f;
m_desiredCount = 0;
m_beginAtWaveIndex = 0;
m_stopAtWaveIndex = 99999;
m_state = NOT_STARTED;
}
//-----------------------------------------------------------------------
bool CMissionPopulator::Parse( KeyValues *values )
{
int waveDuration = 99999;
for ( KeyValues *data = values->GetFirstSubKey(); data != NULL; data = data->GetNextKey() )
{
const char *name = data->GetName();
if ( Q_strlen( name ) <= 0 )
{
continue;
}
if ( m_where.Parse( data ) )
{
continue;
}
if ( !Q_stricmp( name, "Objective" ) )
{
if ( !Q_stricmp( data->GetString(), "DestroySentries" ) )
{
m_mission = CTFBot::MISSION_DESTROY_SENTRIES;
}
else if ( !Q_stricmp( data->GetString(), "Sniper" ) )
{
m_mission = CTFBot::MISSION_SNIPER;
}
else if ( !Q_stricmp( data->GetString(), "Spy" ) )
{
m_mission = CTFBot::MISSION_SPY;
}
else if ( !Q_stricmp( data->GetString(), "Engineer" ) )
{
m_mission = CTFBot::MISSION_ENGINEER;
}
else if ( !Q_stricmp( data->GetString(), "SeekAndDestroy" ) )
{
m_mission = CTFBot::MISSION_DESTROY_SENTRIES;
}
else
{
Warning( "Invalid mission '%s'\n", data->GetString() );
return false;
}
}
else if ( !Q_stricmp( name, "InitialCooldown" ) )
{
m_initialCooldown = data->GetFloat();
}
else if ( !Q_stricmp( name, "CooldownTime" ) )
{
m_cooldownDuration = data->GetFloat();
}
else if ( !Q_stricmp( name, "BeginAtWave" ) )
{
m_beginAtWaveIndex = data->GetInt() - 1; // internally counts from 0
}
else if ( !Q_stricmp( name, "RunForThisManyWaves" ) )
{
waveDuration = data->GetInt();
}
else if ( !Q_stricmp( name, "DesiredCount" ) )
{
m_desiredCount = data->GetInt();
}
else
{
m_spawner = IPopulationSpawner::ParseSpawner( this, data );
if ( m_spawner == NULL )
{
Warning( "Unknown attribute '%s' in Mission definition.\n", name );
}
}
}
m_stopAtWaveIndex = m_beginAtWaveIndex + waveDuration;
return true;
}
//--------------------------------------------------------------------------------------------------------
// Dispatch sentry killer squads
bool CMissionPopulator::UpdateMissionDestroySentries( void )
{
VPROF_BUDGET( "CMissionPopulator::UpdateMissionDestroySentries", "NextBot" );
if ( !m_cooldownTimer.IsElapsed() )
{
return false;
}
if ( !m_checkForDangerousSentriesTimer.IsElapsed() )
{
return false;
}
if( g_pPopulationManager->IsSpawningPaused() )
{
return false;
}
m_checkForDangerousSentriesTimer.Start( RandomFloat( 5.0f, 10.0f ) );
// collect all of the dangerous sentries
CUtlVector< CObjectSentrygun * > dangerousSentryVector;
int nDmgLimit = 0;
int nKillLimit = 0;
GetManager()->GetSentryBusterDamageAndKillThreshold( nDmgLimit, nKillLimit );
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 )
{
CTFPlayer *sentryOwner = pObj->GetOwner();
if ( sentryOwner )
{
int nDmgDone = sentryOwner->GetAccumulatedSentryGunDamageDealt();
int nKillsMade = sentryOwner->GetAccumulatedSentryGunKillCount();
if ( nDmgDone >= nDmgLimit || nKillsMade >= nKillLimit )
{
dangerousSentryVector.AddToTail( static_cast< CObjectSentrygun* >( pObj ) );
}
}
}
}
}
CUtlVector< CTFPlayer * > livePlayerVector;
CollectPlayers( &livePlayerVector, TF_TEAM_PVE_INVADERS, COLLECT_ONLY_LIVING_PLAYERS );
// dispatch a sentry busting squad for each dangerous sentry
bool didSpawn = false;
for( int i=0; i<dangerousSentryVector.Count(); ++i )
{
CObjectSentrygun *targetSentry = dangerousSentryVector[i];
// if there is already a squad out there destroying this sentry, don't spawn another one
int j;
for( j=0; j<livePlayerVector.Count(); ++j )
{
CTFBot *bot = dynamic_cast<CTFBot *>( livePlayerVector[j] );
if ( bot && bot->HasMission( CTFBot::MISSION_DESTROY_SENTRIES ) && bot->GetMissionTarget() == targetSentry )
{
// there is already a sentry busting squad active for this sentry
break;
}
}
if ( j < livePlayerVector.Count() )
{
continue;
}
// spawn a sentry buster squad to destroy this sentry
Vector vSpawnPosition;
SpawnLocationResult spawnLocationResult = m_where.FindSpawnLocation( vSpawnPosition );
if ( spawnLocationResult != SPAWN_LOCATION_NOT_FOUND )
{
EntityHandleVector_t spawnVector;
if ( m_spawner && m_spawner->Spawn( vSpawnPosition, &spawnVector ) )
{
// success
if ( tf_populator_debug.GetBool() )
{
DevMsg( "MANN VS MACHINE: %3.2f: <<<< Spawning Sentry Busting Mission >>>>\n", gpGlobals->curtime );
}
for( int k=0; k<spawnVector.Count(); ++k )
{
CTFBot *bot = ToTFBot( spawnVector[k] );
if ( bot )
{
bot->SetFlagTarget( NULL );
bot->SetMission( CTFBot::MISSION_DESTROY_SENTRIES );
bot->SetMissionTarget( targetSentry );
// force an update to start the behavior so we can set the sentry
bot->Update();
bot->MarkAsMissionEnemy();
didSpawn = true;
bot->GetPlayerClass()->SetCustomModel( g_szBotBossSentryBusterModel, USE_CLASS_ANIMATIONS );
bot->UpdateModel();
bot->SetBloodColor( DONT_BLEED );
if ( TFObjectiveResource() )
{
unsigned int iFlags = MVM_CLASS_FLAG_MISSION;
if ( bot->IsMiniBoss() )
{
iFlags |= MVM_CLASS_FLAG_MINIBOSS;
}
if ( bot->HasAttribute( CTFBot::ALWAYS_CRIT ) )
{
iFlags |= MVM_CLASS_FLAG_ALWAYSCRIT;
}
TFObjectiveResource()->IncrementMannVsMachineWaveClassCount( m_spawner->GetClassIcon( k ), iFlags );
}
if ( TFGameRules() )
{
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_SENTRY_BUSTER, TF_TEAM_PVE_DEFENDERS );
}
// what bot should do after spawning at teleporter exit
if ( spawnLocationResult == SPAWN_LOCATION_TELEPORTER )
{
OnBotTeleported( bot );
}
}
}
}
}
else if ( tf_populator_debug.GetBool() )
{
Warning( "MissionPopulator: %3.2f: Can't find a place to spawn a sentry destroying squad\n", gpGlobals->curtime );
}
}
if ( didSpawn )
{
float flCoolDown = m_cooldownDuration;
CWave *pWave = GetManager()->GetCurrentWave();
if ( pWave )
{
pWave->IncrementSentryBustersSpawned();
if ( TFGameRules() )
{
if ( pWave->NumSentryBustersSpawned() > 1 )
{
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Sentry_Buster_Alert_Another" );
}
else
{
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Sentry_Buster_Alert" );
}
}
flCoolDown = m_cooldownDuration + pWave->NumSentryBustersKilled() * m_cooldownDuration;
pWave->ResetSentryBustersKilled();
}
m_cooldownTimer.Start( flCoolDown );
}
return didSpawn;
}
//-----------------------------------------------------------------------
bool CMissionPopulator::UpdateMission( CTFBot::MissionType mission )
{
VPROF_BUDGET( "CMissionPopulator::UpdateMission", "NextBot" );
int activeMissionMembers = 0;
CUtlVector< CTFPlayer * > livePlayerVector;
CollectPlayers( &livePlayerVector, TF_TEAM_PVE_INVADERS, COLLECT_ONLY_LIVING_PLAYERS );
for( int i=0; i<livePlayerVector.Count(); ++i )
{
CTFBot *pBot = dynamic_cast<CTFBot *>( livePlayerVector[i] );
if ( pBot && pBot ->HasMission( mission ) )
{
++activeMissionMembers;
}
}
if( g_pPopulationManager->IsSpawningPaused() )
{
return false;
}
if ( activeMissionMembers > 0 )
{
// wait until prior mission is dead
// cooldown is time after death of last mission member
m_cooldownTimer.Start( m_cooldownDuration );
return false;
}
if ( !m_cooldownTimer.IsElapsed() )
{
return false;
}
// are there enough free slots?
int currentEnemyCount = GetGlobalTeam( TF_TEAM_PVE_INVADERS )->GetNumPlayers();
if ( currentEnemyCount + m_desiredCount > CPopulationManager::MVM_INVADERS_TEAM_SIZE )
{
// not enough slots yet
if ( tf_populator_debug.GetBool() )
{
DevMsg( "MANN VS MACHINE: %3.2f: Waiting for slots to spawn mission.\n", gpGlobals->curtime );
}
return false;
}
if ( tf_populator_debug.GetBool() )
{
DevMsg( "MANN VS MACHINE: %3.2f: <<<< Spawning Mission >>>>\n", gpGlobals->curtime );
}
int nSniperCount = 0;
FOR_EACH_VEC( livePlayerVector, iLiveBot )
{
CTFBot *pBot = dynamic_cast<CTFBot *>( livePlayerVector[iLiveBot] );
if ( pBot && pBot->IsPlayerClass( TF_CLASS_SNIPER ) )
{
nSniperCount++;
}
}
// dispatch mission members
for( int iDesiredCount=0; iDesiredCount<m_desiredCount; ++iDesiredCount )
{
Vector vSpawnPosition;
SpawnLocationResult spawnLocationResult = m_where.FindSpawnLocation( vSpawnPosition );
if ( spawnLocationResult != SPAWN_LOCATION_NOT_FOUND )
{
EntityHandleVector_t spawnedVector;
if ( m_spawner && m_spawner->Spawn( vSpawnPosition, &spawnedVector ) )
{
// success
for( int iSpawn=0; iSpawn<spawnedVector.Count(); ++iSpawn )
{
CTFBot *bot = ToTFBot( spawnedVector[iSpawn] );
if ( bot )
{
bot->SetFlagTarget( NULL );
bot->SetMission( mission );
//bot->SetMissionString( "" );
bot->MarkAsMissionEnemy();
if ( TFObjectiveResource() )
{
unsigned int iFlags = MVM_CLASS_FLAG_MISSION;
if ( bot->IsMiniBoss() )
{
iFlags |= MVM_CLASS_FLAG_MINIBOSS;
}
if ( bot->HasAttribute( CTFBot::ALWAYS_CRIT ) )
{
iFlags |= MVM_CLASS_FLAG_ALWAYSCRIT;
}
TFObjectiveResource()->IncrementMannVsMachineWaveClassCount( bot->GetPlayerClass()->GetClassIconName(), iFlags );
}
// Response rules stuff for MvM
if ( TFGameRules()->IsMannVsMachineMode() )
{
// Only have defenders announce the arrival of the first enemy Sniper
if ( bot->HasMission( CTFBot::MISSION_SNIPER ) )
{
nSniperCount++;
if ( nSniperCount == 1 )
{
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_SNIPER_CALLOUT, TF_TEAM_PVE_DEFENDERS );
}
}
}
// what bot should do after spawning at teleporter exit
if ( spawnLocationResult == SPAWN_LOCATION_TELEPORTER )
{
OnBotTeleported( bot );
}
}
}
}
}
else
{
if ( tf_populator_debug.GetBool() )
{
Warning( "MissionPopulator: %3.2f: Skipped a member - can't find a place to spawn\n", gpGlobals->curtime );
}
}
}
m_cooldownTimer.Start( m_cooldownDuration );
return true;
}
//-----------------------------------------------------------------------
void CMissionPopulator::Update( void )
{
VPROF_BUDGET( "CMissionPopulator::Update", "NextBot" );
if ( TFGameRules()->InSetup() ||
GetManager()->GetWaveNumber() < m_beginAtWaveIndex ||
GetManager()->GetWaveNumber() >= m_stopAtWaveIndex ||
TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() )
{
m_state = NOT_STARTED;
return;
}
if ( m_state == NOT_STARTED )
{
if ( m_initialCooldown > 0.0f )
{
m_state = INITIAL_COOLDOWN;
m_cooldownTimer.Start( m_initialCooldown );
return;
}
m_state = RUNNING;
m_cooldownTimer.Invalidate();
}
else if ( m_state == INITIAL_COOLDOWN )
{
if ( !m_cooldownTimer.IsElapsed() )
{
return;
}
m_state = RUNNING;
m_cooldownTimer.Invalidate();
}
switch( m_mission )
{
case CTFBot::MISSION_SEEK_AND_DESTROY:
break;
case CTFBot::MISSION_DESTROY_SENTRIES:
UpdateMissionDestroySentries();
break;
case CTFBot::MISSION_SNIPER:
case CTFBot::MISSION_SPY:
case CTFBot::MISSION_ENGINEER:
UpdateMission( m_mission );
break;
}
}
void CMissionPopulator::UnpauseSpawning( void )
{
m_cooldownTimer.Start( m_cooldownDuration );
m_checkForDangerousSentriesTimer.Start( RandomFloat( 5.0f, 10.0f ) );
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
CRandomPlacementPopulator::CRandomPlacementPopulator( CPopulationManager *manager ) : IPopulator( manager )
{
m_count = 0;
m_minSeparation = 0.0f;
m_navAreaFilter = 0xFFFFFFFF;
}
//-----------------------------------------------------------------------
bool CRandomPlacementPopulator::Parse( KeyValues *values )
{
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, "Count" ) )
{
m_count = data->GetInt();
}
else if ( !Q_stricmp( name, "MinimumSeparation" ) )
{
m_minSeparation = data->GetFloat();
}
else if ( !Q_stricmp( name, "NavAreaFilter" ) )
{
if ( !Q_stricmp( data->GetString(), "SENTRY_SPOT" ) )
{
m_navAreaFilter = TF_NAV_SENTRY_SPOT;
}
else if ( !Q_stricmp( data->GetString(), "SNIPER_SPOT" ) )
{
m_navAreaFilter = TF_NAV_SNIPER_SPOT;
}
else
{
Warning( "Unknown NavAreaFilter value '%s'\n", data->GetString() );
}
}
else
{
m_spawner = IPopulationSpawner::ParseSpawner( this, data );
if ( m_spawner == NULL )
{
Warning( "Unknown attribute '%s' in RandomPlacement definition.\n", name );
}
}
}
return true;
}
//-----------------------------------------------------------------------
// Create initial population at start of scenario
void CRandomPlacementPopulator::PostInitialize( void )
{
int i;
CUtlVector< CTFNavArea * > candidateAreaVector;
for( i=0; i<TheNavAreas.Count(); ++i )
{
CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];
if ( area->HasAttributeTF( m_navAreaFilter ) )
{
candidateAreaVector.AddToTail( area );
}
}
CUtlVector< CTFNavArea * > selectedAreaVector;
SelectSeparatedShuffleSet< CTFNavArea >( m_count, m_minSeparation, candidateAreaVector, &selectedAreaVector );
if ( m_spawner )
{
for( i=0; i<selectedAreaVector.Count(); ++i )
{
m_spawner->Spawn( selectedAreaVector[i]->GetCenter() );
}
}
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
CPeriodicSpawnPopulator::CPeriodicSpawnPopulator( CPopulationManager *manager ) : IPopulator( manager )
{
m_minInterval = 30.0f;
m_maxInterval = 30.0f;
}
//-----------------------------------------------------------------------
bool CPeriodicSpawnPopulator::Parse( KeyValues *values )
{
for ( KeyValues *data = values->GetFirstSubKey(); data != NULL; data = data->GetNextKey() )
{
const char *name = data->GetName();
if ( Q_strlen( name ) <= 0 )
{
continue;
}
if ( m_where.Parse( data ) )
{
continue;
}
if ( !Q_stricmp( name, "When" ) )
{
if ( data->GetFirstSubKey() )
{
for ( KeyValues *whenData = data->GetFirstSubKey(); whenData != NULL; whenData = whenData->GetNextKey() )
{
if ( !Q_stricmp( whenData->GetName(), "MinInterval" ) )
{
m_minInterval = whenData->GetFloat();
}
else if ( !Q_stricmp( whenData->GetName(), "MaxInterval" ) )
{
m_maxInterval = whenData->GetFloat();
}
else
{
Warning( "Invalid field '%s' encountered in When\n", whenData->GetName() );
return false;
}
}
}
else
{
// single constant value
m_minInterval = data->GetFloat();
m_maxInterval = m_minInterval;
}
}
else
{
m_spawner = IPopulationSpawner::ParseSpawner( this, data );
if ( m_spawner == NULL )
{
Warning( "Unknown attribute '%s' in PeriodicSpawn definition.\n", name );
}
}
}
return true;
}
//-----------------------------------------------------------------------
// Create initial population at start of scenario
void CPeriodicSpawnPopulator::PostInitialize( void )
{
m_timer.Start( RandomFloat( m_minInterval, m_maxInterval ) );
}
//-----------------------------------------------------------------------
// Continuously invoked to modify population over time
void CPeriodicSpawnPopulator::Update( void )
{
if ( m_timer.IsElapsed() && !g_pPopulationManager->IsSpawningPaused() )
{
m_timer.Start( RandomFloat( m_minInterval, m_maxInterval ) );
Vector vSpawnPosition;
SpawnLocationResult spawnLocationResult = m_where.FindSpawnLocation( vSpawnPosition );
if ( spawnLocationResult != SPAWN_LOCATION_NOT_FOUND )
{
EntityHandleVector_t spawnedVector;
if ( m_spawner && m_spawner->Spawn( vSpawnPosition, &spawnedVector ) )
{
// success
for( int i=0; i<spawnedVector.Count(); ++i )
{
CTFBot *bot = ToTFBot( spawnedVector[i] );
if ( bot )
{
// what bot should do after spawning at teleporter exit
if ( spawnLocationResult == SPAWN_LOCATION_TELEPORTER )
{
OnBotTeleported( bot );
}
}
}
return;
}
}
// retry soon, in the hopes constraints have changed
m_timer.Start( 2.0f );
}
}
void CPeriodicSpawnPopulator::UnpauseSpawning( void )
{
m_timer.Start( RandomFloat( m_minInterval, m_maxInterval ) );
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
int CWaveSpawnPopulator::m_reservedPlayerSlotCount = 0;
CWaveSpawnPopulator::CWaveSpawnPopulator( CPopulationManager *manager ) : IPopulator( manager )
{
m_totalCount = 0;
m_maxActive = 999;
m_spawnCount = 1;
m_waitBeforeStarting = 0.0f;
m_waitBetweenSpawns = 0.0f;
m_bWaitBetweenSpawnAfterDeath = false;
m_totalCurrency = -1;
SetState( PENDING );
m_startWaveOutput = NULL;
m_firstSpawnOutput = NULL;
m_lastSpawnOutput = NULL;
m_doneOutput = NULL;
m_bSupportWave = false;
m_bLimitedSupport = false;
m_pParent = NULL;
m_bRandomSpawn = false;
m_spawnLocationResult = SPAWN_LOCATION_NOT_FOUND;
}
//-----------------------------------------------------------------------
CWaveSpawnPopulator::~CWaveSpawnPopulator()
{
delete m_startWaveOutput;
delete m_firstSpawnOutput;
delete m_lastSpawnOutput;
delete m_doneOutput;
}
//-----------------------------------------------------------------------
void CWaveSpawnPopulator::ForceFinish()
{
if ( m_state < WAIT_FOR_ALL_DEAD )
{
SetState( WAIT_FOR_ALL_DEAD );
}
else if ( m_state != WAIT_FOR_ALL_DEAD )
{
SetState( DONE );
}
FOR_EACH_VEC( m_activeVector, i )
{
// Move bots over to spectator
CTFBot *pBot = ToTFBot( m_activeVector[i] );
if ( pBot )
{
pBot->ChangeTeam( TEAM_SPECTATOR, false, true );
}
else // Other things just get removed. (ie. Tanks)
{
m_activeVector[i]->Remove();
}
}
m_activeVector.Purge();
}
//-----------------------------------------------------------------------
bool CWaveSpawnPopulator::Parse( KeyValues *values )
{
// First, see if we have any Template keys
KeyValues *pTemplate = values->FindKey( "Template" );
if ( pTemplate )
{
KeyValues *pTemplateKV = GetManager()->GetTemplate( pTemplate->GetString() );
if ( pTemplateKV )
{
// Pump all the keys into ourself now
if ( Parse( pTemplateKV ) == false )
{
return false;
}
}
else
{
Warning( "Unknown Template '%s' in WaveSpawn definition\n", pTemplate->GetString() );
}
}
for ( KeyValues *data = values->GetFirstSubKey(); data != NULL; data = data->GetNextKey() )
{
const char *name = data->GetName();
if ( Q_strlen( name ) <= 0 )
{
continue;
}
if ( m_where.Parse( data ) )
{
continue;
}
// Skip templates when looping through the rest of the keys
if ( !Q_stricmp( name, "Template" ) )
continue;
if ( !Q_stricmp( name, "TotalCount" ) )
{
m_totalCount = data->GetInt();
}
else if ( !Q_stricmp( name, "MaxActive" ) )
{
m_maxActive = data->GetInt();
}
else if ( !Q_stricmp( name, "SpawnCount" ) )
{
m_spawnCount = data->GetInt();
}
else if ( !Q_stricmp( name, "WaitBeforeStarting" ) )
{
m_waitBeforeStarting = data->GetFloat();
}
else if ( !Q_stricmp( name, "WaitBetweenSpawns" ) )
{
if ( m_waitBetweenSpawns != 0.f && m_bWaitBetweenSpawnAfterDeath )
{
Warning( "Already specified WaitBetweenSpawnsAfterDeath time, WaitBetweenSpawns won't be used\n" );
continue;
}
m_waitBetweenSpawns = data->GetFloat();
}
else if ( !Q_stricmp( name, "WaitBetweenSpawnsAfterDeath" ) )
{
if ( m_waitBetweenSpawns != 0.f )
{
Warning( "Already specified WaitBetweenSpawns time, WaitBetweenSpawnsAfterDeath won't be used\n" );
continue;
}
m_bWaitBetweenSpawnAfterDeath = true;
m_waitBetweenSpawns = data->GetFloat();
}
else if ( !Q_stricmp( name, "StartWaveWarningSound" ) )
{
m_startWaveWarningSound.sprintf( "%s", data->GetString() );
}
else if ( !Q_stricmp( name, "StartWaveOutput" ) )
{
m_startWaveOutput = ParseEvent( data );
}
else if ( !Q_stricmp( name, "FirstSpawnWarningSound" ) )
{
m_firstSpawnWarningSound.sprintf( "%s", data->GetString() );
}
else if ( !Q_stricmp( name, "FirstSpawnOutput" ) )
{
m_firstSpawnOutput = ParseEvent( data );
}
else if ( !Q_stricmp( name, "LastSpawnWarningSound" ) )
{
m_lastSpawnWarningSound.sprintf( "%s", data->GetString() );
}
else if ( !Q_stricmp( name, "LastSpawnOutput" ) )
{
m_lastSpawnOutput = ParseEvent( data );
}
else if ( !Q_stricmp( name, "DoneWarningSound" ) )
{
m_doneWarningSound.sprintf( "%s", data->GetString() );
}
else if ( !Q_stricmp( name, "DoneOutput" ) )
{
m_doneOutput = ParseEvent( data );
}
else if ( !Q_stricmp( name, "TotalCurrency" ) )
{
m_totalCurrency = data->GetInt();
}
else if ( !Q_stricmp( name, "Name" ) )
{
m_name = data->GetString();
}
else if ( !Q_stricmp( name, "WaitForAllSpawned" ) )
{
m_waitForAllSpawned = data->GetString();
}
else if ( !Q_stricmp( name, "WaitForAllDead" ) )
{
m_waitForAllDead = data->GetString();
}
else if ( !Q_stricmp( name, "Support" ) )
{
m_bLimitedSupport = !Q_stricmp( data->GetString(), "Limited" );
m_bSupportWave = true;
}
else if ( !Q_stricmp( name, "RandomSpawn" ) )
{
m_bRandomSpawn = data->GetBool();
}
else
{
m_spawner = IPopulationSpawner::ParseSpawner( this, data );
if ( m_spawner == NULL )
{
Warning( "Unknown attribute '%s' in WaveSpawn definition.\n", name );
}
}
// These allow us to avoid rounding errors later when divvying money to bots
m_unallocatedCurrency = m_totalCurrency;
m_remainingCount = m_totalCount;
}
return true;
}
//-----------------------------------------------------------------------
void CWaveSpawnPopulator::OnPlayerKilled( CTFPlayer *corpse )
{
m_activeVector.FindAndFastRemove( corpse );
}
//-----------------------------------------------------------------------
bool CWaveSpawnPopulator::IsFinishedSpawning( void )
{
if ( m_bSupportWave && !m_bLimitedSupport )
{
// support waves are never done spawning until
// we get OnNonSupportWavesDone called
return false;
}
return ( m_countSpawnedSoFar >= m_totalCount );
}
//-----------------------------------------------------------------------
void CWaveSpawnPopulator::SetState( InternalStateType eState )
{
m_state = eState;
switch( m_state )
{
case PENDING:
case PRE_SPAWN_DELAY:
case SPAWNING:
break;
case WAIT_FOR_ALL_DEAD:
// last spawn has occurred
if ( m_lastSpawnWarningSound.Length() > 0 )
{
TFGameRules()->BroadcastSound( 255, m_lastSpawnWarningSound );
}
FireEvent( m_lastSpawnOutput, "LastSpawnOutput" );
if ( tf_populator_debug.GetBool() )
{
DevMsg( "%3.2f: WaveSpawn(%s) started WAIT_FOR_ALL_DEAD\n", gpGlobals->curtime, m_name.IsEmpty() ? "" : m_name.Get() );
}
break;
case DONE:
if ( m_doneWarningSound.Length() > 0 )
{
TFGameRules()->BroadcastSound( 255, m_doneWarningSound );
}
FireEvent( m_doneOutput, "DoneOutput" );
if ( tf_populator_debug.GetBool() )
{
DevMsg( "%3.2f: WaveSpawn(%s) DONE\n", gpGlobals->curtime, m_name.IsEmpty() ? "" : m_name.Get() );
}
break;
}
}
//-----------------------------------------------------------------------
void CWaveSpawnPopulator::OnNonSupportWavesDone( void )
{
if ( m_bSupportWave )
{
switch( m_state )
{
case PENDING:
case PRE_SPAWN_DELAY:
SetState( DONE );
break;
case SPAWNING:
case WAIT_FOR_ALL_DEAD:
if ( TFGameRules() && ( m_unallocatedCurrency > 0 ) )
{
TFGameRules()->DistributeCurrencyAmount( m_unallocatedCurrency, NULL, true, true );
m_unallocatedCurrency = 0;
}
SetState( WAIT_FOR_ALL_DEAD );
case DONE:
break;
}
}
}
//-----------------------------------------------------------------------
int CWaveSpawnPopulator::GetCurrencyAmountPerDeath( void )
{
int nCurrency = 0;
if ( m_bSupportWave )
{
if ( m_state == WAIT_FOR_ALL_DEAD )
{
// we're still in the m_ActiveVector at this point so the number of players
// in the vector is the division of the money since we're done spawning
m_remainingCount = m_activeVector.Count();
}
}
if ( m_unallocatedCurrency > 0 )
{
// We shouldn't be back in here if our remaining count is 0
Assert ( m_remainingCount > 0 );
// Band-aid for playtest
m_remainingCount = m_remainingCount <= 0 ? 1 : m_remainingCount;
nCurrency = m_unallocatedCurrency / m_remainingCount;
m_unallocatedCurrency -= nCurrency;
m_remainingCount--;
}
return nCurrency;
}
//-----------------------------------------------------------------------
// Continuously invoked to modify population over time
void CWaveSpawnPopulator::Update( void )
{
VPROF_BUDGET( "CWaveSpawnPopulator::Update", "NextBot" );
switch( m_state )
{
case DONE:
return;
case PENDING:
m_timer.Start( m_waitBeforeStarting );
SetState( PRE_SPAWN_DELAY );
// zero this here to ensure it is cleared between Waves
// since all WaveSpawns start at the same time at the beginning of a Wave
m_reservedPlayerSlotCount = 0;
if ( m_startWaveWarningSound.Length() > 0 )
{
TFGameRules()->BroadcastSound( 255, m_startWaveWarningSound );
}
FireEvent( m_startWaveOutput, "StartWaveOutput" );
if ( tf_populator_debug.GetBool() )
{
DevMsg( "%3.2f: WaveSpawn(%s) started PRE_SPAWN_DELAY\n", gpGlobals->curtime, m_name.IsEmpty() ? "" : m_name.Get() );
}
break;
case PRE_SPAWN_DELAY:
if ( m_timer.IsElapsed() )
{
m_countSpawnedSoFar = 0;
m_myReservedSlotCount = 0;
SetState( SPAWNING );
if ( m_firstSpawnWarningSound.Length() > 0 )
{
TFGameRules()->BroadcastSound( 255, m_firstSpawnWarningSound );
}
FireEvent( m_firstSpawnOutput, "FirstSpawnOutput" );
if ( tf_populator_debug.GetBool() )
{
DevMsg( "%3.2f: WaveSpawn(%s) started SPAWNING\n", gpGlobals->curtime, m_name.IsEmpty() ? "" : m_name.Get() );
}
}
break;
case SPAWNING:
if ( m_timer.IsElapsed() )
{
if( g_pPopulationManager->IsSpawningPaused() )
{
return;
}
if ( !m_spawner )
{
Warning( "Invalid spawner\n" );
SetState( DONE );
return;
}
// count up how many entities we've spawned are still active
int currentActive = 0;
for( int i=0; i<m_activeVector.Count(); ++i )
{
if ( m_activeVector[i] != NULL && m_activeVector[i]->IsAlive() )
{
++currentActive;
}
}
if ( m_bWaitBetweenSpawnAfterDeath )
{
if ( currentActive == 0 )
{
if ( m_spawnLocationResult != SPAWN_LOCATION_NOT_FOUND )
{
// free up the current spawn area so we select a new one for the next group
m_spawnLocationResult = SPAWN_LOCATION_NOT_FOUND;
if ( m_waitBetweenSpawns > 0.0f )
{
// start delay
m_timer.Start( m_waitBetweenSpawns );
}
// wait for the timer
return;
}
}
else
{
// wait until all current actives are dead
return;
}
}
if ( currentActive >= m_maxActive )
{
// we've reached our allowed cap
return;
}
if ( m_myReservedSlotCount <= 0 )
{
// are there enough free slots?
if ( ( m_maxActive - currentActive ) < m_spawnCount )
{
// not enough room to spawn a group so wait
return;
}
int currentEnemyCount = GetGlobalTeam( TF_TEAM_PVE_INVADERS )->GetNumPlayers();
if ( currentEnemyCount + m_spawnCount + m_reservedPlayerSlotCount > CPopulationManager::MVM_INVADERS_TEAM_SIZE )
{
// no space right now
return;
}
// there is room - reserve our slots to ensure another concurrent WaveSpawn doesn't consume them
m_reservedPlayerSlotCount += m_spawnCount;
m_myReservedSlotCount = m_spawnCount;
}
bool bTeleported = ( m_spawnLocationResult == SPAWN_LOCATION_TELEPORTER );
Vector vSpawnPosition = vec3_origin;
if ( m_spawner && m_spawner->IsWhereRequired() )
{
// try to look for a spawn point or a new teleport location
if ( m_spawnLocationResult == SPAWN_LOCATION_NOT_FOUND || m_spawnLocationResult == SPAWN_LOCATION_TELEPORTER )
{
m_spawnLocationResult = m_where.FindSpawnLocation( m_vSpawnPosition );
if ( m_spawnLocationResult == SPAWN_LOCATION_NOT_FOUND )
{
// try again
return;
}
}
vSpawnPosition = m_vSpawnPosition;
bTeleported = ( m_spawnLocationResult == SPAWN_LOCATION_TELEPORTER );
// reset m_pCurrentSpawnArea if we want to pick a new spawn area for the next bot to spawn
if ( m_bRandomSpawn )
{
m_spawnLocationResult = SPAWN_LOCATION_NOT_FOUND;
}
}
EntityHandleVector_t m_justSpawnedVector;
if ( m_spawner && m_spawner->Spawn( vSpawnPosition, &m_justSpawnedVector ) )
{
// successfully spawned
FOR_EACH_VEC( m_justSpawnedVector, i )
{
if ( m_justSpawnedVector[i].Get() == NULL )
continue;
CTFBot *bot = ToTFBot( m_justSpawnedVector[i] );
if ( bot )
{
bot->SetCustomCurrencyWorth( 0 );
bot->SetWaveSpawnPopulator( this );
// Allows client UI to know if a specific spawner is active
TFObjectiveResource()->SetMannVsMachineWaveClassActive( bot->GetPlayerClass()->GetClassIconName() );
if ( IsLimitedSupportWave() )
{
bot->MarkAsLimitedSupportEnemy();
}
// what bot should do after spawning at teleporter exit
if ( bTeleported )
{
OnBotTeleported( bot );
}
}
else
{
CTFTankBoss *tank = dynamic_cast< CTFTankBoss * >( m_justSpawnedVector[i].Get() );
if ( tank )
{
tank->SetCurrencyValue( 0 );
tank->SetWaveSpawnPopulator( this );
m_pParent->IncrementTanksSpawned();
}
}
}
int justSpawnedCount = m_justSpawnedVector.Count();
m_countSpawnedSoFar += justSpawnedCount;
// release our reserved slots
int slotsToReleaseCount = ( justSpawnedCount <= m_myReservedSlotCount ) ? justSpawnedCount : m_myReservedSlotCount;
m_myReservedSlotCount -= slotsToReleaseCount;
m_reservedPlayerSlotCount -= slotsToReleaseCount;
// somehow, duplicate entries can end up in m_activeVector if we just AddVectorToTail() - look into this
for( int i = 0 ; i < m_justSpawnedVector.Count() ; ++i )
{
CBaseEntity *newEntity = m_justSpawnedVector[i];
for( int j = 0 ; j < m_activeVector.Count() ; ++j )
{
if ( m_activeVector[j] == NULL )
continue;
if ( m_activeVector[j]->entindex() == newEntity->entindex() )
{
Warning( "WaveSpawn duplicate entry in active vector\n" );
continue;
}
}
m_activeVector.AddToTail( newEntity );
}
if ( IsFinishedSpawning() )
{
SetState( WAIT_FOR_ALL_DEAD );
return;
}
// successfully spawned a group of SpawnCount entities
if ( m_myReservedSlotCount <= 0 && !m_bWaitBetweenSpawnAfterDeath )
{
// free up the current spawn area so we select a new one for the next group
m_spawnLocationResult = SPAWN_LOCATION_NOT_FOUND;
if ( m_waitBetweenSpawns > 0.0f )
{
// start delay
m_timer.Start( m_waitBetweenSpawns );
}
}
// not done yet
return;
}
// couldn't spawn - retry soon
m_timer.Start( 1.0f );
}
break;
case WAIT_FOR_ALL_DEAD:
FOR_EACH_VEC( m_activeVector, i )
{
if ( m_activeVector[i] != NULL && m_activeVector[i]->IsAlive() )
{
// not done yet
return;
}
}
// everyone we spawned is dead
SetState( DONE );
break;
} // switch
// not done yet
}
//-------------------------------------------------------------------------
// End CWaveSpawnPopulator
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// CWave
//-------------------------------------------------------------------------
CWave::CWave( CPopulationManager *manager ) : IPopulator( manager )
{
m_iEnemyCount = 0;
m_nTanksSpawned = 0;
m_nSentryBustersSpawned = 0;
m_nNumEngineersTeleportSpawned = 0;
m_nNumSentryBustersKilled = 0;
m_totalCurrency = 0;
m_waitWhenDone = 0.0f;
m_isStarted = false;
m_bFiredInitWaveOutput = false;
m_startOutput = NULL;
m_doneOutput = NULL;
m_initOutput = NULL;
m_bCheckBonusCreditsMin = true;
m_bCheckBonusCreditsMax = true;
m_bPlayedUpgradeAlert = false;
m_flBonusCreditsTime = 0;
m_isEveryContainedWaveSpawnDone = false;
m_flStartTime = 0;
m_doneTimer.Invalidate();
}
//-------------------------------------------------------------------------
CWave::~CWave()
{
delete m_startOutput;
delete m_doneOutput;
delete m_initOutput;
m_waveSpawnVector.PurgeAndDeleteElements();
}
//-------------------------------------------------------------------------
bool CWave::Parse( KeyValues *data )
{
m_iEnemyCount = 0;
m_nWaveClassCounts.RemoveAll();
m_totalCurrency = 0;
FOR_EACH_SUBKEY( data, kvWave )
{
if ( !Q_stricmp( kvWave->GetName(), "WaveSpawn" ) )
{
CWaveSpawnPopulator *wavePopulator = new CWaveSpawnPopulator( GetManager() );
if ( wavePopulator->Parse( kvWave ) == false )
{
Warning( "Error reading WaveSpawn definition\n" );
return false;
}
m_waveSpawnVector.AddToTail( wavePopulator );
if ( !wavePopulator->IsSupportWave() )
{
// this is a total of all enemies we have to fight that are NOT support enemies
m_iEnemyCount += wavePopulator->m_totalCount;
}
m_totalCurrency += wavePopulator->m_totalCurrency;
wavePopulator->SetParent( this );
if ( wavePopulator->m_spawner )
{
if ( wavePopulator->m_spawner->IsVarious() )
{
for ( int i = 0; i < wavePopulator->m_totalCount; ++i )
{
unsigned int iFlags = wavePopulator->IsSupportWave() ? MVM_CLASS_FLAG_SUPPORT : MVM_CLASS_FLAG_NORMAL;
if ( wavePopulator->m_spawner->IsMiniBoss( i ) )
{
iFlags |= MVM_CLASS_FLAG_MINIBOSS;
}
if ( wavePopulator->m_spawner->HasAttribute( CTFBot::ALWAYS_CRIT, i ) )
{
iFlags |= MVM_CLASS_FLAG_ALWAYSCRIT;
}
if ( wavePopulator->IsLimitedSupportWave() )
{
iFlags |= MVM_CLASS_FLAG_SUPPORT_LIMITED;
}
AddClassType( wavePopulator->m_spawner->GetClassIcon( i ), 1, iFlags );
}
}
else
{
unsigned int iFlags = wavePopulator->IsSupportWave() ? MVM_CLASS_FLAG_SUPPORT : MVM_CLASS_FLAG_NORMAL;
if ( wavePopulator->m_spawner->IsMiniBoss() )
{
iFlags |= MVM_CLASS_FLAG_MINIBOSS;
}
if ( wavePopulator->m_spawner->HasAttribute( CTFBot::ALWAYS_CRIT ) )
{
iFlags |= MVM_CLASS_FLAG_ALWAYSCRIT;
}
if ( wavePopulator->IsLimitedSupportWave() )
{
iFlags |= MVM_CLASS_FLAG_SUPPORT_LIMITED;
}
AddClassType( wavePopulator->m_spawner->GetClassIcon(), wavePopulator->m_totalCount, iFlags );
}
}
}
else if ( !Q_stricmp( kvWave->GetName(), "Sound" ) )
{
m_soundName.sprintf( "%s", kvWave->GetString() );
}
else if ( !Q_stricmp( kvWave->GetName(), "Description" ) )
{
m_description.sprintf( "%s", kvWave->GetString() );
}
else if ( !Q_stricmp( kvWave->GetName(), "WaitWhenDone" ) )
{
m_waitWhenDone = kvWave->GetFloat();
}
else if ( !Q_stricmp( kvWave->GetName(), "Checkpoint" ) )
{
//m_isCheckpoint = true;
}
else if ( !Q_stricmp( kvWave->GetName(), "StartWaveOutput" ) )
{
m_startOutput = ParseEvent( kvWave );
}
else if ( !Q_stricmp( kvWave->GetName(), "DoneOutput" ) )
{
m_doneOutput = ParseEvent( kvWave );
}
else if ( !Q_stricmp( kvWave->GetName(), "InitWaveOutput" ) )
{
m_initOutput = ParseEvent( kvWave );
}
else
{
Warning( "Unknown attribute '%s' in Wave definition.\n", kvWave->GetName() );
}
}
return true;
}
//-------------------------------------------------------------------------
// If we are the currently active wave, update all contained WaveSpawns.
void CWave::Update( void )
{
VPROF_BUDGET( "CWave::Update", "NextBot" );
if ( TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
{
ActiveWaveUpdate();
}
else if ( TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS || TFGameRules()->State_Get() == GR_STATE_TEAM_WIN )
{
WaveIntermissionUpdate();
}
// is the wave done?
if ( m_isEveryContainedWaveSpawnDone && TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
{
if ( GetManager()->IsBonusRound() && GetManager()->GetBonusBoss() && GetManager()->GetBonusBoss()->IsAlive() )
{
return;
}
WaveCompleteUpdate();
}
}
//-------------------------------------------------------------------------
void CWave::OnPlayerKilled( CTFPlayer *corpse )
{
for( int i=0; i<m_waveSpawnVector.Count(); ++i )
{
m_waveSpawnVector[i]->OnPlayerKilled( corpse );
}
}
//-------------------------------------------------------------------------
bool CWave::HasEventChangeAttributes( const char* pszEventName ) const
{
for ( int i=0; i<m_waveSpawnVector.Count(); ++i )
{
if ( m_waveSpawnVector[i]->HasEventChangeAttributes( pszEventName ) )
{
return true;
}
}
return false;
}
//-------------------------------------------------------------------------
void CWave::ForceFinish()
{
FOR_EACH_VEC( m_waveSpawnVector, i )
{
m_waveSpawnVector[i]->ForceFinish();
}
}
//-------------------------------------------------------------------------
void CWave::ForceReset()
{
m_isStarted = false;
m_bFiredInitWaveOutput = false;
m_flBonusCreditsTime = 0;
m_isEveryContainedWaveSpawnDone = false;
m_flStartTime = 0;
m_doneTimer.Invalidate();
FOR_EACH_VEC( m_waveSpawnVector, i )
{
m_waveSpawnVector[i]->ForceReset();
}
}
//-------------------------------------------------------------------------
CWaveSpawnPopulator *CWave::FindWaveSpawnPopulator( const char *name )
{
FOR_EACH_VEC( m_waveSpawnVector, i )
{
CWaveSpawnPopulator *waveSpawnPopulator = m_waveSpawnVector[i];
if ( !Q_stricmp( waveSpawnPopulator->m_name.Get(), name ) )
{
return waveSpawnPopulator;
}
}
return NULL;
}
//-------------------------------------------------------------------------
void CWave::AddClassType( string_t iszClassIconName, int nCount, unsigned int iFlags )
{
int nIndex = -1;
for ( int nClass = 0; nClass < m_nWaveClassCounts.Count(); ++nClass )
{
if ( ( m_nWaveClassCounts[ nClass ].iszClassIconName == iszClassIconName ) && ( m_nWaveClassCounts[ nClass ].iFlags & iFlags ) )
{
nIndex = nClass;
break;
}
}
if ( nIndex == -1 )
{
nIndex = m_nWaveClassCounts.AddToTail();
m_nWaveClassCounts[ nIndex ].iszClassIconName = iszClassIconName;
m_nWaveClassCounts[ nIndex ].nClassCount = 0;
m_nWaveClassCounts[ nIndex ].iFlags = MVM_CLASS_FLAG_NONE;
}
m_nWaveClassCounts[ nIndex ].nClassCount += nCount;
m_nWaveClassCounts[ nIndex ].iFlags |= iFlags;
}
//-------------------------------------------------------------------------
// Private
//-------------------------------------------------------------------------
bool CWave::IsDoneWithNonSupportWaves( void )
{
FOR_EACH_VEC( m_waveSpawnVector, i )
{
CWaveSpawnPopulator *waveSpawnPopulator = m_waveSpawnVector[i];
if ( waveSpawnPopulator )
{
if ( !waveSpawnPopulator->IsSupportWave() && !waveSpawnPopulator->IsDone() )
return false;
}
}
return true;
}
//-------------------------------------------------------------------------
void CWave::ActiveWaveUpdate( void )
{
VPROF_BUDGET( "CWave::ActiveWaveUpdate", "NextBot" );
if ( !m_isStarted )
{
// Delay the start of the next wave
if ( GetManager()->IsInEndlessWaves() && m_flStartTime > gpGlobals->curtime )
return;
// wave just started
m_isStarted = true;
FireEvent( m_startOutput, "StartWaveOutput" );
if ( m_soundName.Length() > 0 )
{
TFGameRules()->BroadcastSound( 255, m_soundName );
}
GetManager()->AdjustMinPlayerSpawnTime();
}
m_isEveryContainedWaveSpawnDone = true;
if ( GetManager()->IsBonusRound() )
{
return;
}
// update each contained WaveSpawn
FOR_EACH_VEC( m_waveSpawnVector, i )
{
CWaveSpawnPopulator *waveSpawnPopulator = m_waveSpawnVector[i];
bool bWaiting = false;
// check if this WaveSpawn is waiting for another WaveSpawn to be done spawning players
if ( !waveSpawnPopulator->m_waitForAllSpawned.IsEmpty() )
{
char *name = waveSpawnPopulator->m_waitForAllSpawned.GetForModify();
FOR_EACH_VEC( m_waveSpawnVector, j )
{
CWaveSpawnPopulator *predecessor = m_waveSpawnVector[j];
if ( predecessor && !Q_stricmp( predecessor->m_name.Get(), name ) )
{
if ( !predecessor->IsDoneSpawningBots() )
{
bWaiting = true;
break;
}
}
}
}
if ( !bWaiting )
{
// check if this WaveSpawn is waiting for another WaveSpawn's players to all be dead
if ( !waveSpawnPopulator->m_waitForAllDead.IsEmpty() )
{
const char *name = waveSpawnPopulator->m_waitForAllDead.Get();
FOR_EACH_VEC( m_waveSpawnVector, j )
{
CWaveSpawnPopulator *predecessor = m_waveSpawnVector[j];
if ( predecessor && !Q_stricmp( predecessor->m_name.Get(), name ) )
{
if ( !predecessor->IsDone() )
{
bWaiting = true;
break;
}
}
}
}
}
if ( bWaiting )
{
continue;
}
waveSpawnPopulator->Update();
m_isEveryContainedWaveSpawnDone &= waveSpawnPopulator->IsDone();
}
if ( IsDoneWithNonSupportWaves() )
{
// Loop through and tell all the WaveSpawns
FOR_EACH_VEC( m_waveSpawnVector, i )
{
CWaveSpawnPopulator *waveSpawnPopulator = m_waveSpawnVector[i];
waveSpawnPopulator->OnNonSupportWavesDone();
}
for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
{
// Now let's kill everyone left on the attacking team
CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
if ( pPlayer && pPlayer->IsAlive() &&
( ( pPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) || pPlayer->m_Shared.InCond( TF_COND_REPROGRAMMED ) ) )
{
pPlayer->CommitSuicide( true, false );
}
}
}
}
//-------------------------------------------------------------------------
void CWave::WaveCompleteUpdate( void )
{
bool bHasTank = NumTanksSpawned() >= 1;
FireEvent( m_doneOutput, "DoneOutput" );
bool bLastWave = ( GetManager()->GetWaveNumber() + 1 ) >= GetManager()->GetTotalWaveCount();
bool bMidWave = ( GetManager()->GetWaveNumber() + 1 ) >= ( GetManager()->GetTotalWaveCount() / 2 );
bool bAdvancedPopfile = ( g_pPopulationManager ? g_pPopulationManager->IsAdvancedPopFile() : false );
IGameEvent *event = gameeventmanager->CreateEvent( "mvm_wave_complete" );
if ( event )
{
event->SetBool( "advanced", bAdvancedPopfile );
gameeventmanager->FireEvent( event );
}
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
if ( bAdvancedPopfile )
{
CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
if ( pMaster && ( pMaster->GetNumPoints() > 0 ) )
{
if ( pMaster->GetNumPointsOwnedByTeam( TF_TEAM_PVE_DEFENDERS ) == pMaster->GetNumPoints() )
{
IGameEvent *event = gameeventmanager->CreateEvent( "mvm_adv_wave_complete_no_gates" );
if ( event )
{
event->SetInt( "index", GetManager()->GetWaveNumber() );
gameeventmanager->FireEvent( event );
}
}
}
}
}
if ( bLastWave && !GetManager()->IsInEndlessWaves() )
{
GetManager()->MvMVictory();
if ( TFGameRules() )
{
if ( GTFGCClientSystem()->GetMatch() && GTFGCClientSystem()->GetMatch()->m_eMatchGroup == k_nMatchGroup_MvM_MannUp )
{
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Manned_Up_Wave_End" );
}
else
{
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Final_Wave_End" );
}
TFGameRules()->BroadcastSound( 255, "music.mvm_end_last_wave" );
}
event = gameeventmanager->CreateEvent( "mvm_mission_complete" );
if ( event )
{
event->SetString( "mission", GetManager()->GetPopulationFilename() );
gameeventmanager->FireEvent( event );
}
}
else
{
if ( TFGameRules() )
{
TFGameRules()->BroadcastSound( 255,"Announcer.MVM_Wave_End" );
if( bHasTank )
{
TFGameRules()->BroadcastSound( 255, "music.mvm_end_tank_wave" );
}
else if( bMidWave )
{
TFGameRules()->BroadcastSound( 255, "music.mvm_end_mid_wave" );
}
else
{
TFGameRules()->BroadcastSound( 255, "music.mvm_end_wave" );
}
}
}
CBroadcastRecipientFilter filter;
filter.MakeReliable();
UserMessageBegin( filter, "MVMAnnouncement" );
WRITE_CHAR( TF_MVM_ANNOUNCEMENT_WAVE_COMPLETE );
WRITE_CHAR( GetManager()->GetWaveNumber() );
MessageEnd();
if ( TFObjectiveResource() )
{
// if we're using a timer between waves...
if ( !g_pPopulationManager->GetWavesUseReadyBetween() )
{
if ( !m_doneTimer.HasStarted() )
{
m_doneTimer.Start( m_waitWhenDone );
}
TFObjectiveResource()->SetMannVsMachineNextWaveTime( gpGlobals->curtime + m_waitWhenDone );
}
// Force respawn dead defenders
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_PVE_DEFENDERS );
FOR_EACH_VEC( playerVector, i )
{
if ( !playerVector[i]->IsAlive() )
{
playerVector[i]->ForceRespawn();
}
// clear player's accumulated sentry damage
playerVector[i]->ResetAccumulatedSentryGunDamageDealt();
playerVector[i]->ResetAccumulatedSentryGunKillCount();
}
}
GetManager()->WaveEnd( true );
}
//-------------------------------------------------------------------------
void CWave::WaveIntermissionUpdate ( void )
{
if ( !m_bFiredInitWaveOutput )
{
FireEvent( m_initOutput, "InitWaveOutput" );
m_bFiredInitWaveOutput = true;
}
if ( m_GetUpgradesAlertTimer.HasStarted() && m_GetUpgradesAlertTimer.IsElapsed() )
{
// Monitor for full wave currency collection bonus
if ( ( m_bCheckBonusCreditsMin || m_bCheckBonusCreditsMax ) && gpGlobals->curtime > m_flBonusCreditsTime )
{
int nWaveNum = GetManager()->GetWaveNumber() - 1;
int nDropped = MannVsMachineStats_GetDroppedCredits( nWaveNum );
int nAcquired = MannVsMachineStats_GetAcquiredCredits( nWaveNum, false );
float flRatioCollected = clamp( ( (float)nAcquired / (float)nDropped ), 0.1f, 1.f );
float flMinBonus = tf_mvm_currency_bonus_ratio_min.GetFloat();
float flMaxBonus = tf_mvm_currency_bonus_ratio_max.GetFloat();
Assert( flMinBonus <= flMaxBonus );
if ( flMinBonus > flMaxBonus )
flMinBonus = flMaxBonus;
// Max bonus
if ( m_bCheckBonusCreditsMax && nDropped > 0 && flRatioCollected >= flMaxBonus )
{
int nAmount = (float)TFGameRules()->CalculateCurrencyAmount_ByType( TF_CURRENCY_WAVE_COLLECTION_BONUS ) * 0.5f;
TFGameRules()->DistributeCurrencyAmount( nAmount, NULL, true, false, true );
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Bonus" );
IGameEvent *event = gameeventmanager->CreateEvent( "mvm_creditbonus_wave" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
m_bCheckBonusCreditsMax = false;
m_GetUpgradesAlertTimer.Reset();
}
// Min bonus
if ( m_bCheckBonusCreditsMin && nDropped > 0 && flRatioCollected >= flMinBonus )
{
int nAmount = (float)TFGameRules()->CalculateCurrencyAmount_ByType( TF_CURRENCY_WAVE_COLLECTION_BONUS ) * 0.5f;
TFGameRules()->DistributeCurrencyAmount( nAmount, NULL, true, false, true );
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Bonus" );
IGameEvent *event = gameeventmanager->CreateEvent( "mvm_creditbonus_wave" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
m_bCheckBonusCreditsMin = false;
}
else if ( !m_bPlayedUpgradeAlert )
{
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Get_To_Upgrade" );
m_bPlayedUpgradeAlert = true;
m_GetUpgradesAlertTimer.Reset();
}
m_flBonusCreditsTime = gpGlobals->curtime + 0.25f;
}
}
// When we use a timer between waves, start it here
if ( m_doneTimer.HasStarted() && m_doneTimer.IsElapsed() )
{
m_doneTimer.Invalidate();
GetManager()->StartCurrentWave();
}
}
//-------------------------------------------------------------------------
// End CWave
//-------------------------------------------------------------------------