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.
 
 
 
 
 
 

1890 lines
47 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_population_manager.h"
#include "tf_team.h"
#include "tf_obj_sentrygun.h"
#include "tf_weapon_medigun.h"
#include "tf_tank_boss.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.h"
#include "etwprof.h"
extern ConVar tf_mvm_skill;
extern ConVar tf_mm_trusted;
extern ConVar tf_populator_debug;
extern ConVar tf_populator_active_buffer_range;
extern ConVar tf_mvm_miniboss_scale;
ConVar tf_debug_placement_failure( "tf_debug_placement_failure", "0", FCVAR_CHEAT );
LINK_ENTITY_TO_CLASS( populator_internal_spawn_point, CPopulatorInternalSpawnPoint );
CHandle< CPopulatorInternalSpawnPoint > g_internalSpawnPoint = NULL;
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if a player has room to spawn at the given position
*/
bool IsSpaceToSpawnHere( const Vector &where )
{
// make sure a player will fit here
trace_t result;
float bloat = 5.0f;
UTIL_TraceHull( where, where, VEC_HULL_MIN - Vector( bloat, bloat, 0 ), VEC_HULL_MAX + Vector( bloat, bloat, bloat ), MASK_SOLID | CONTENTS_PLAYERCLIP, NULL, COLLISION_GROUP_PLAYER_MOVEMENT, &result );
if ( tf_debug_placement_failure.GetBool() && result.fraction < 1.0f )
{
NDebugOverlay::Cross3D( where, 5.0f, 255, 100, 0, true, 99999.9f );
}
return result.fraction >= 1.0;
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
/*static*/ IPopulationSpawner *IPopulationSpawner::ParseSpawner( IPopulator *populator, KeyValues *data )
{
const char *name = data->GetName();
if ( Q_strlen( name ) <= 0 )
{
return NULL;
}
if ( !Q_stricmp( name, "TFBot" ) )
{
CTFBotSpawner *botSpawner = new CTFBotSpawner( populator );
if ( botSpawner->Parse( data ) == false )
{
Warning( "Warning reading TFBot spawner definition\n" );
delete botSpawner;
return NULL;
}
return botSpawner;
}
else if ( !Q_stricmp( name, "Tank" ) )
{
CTankSpawner *tankSpawner = new CTankSpawner( populator );
if ( tankSpawner->Parse( data ) == false )
{
Warning( "Warning reading Tank spawner definition\n" );
delete tankSpawner;
return NULL;
}
return tankSpawner;
}
else if ( !Q_stricmp( name, "SentryGun" ) )
{
CSentryGunSpawner *sentrySpawner = new CSentryGunSpawner( populator );
if ( sentrySpawner->Parse( data ) == false )
{
Warning( "Warning reading SentryGun spawner definition\n" );
delete sentrySpawner;
return NULL;
}
return sentrySpawner;
}
else if ( !Q_stricmp( name, "Squad" ) )
{
CSquadSpawner *squadSpawner = new CSquadSpawner( populator );
if ( squadSpawner->Parse( data ) == false )
{
Warning( "Warning reading Squad spawner definition\n" );
delete squadSpawner;
return NULL;
}
return squadSpawner;
}
else if ( !Q_stricmp( name, "Mob" ) )
{
CMobSpawner *mobSpawner = new CMobSpawner( populator );
if ( mobSpawner->Parse( data ) == false )
{
Warning( "Warning reading Mob spawner definition\n" );
delete mobSpawner;
return NULL;
}
return mobSpawner;
}
else if ( !Q_stricmp( name, "RandomChoice" ) )
{
CRandomChoiceSpawner *randomSpawner = new CRandomChoiceSpawner( populator );
if ( randomSpawner->Parse( data ) == false )
{
Warning( "Warning reading RandomChoice spawner definition\n" );
delete randomSpawner;
return NULL;
}
return randomSpawner;
}
return 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;
}
//-----------------------------------------------------------------------
// CRandomChoiceSpawner
//-----------------------------------------------------------------------
CRandomChoiceSpawner::CRandomChoiceSpawner( IPopulator *populator ) : IPopulationSpawner( populator )
{
m_spawnerVector.RemoveAll();
m_nRandomPickDecision.RemoveAll();
m_nNumSpawned = 0;
}
//-----------------------------------------------------------------------
CRandomChoiceSpawner::~CRandomChoiceSpawner()
{
for( int i=0; i<m_spawnerVector.Count(); ++i )
{
delete m_spawnerVector[i];
}
m_spawnerVector.RemoveAll();
m_nRandomPickDecision.RemoveAll();
m_nNumSpawned = 0;
}
//-----------------------------------------------------------------------
bool CRandomChoiceSpawner::Parse( KeyValues *values )
{
for ( KeyValues *data = values->GetFirstSubKey(); data != NULL; data = data->GetNextKey() )
{
const char *name = data->GetName();
if ( Q_strlen( name ) <= 0 )
{
continue;
}
IPopulationSpawner *spawner = ParseSpawner( m_populator, data );
if ( spawner == NULL )
{
Warning( "Unknown attribute '%s' in RandomChoice definition.\n", name );
}
else
{
m_spawnerVector.AddToTail( spawner );
}
}
return true;
}
//-----------------------------------------------------------------------
bool CRandomChoiceSpawner::Spawn( const Vector &here, EntityHandleVector_t *result )
{
if ( m_spawnerVector.Count() < 1 )
return false;
int nCurrentCount = m_nRandomPickDecision.Count();
int nNumToAdd = ( m_nNumSpawned + 1 ) - nCurrentCount;
if ( nNumToAdd > 0 )
{
m_nRandomPickDecision.AddMultipleToTail( nNumToAdd );
for ( int i = nCurrentCount; i < m_nNumSpawned + 1; ++i )
{
m_nRandomPickDecision[ i ] = RandomInt( 0, m_spawnerVector.Count() - 1 );
}
}
bool bResult = m_spawnerVector[ m_nRandomPickDecision[ m_nNumSpawned ] ]->Spawn( here, result );
m_nNumSpawned++;
return bResult;
}
int CRandomChoiceSpawner::GetClass( int nSpawnNum /*= -1*/ )
{
if ( nSpawnNum < 0 )
{
return TF_CLASS_UNDEFINED;
}
int nCurrentCount = m_nRandomPickDecision.Count();
int nNumToAdd = ( nSpawnNum + 1 ) - nCurrentCount;
if ( nNumToAdd > 0 )
{
m_nRandomPickDecision.AddMultipleToTail( nNumToAdd );
for ( int i = nCurrentCount; i < nSpawnNum + 1; ++i )
{
m_nRandomPickDecision[ i ] = RandomInt( 0, m_spawnerVector.Count() - 1 );
}
}
if ( !m_spawnerVector[ m_nRandomPickDecision[ nSpawnNum ] ]->IsVarious() )
{
return m_spawnerVector[ m_nRandomPickDecision[ nSpawnNum ] ]->GetClass();
}
else
{
// FIXME: Nested complex spawner types... need a method for counting these.
Assert( 1 );
DevWarning( "Nested complex spawner types... need a method for counting these." );
return TF_CLASS_UNDEFINED;
}
}
string_t CRandomChoiceSpawner::GetClassIcon( int nSpawnNum /*= -1*/ )
{
if ( nSpawnNum < 0 )
{
return NULL_STRING;
}
int nCurrentCount = m_nRandomPickDecision.Count();
int nNumToAdd = ( nSpawnNum + 1 ) - nCurrentCount;
if ( nNumToAdd > 0 )
{
m_nRandomPickDecision.AddMultipleToTail( nNumToAdd );
for ( int i = nCurrentCount; i < nSpawnNum + 1; ++i )
{
m_nRandomPickDecision[ i ] = RandomInt( 0, m_spawnerVector.Count() - 1 );
}
}
if ( !m_spawnerVector[ m_nRandomPickDecision[ nSpawnNum ] ]->IsVarious() )
{
return m_spawnerVector[ m_nRandomPickDecision[ nSpawnNum ] ]->GetClassIcon();
}
else
{
// FIXME: Nested complex spawner types... need a method for counting these.
Assert( 1 );
DevWarning( "Nested complex spawner types... need a method for counting these." );
return NULL_STRING;
}
}
int CRandomChoiceSpawner::GetHealth( int nSpawnNum /*= -1*/ )
{
if ( nSpawnNum < 0 )
{
return 0;
}
int nCurrentCount = m_nRandomPickDecision.Count();
int nNumToAdd = ( nSpawnNum + 1 ) - nCurrentCount;
if ( nNumToAdd > 0 )
{
m_nRandomPickDecision.AddMultipleToTail( nNumToAdd );
for ( int i = nCurrentCount; i < nSpawnNum + 1; ++i )
{
m_nRandomPickDecision[ i ] = RandomInt( 0, m_spawnerVector.Count() - 1 );
}
}
if ( !m_spawnerVector[ m_nRandomPickDecision[ nSpawnNum ] ]->IsVarious() )
{
return m_spawnerVector[ m_nRandomPickDecision[ nSpawnNum ] ]->GetHealth();
}
else
{
// FIXME: Nested complex spawner types... need a method for counting these.
Assert( 1 );
DevWarning( "Nested complex spawner types... need a method for counting these." );
return 0;
}
}
bool CRandomChoiceSpawner::IsMiniBoss( int nSpawnNum /*= -1*/ )
{
if ( nSpawnNum < 0 )
{
return false;
}
int nCurrentCount = m_nRandomPickDecision.Count();
int nNumToAdd = ( nSpawnNum + 1 ) - nCurrentCount;
if ( nNumToAdd > 0 )
{
m_nRandomPickDecision.AddMultipleToTail( nNumToAdd );
for ( int i = nCurrentCount; i < nSpawnNum + 1; ++i )
{
m_nRandomPickDecision[ i ] = RandomInt( 0, m_spawnerVector.Count() - 1 );
}
}
if ( !m_spawnerVector[ m_nRandomPickDecision[ nSpawnNum ] ]->IsVarious() )
{
return m_spawnerVector[ m_nRandomPickDecision[ nSpawnNum ] ]->IsMiniBoss();
}
else
{
// FIXME: Nested complex spawner types... need a method for counting these.
Assert( 1 );
DevWarning( "Nested complex spawner types... need a method for counting these." );
return false;
}
}
bool CRandomChoiceSpawner::HasAttribute( CTFBot::AttributeType type, int nSpawnNum /*= -1*/ )
{
if ( nSpawnNum < 0 )
{
return false;
}
int nCurrentCount = m_nRandomPickDecision.Count();
int nNumToAdd = ( nSpawnNum + 1 ) - nCurrentCount;
if ( nNumToAdd > 0 )
{
m_nRandomPickDecision.AddMultipleToTail( nNumToAdd );
for ( int i = nCurrentCount; i < nSpawnNum + 1; ++i )
{
m_nRandomPickDecision[ i ] = RandomInt( 0, m_spawnerVector.Count() - 1 );
}
}
if ( !m_spawnerVector[ m_nRandomPickDecision[ nSpawnNum ] ]->IsVarious() )
{
return m_spawnerVector[ m_nRandomPickDecision[ nSpawnNum ] ]->HasAttribute( type, nSpawnNum );
}
else
{
// FIXME: Nested complex spawner types... need a method for counting these.
Assert( 1 );
DevWarning( "Nested complex spawner types... need a method for counting these." );
return false;
}
}
bool CRandomChoiceSpawner::HasEventChangeAttributes( const char* pszEventName ) const
{
for ( int i=0; i<m_spawnerVector.Count(); ++i )
{
m_spawnerVector[i]->HasEventChangeAttributes( pszEventName );
}
return false;
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
CTFBotSpawner::CTFBotSpawner( IPopulator *populator ) : IPopulationSpawner( populator )
{
m_class = TF_CLASS_UNDEFINED;
m_iszClassIcon = NULL_STRING;
m_health = -1; // default health
m_scale = -1.0f; // default scale
m_flAutoJumpMin = m_flAutoJumpMax = 0.f; // default AutoJumpMin/Max
m_defaultAttributes.Reset();
}
static void ParseCharacterAttributes( CTFBot::EventChangeAttributes_t& event, KeyValues *data )
{
CUtlVector<static_attrib_t> vecStaticAttribs;
FOR_EACH_SUBKEY( data, pKVAttribute )
{
CUtlVector<CUtlString> vecErrors;
static_attrib_t staticAttrib;
if ( !staticAttrib.BInitFromKV_SingleLine( "CharacterAttributes", pKVAttribute, &vecErrors ) )
{
FOR_EACH_VEC( vecErrors, i )
{
Warning( "TFBotSpawner: attribute error: '%s'\n", vecErrors[i].Get() );
}
continue;
}
vecStaticAttribs.AddToTail( staticAttrib );
}
// found an existing entry for this name -- stomp attribute values if present or add
// new entries if not
FOR_EACH_VEC( vecStaticAttribs, iNewAttribute )
{
const static_attrib_t& newStaticAttrib = vecStaticAttribs[iNewAttribute];
bool bAdd = true;
int iExistingAttribute;
for ( iExistingAttribute = 0; iExistingAttribute < event.m_characterAttributes.Count(); iExistingAttribute++ )
{
// matching existing attribute? stomp value
if ( event.m_characterAttributes[iExistingAttribute].iDefIndex == newStaticAttrib.iDefIndex )
{
// stomp value
event.m_characterAttributes[iExistingAttribute].m_value = newStaticAttrib.m_value;
bAdd = false;
// move on to next new attribute
break;
}
}
if ( bAdd )
{
// couldn't find? add new attribute entry
event.m_characterAttributes.AddToTail( newStaticAttrib );
}
}
}
static void ParseItemAttributes( CTFBot::EventChangeAttributes_t& event, KeyValues *data )
{
const char *pszItemName = NULL;
CUtlVector<static_attrib_t> vecStaticAttribs;
FOR_EACH_SUBKEY( data, pKVSubkey )
{
if ( !Q_stricmp( pKVSubkey->GetName(), "ItemName" ) )
{
if ( pszItemName )
{
Warning( "TFBotSpawner: \"ItemName\" field specified multiple times ('%s' / '%s').\n", pszItemName, pKVSubkey->GetString() );
}
pszItemName = pKVSubkey->GetString();
}
else
{
CUtlVector<CUtlString> vecErrors;
static_attrib_t staticAttrib;
if ( !staticAttrib.BInitFromKV_SingleLine( "ItemAttributes", pKVSubkey, &vecErrors ) )
{
FOR_EACH_VEC( vecErrors, i )
{
Warning( "TFBotSpawner: attribute error: '%s'\n", vecErrors[i].Get() );
}
continue;
}
vecStaticAttribs.AddToTail( staticAttrib );
}
}
if ( !pszItemName )
{
Warning( "TFBotSpawner: need to specify ItemName in ItemAttributes.\n" );
return;
}
// check if the item name is already on the list
FOR_EACH_VEC( event.m_itemsAttributes, i )
{
CTFBot::EventChangeAttributes_t::item_attributes_t& botItemAttrs = event.m_itemsAttributes[i];
if ( !Q_stricmp( botItemAttrs.m_itemName, pszItemName ) )
{
// found an existing entry for this name -- stomp attribute values if present or add
// new entries if not
FOR_EACH_VEC( vecStaticAttribs, iNewAttribute )
{
const static_attrib_t& newStaticAttrib = vecStaticAttribs[iNewAttribute];
int iExistingAttribute;
for ( iExistingAttribute = 0; iExistingAttribute < botItemAttrs.m_attributes.Count(); iExistingAttribute++ )
{
// matching existing attribute? stomp value
if ( botItemAttrs.m_attributes[iExistingAttribute].iDefIndex == newStaticAttrib.iDefIndex )
{
// stomp value
botItemAttrs.m_attributes[iExistingAttribute].m_value = newStaticAttrib.m_value;
// move on to next new attribute
break;
}
}
// couldn't find? add new attribute entry
botItemAttrs.m_attributes.AddToTail( newStaticAttrib );
}
// only one entry expected -- done here, before we add a new one
return;
}
}
// new item
CTFBot::EventChangeAttributes_t::item_attributes_t botItemAttributes;
botItemAttributes.m_itemName = pszItemName;
botItemAttributes.m_attributes.AddVectorToTail( vecStaticAttribs );
event.m_itemsAttributes.AddToTail( botItemAttributes );
}
static bool ParseDynamicAttributes( CTFBot::EventChangeAttributes_t& event, KeyValues* data )
{
const char *name = data->GetName();
const char *value = data->GetString();
if ( !Q_stricmp( name, "Skill" ) )
{
if ( !Q_stricmp( value, "Easy" ) )
{
event.m_skill = CTFBot::EASY;
}
else if ( !Q_stricmp( value, "Normal" ) )
{
event.m_skill = CTFBot::NORMAL;
}
else if ( !Q_stricmp( value, "Hard" ) )
{
event.m_skill = CTFBot::HARD;
}
else if ( !Q_stricmp( value, "Expert" ) )
{
event.m_skill = CTFBot::EXPERT;
}
else
{
Warning( "TFBotSpawner: Invalid skill '%s'\n", value );
return false;
}
}
else if ( !Q_stricmp( name, "WeaponRestrictions" ) )
{
if ( !Q_stricmp( value, "MeleeOnly" ) )
{
event.m_weaponRestriction = CTFBot::MELEE_ONLY;
}
else if ( !Q_stricmp( value, "PrimaryOnly" ) )
{
event.m_weaponRestriction = CTFBot::PRIMARY_ONLY;
}
else if ( !Q_stricmp( value, "SecondaryOnly" ) )
{
event.m_weaponRestriction = CTFBot::SECONDARY_ONLY;
}
else
{
Warning( "TFBotSpawner: Invalid weapon restriction '%s'\n", value );
return false;
}
}
else if ( !Q_stricmp( name, "BehaviorModifiers" ) )
{
// modifying bot attribute flags here due to legacy code
if ( !Q_stricmp( value, "Mobber" ) || !Q_stricmp( value, "Push" ) )
{
event.m_attributeFlags |= CTFBot::AGGRESSIVE;
}
else
{
Warning( "TFBotSpawner: Invalid behavior modifier '%s'\n", value );
return false;
}
}
else if ( !Q_stricmp( name, "Attributes" ) )
{
if ( !Q_stricmp( value, "RemoveOnDeath" ) )
{
event.m_attributeFlags |= CTFBot::REMOVE_ON_DEATH;
}
else if ( !Q_stricmp( value, "Aggressive" ) )
{
event.m_attributeFlags |= CTFBot::AGGRESSIVE;
}
else if ( !Q_stricmp( value, "SuppressFire" ) )
{
event.m_attributeFlags |= CTFBot::SUPPRESS_FIRE;
}
else if ( !Q_stricmp( value, "DisableDodge" ) )
{
event.m_attributeFlags |= CTFBot::DISABLE_DODGE;
}
else if ( !Q_stricmp( value, "BecomeSpectatorOnDeath" ) )
{
event.m_attributeFlags |= CTFBot::BECOME_SPECTATOR_ON_DEATH;
}
else if ( !Q_stricmp( value, "RetainBuildings" ) )
{
event.m_attributeFlags |= CTFBot::RETAIN_BUILDINGS;
}
else if ( !Q_stricmp( value, "SpawnWithFullCharge" ) )
{
event.m_attributeFlags |= CTFBot::SPAWN_WITH_FULL_CHARGE;
}
else if ( !Q_stricmp( value, "AlwaysCrit" ) )
{
event.m_attributeFlags |= CTFBot::ALWAYS_CRIT;
}
else if ( !Q_stricmp( value, "IgnoreEnemies" ) )
{
event.m_attributeFlags |= CTFBot::IGNORE_ENEMIES;
}
else if ( !Q_stricmp( value, "HoldFireUntilFullReload" ) )
{
event.m_attributeFlags |= CTFBot::HOLD_FIRE_UNTIL_FULL_RELOAD;
}
else if ( !Q_stricmp( value, "AlwaysFireWeapon" ) )
{
event.m_attributeFlags |= CTFBot::ALWAYS_FIRE_WEAPON;
}
else if ( !Q_stricmp( value, "TeleportToHint" ) )
{
event.m_attributeFlags |= CTFBot::TELEPORT_TO_HINT;
}
else if ( !Q_stricmp( value, "MiniBoss" ) )
{
event.m_attributeFlags |= CTFBot::MINIBOSS;
}
else if ( !Q_stricmp( value, "UseBossHealthBar" ) )
{
event.m_attributeFlags |= CTFBot::USE_BOSS_HEALTH_BAR;
}
else if ( !Q_stricmp( value, "IgnoreFlag" ) )
{
event.m_attributeFlags |= CTFBot::IGNORE_FLAG;
}
else if ( !Q_stricmp( value, "AutoJump" ) )
{
event.m_attributeFlags |= CTFBot::AUTO_JUMP;
}
else if ( !Q_stricmp( value, "AirChargeOnly" ) )
{
event.m_attributeFlags |= CTFBot::AIR_CHARGE_ONLY;
}
else if( !Q_stricmp( value, "VaccinatorBullets" ) )
{
event.m_attributeFlags |= CTFBot::PREFER_VACCINATOR_BULLETS;
}
else if( !Q_stricmp( value, "VaccinatorBlast" ) )
{
event.m_attributeFlags |= CTFBot::PREFER_VACCINATOR_BLAST;
}
else if( !Q_stricmp( value, "VaccinatorFire" ) )
{
event.m_attributeFlags |= CTFBot::PREFER_VACCINATOR_FIRE;
}
else if( !Q_stricmp( value, "BulletImmune" ) )
{
event.m_attributeFlags |= CTFBot::BULLET_IMMUNE;
}
else if( !Q_stricmp( value, "BlastImmune" ) )
{
event.m_attributeFlags |= CTFBot::BLAST_IMMUNE;
}
else if( !Q_stricmp( value, "FireImmune" ) )
{
event.m_attributeFlags |= CTFBot::FIRE_IMMUNE;
}
else if ( !Q_stricmp( value, "Parachute" ) )
{
event.m_attributeFlags |= CTFBot::PARACHUTE;
}
else if ( !Q_stricmp( value, "ProjectileShield" ) )
{
event.m_attributeFlags |= CTFBot::PROJECTILE_SHIELD;
}
else
{
Warning( "TFBotSpawner: Invalid attribute '%s'\n", value );
return false;
}
}
else if ( !Q_stricmp( name, "MaxVisionRange" ) )
{
event.m_maxVisionRange = data->GetFloat();
}
else if ( !Q_stricmp( name, "Item" ) )
{
event.m_items.CopyAndAddToTail( value );
}
else if ( !Q_stricmp( name, "ItemAttributes" ) )
{
ParseItemAttributes( event, data );
}
else if ( !Q_stricmp( name, "CharacterAttributes" ) )
{
ParseCharacterAttributes( event, data );
}
else if ( !Q_stricmp( name, "Tag" ) )
{
event.m_tags.CopyAndAddToTail( value );
}
else
{
return false;
}
return true;
}
//-----------------------------------------------------------------------
bool CTFBotSpawner::Parse( KeyValues *values )
{
// Reset All values
m_class = TF_CLASS_UNDEFINED;
m_iszClassIcon = NULL_STRING;
m_health = -1; // default health
m_scale = -1.0f; // default scale
m_flAutoJumpMin = m_flAutoJumpMax = 0.f; // default AutoJumpMin/Max
m_defaultAttributes.Reset();
m_eventChangeAttributes.RemoveAll();
// First, see if we have any Template key
KeyValues *pTemplate = values->FindKey("Template");
if ( pTemplate )
{
KeyValues *pTemplateKV = GetPopulator()->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 TFBotSpawner definition\n", pTemplate->GetString() );
}
}
for ( KeyValues *data = values->GetFirstSubKey(); data != NULL; data = data->GetNextKey() )
{
const char *name = data->GetName();
const char *value = data->GetString();
if ( Q_strlen( name ) <= 0 )
{
continue;
}
// Skip templates when looping through the rest of the keys
if ( !Q_stricmp( name, "Template" ) )
continue;
if ( !Q_stricmp( name, "Class" ) )
{
m_class = GetClassIndexFromString( value );
if ( m_class == TF_CLASS_UNDEFINED )
{
Warning( "TFBotSpawner: Invalid class '%s'\n", value );
return false;
}
if ( m_name.IsEmpty() )
{
m_name = value;
}
}
else if ( !Q_stricmp( name, "ClassIcon" ) )
{
m_iszClassIcon = AllocPooledString( value );
}
else if ( !Q_stricmp( name, "Health" ) )
{
m_health = data->GetInt();
}
else if ( !Q_stricmp( name, "Scale" ) )
{
m_scale = data->GetFloat();
}
else if ( !Q_stricmp( name, "Name" ) )
{
m_name = value;
}
else if ( !Q_stricmp( name, "TeleportWhere" ) )
{
m_teleportWhereName.CopyAndAddToTail( data->GetString() );
}
else if ( !Q_stricmp( name, "AutoJumpMin" ) )
{
m_flAutoJumpMin = data->GetFloat();
}
else if ( !Q_stricmp( name, "AutoJumpMax" ) )
{
m_flAutoJumpMax = data->GetFloat();
}
else if ( !Q_stricmp( name, "EventChangeAttributes" ) )
{
if ( !ParseEventChangeAttributes( data ) )
{
Warning( "TFBotSpawner: Failed to parse EventChangeAttributes\n" );
return false;
}
}
else if ( ParseDynamicAttributes( m_defaultAttributes, data ) )
{
// Do nothing on success
}
else
{
Warning( "TFBotSpawner: Unknown field '%s'\n", name );
return false;
}
}
return true;
}
//-----------------------------------------------------------------------
bool CTFBotSpawner::ParseEventChangeAttributes( KeyValues *data )
{
for ( KeyValues *pKVEvent = data->GetFirstSubKey(); pKVEvent != NULL; pKVEvent = pKVEvent->GetNextKey() )
{
const char* pszEventName = pKVEvent->GetName();
// Add new event
int index = m_eventChangeAttributes.AddToTail();
CTFBot::EventChangeAttributes_t& event = m_eventChangeAttributes[index];
event.m_eventName = pszEventName;
for ( KeyValues *pAttr=pKVEvent->GetFirstSubKey(); pAttr != NULL; pAttr = pAttr->GetNextKey() )
{
if ( !ParseDynamicAttributes( event, pAttr ) )
{
Warning( "TFBotSpawner EventChangeAttributes: Failed to parse event '%s' with unknown attribute '%s'\n", pKVEvent->GetName(), pAttr->GetName() );
return false;
}
}
// should override default attr?
if ( !Q_stricmp( pszEventName, "default" ) )
{
m_defaultAttributes = event;
}
}
return true;
}
//-----------------------------------------------------------------------
bool CTFBotSpawner::Spawn( const Vector &rawHere, EntityHandleVector_t *result )
{
CETWScope timer( "CTFBotSpawner::Spawn" );
VPROF_BUDGET( "CTFBotSpawner::Spawn", "NextBot" );
CTFBot *newBot = NULL;
Vector here = rawHere;
CTFNavArea *area = (CTFNavArea *)TheTFNavMesh()->GetNavArea( here );
if ( area && area->HasAttributeTF( TF_NAV_NO_SPAWNING ) )
{
if ( tf_populator_debug.GetBool() )
{
DevMsg( "CTFBotSpawner: %3.2f: *** Tried to spawn in a NO_SPAWNING area at (%f, %f, %f)\n", gpGlobals->curtime, here.x, here.y, here.z );
}
return false;
}
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
// Only spawn bots while the round is running in MVM mode
if ( TFGameRules()->State_Get() != GR_STATE_RND_RUNNING )
return false;
}
// the ground may be variable here, try a few heights
float z;
for( z = 0.0f; z<StepHeight; z += 4.0f )
{
here.z = rawHere.z + StepHeight;
if ( IsSpaceToSpawnHere( here ) )
{
break;
}
}
if ( z >= StepHeight )
{
if ( tf_populator_debug.GetBool() )
{
DevMsg( "CTFBotSpawner: %3.2f: *** No space to spawn at (%f, %f, %f)\n", gpGlobals->curtime, here.x, here.y, here.z );
}
return false;
}
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
if ( m_class == TF_CLASS_ENGINEER && m_defaultAttributes.m_attributeFlags & CTFBot::TELEPORT_TO_HINT && CTFBotMvMEngineerHintFinder::FindHint( true, false ) == false )
{
if ( tf_populator_debug.GetBool() )
{
DevMsg( "CTFBotSpawner: %3.2f: *** No teleporter hint for engineer\n", gpGlobals->curtime );
}
return false;
}
}
// find dead bot we can re-use
CTeam *deadTeam = GetGlobalTeam( TEAM_SPECTATOR );
for( int i=0; i<deadTeam->GetNumPlayers(); ++i )
{
if ( !deadTeam->GetPlayer(i)->IsBot() )
continue;
// reuse this guy
newBot = (CTFBot *)deadTeam->GetPlayer(i);
newBot->ClearAllAttributes();
break;
}
if ( newBot == NULL )
{
//AssertMsg( 0, "Bots should be preallocated. This block of code should never get called.\n" );
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
int nNumEnemyBots = 0;
CUtlVector<CTFPlayer *> botVector;
CPopulationManager::CollectMvMBots( &botVector );
nNumEnemyBots = botVector.Count();
if ( nNumEnemyBots >= CPopulationManager::MVM_INVADERS_TEAM_SIZE )
{
// no room for more
if ( tf_populator_debug.GetBool() )
{
DevMsg( "CTFBotSpawner: %3.2f: *** Can't spawn. Max number invaders already spawned.\n", gpGlobals->curtime );
}
// extra guard if we're over full on bots
if ( nNumEnemyBots > CPopulationManager::MVM_INVADERS_TEAM_SIZE )
{
// Kick bots until we are at the proper number starting with spectator bots
int iNumberToKick = nNumEnemyBots - CPopulationManager::MVM_INVADERS_TEAM_SIZE;
int iKickedBots = 0;
// loop through spectators and invaders in that order
// Kick Spectators first
CUtlVector<CTFPlayer *> botsToKick;
for ( int iTeam = 0; iTeam < 2; iTeam++ )
{
int targetTeam = TEAM_SPECTATOR;
if ( iTeam == 1 )
{
targetTeam = TF_TEAM_PVE_INVADERS;
}
FOR_EACH_VEC( botVector, iBot )
{
if ( iKickedBots >= iNumberToKick )
break;
if ( botVector[iBot]->GetTeamNumber() == targetTeam )
{
botsToKick.AddToTail( botVector[iBot] );
iKickedBots++;
}
}
}
FOR_EACH_VEC ( botsToKick, iKick )
{
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", botsToKick[iKick]->GetUserID() ) );
}
}
return false;
}
}
else
{
// need to add another bot - is there room?
int totalPlayerCount = 0;
totalPlayerCount += GetGlobalTeam( TEAM_SPECTATOR )->GetNumPlayers();
totalPlayerCount += GetGlobalTeam( TF_TEAM_BLUE )->GetNumPlayers();
totalPlayerCount += GetGlobalTeam( TF_TEAM_RED )->GetNumPlayers();
if ( totalPlayerCount >= gpGlobals->maxClients )
{
// no room for more
if ( tf_populator_debug.GetBool() )
{
DevMsg( "CTFBotSpawner: %3.2f: *** Can't spawn. No free player slot.\n", gpGlobals->curtime );
}
return false;
}
}
newBot = NextBotCreatePlayerBot< CTFBot >( "TFBot", false );
}
if ( newBot )
{
// remove any player attributes
newBot->RemovePlayerAttributes( false );
// clear any old TeleportWhere settings
newBot->ClearTeleportWhere();
if ( g_internalSpawnPoint == NULL )
{
g_internalSpawnPoint = (CPopulatorInternalSpawnPoint *)CreateEntityByName( "populator_internal_spawn_point" );
g_internalSpawnPoint->Spawn();
}
// set name
engine->SetFakeClientConVarValue( newBot->edict(), "name", m_name.IsEmpty() ? "TFBot" : m_name.Get() );
g_internalSpawnPoint->SetAbsOrigin( here );
g_internalSpawnPoint->SetLocalAngles( vec3_angle );
newBot->SetSpawnPoint( g_internalSpawnPoint );
int team = TF_TEAM_RED;
if ( TFGameRules()->IsMannVsMachineMode() )
{
team = TF_TEAM_PVE_INVADERS;
}
newBot->ChangeTeam( team, false, true );
newBot->AllowInstantSpawn();
newBot->HandleCommand_JoinClass( GetPlayerClassData( m_class )->m_szClassName );
newBot->GetPlayerClass()->SetClassIconName( GetClassIcon() );
newBot->ClearEventChangeAttributes();
for ( int i=0; i<m_eventChangeAttributes.Count(); ++i )
{
newBot->AddEventChangeAttributes( &m_eventChangeAttributes[i] );
}
// Request to Add in Endless
if ( g_pPopulationManager->IsInEndlessWaves() )
{
g_pPopulationManager->EndlessSetAttributesForBot( newBot );
}
newBot->SetTeleportWhere( m_teleportWhereName );
if ( m_defaultAttributes.m_attributeFlags & CTFBot::MINIBOSS )
{
newBot->SetIsMiniBoss( true );
}
if ( m_defaultAttributes.m_attributeFlags & CTFBot::USE_BOSS_HEALTH_BAR )
{
newBot->SetUseBossHealthBar( true );
}
if ( m_defaultAttributes.m_attributeFlags & CTFBot::AUTO_JUMP )
{
newBot->SetAutoJump( m_flAutoJumpMin, m_flAutoJumpMax );
}
if( m_defaultAttributes.m_attributeFlags & CTFBot::BULLET_IMMUNE )
{
newBot->m_Shared.AddCond( TF_COND_BULLET_IMMUNE );
}
if( m_defaultAttributes.m_attributeFlags & CTFBot::BLAST_IMMUNE )
{
newBot->m_Shared.AddCond( TF_COND_BLAST_IMMUNE );
}
if( m_defaultAttributes.m_attributeFlags & CTFBot::FIRE_IMMUNE )
{
newBot->m_Shared.AddCond( TF_COND_FIRE_IMMUNE );
}
if ( TFGameRules()->IsMannVsMachineMode() )
{
// initialize currency to be dropped on death to zero
newBot->SetCurrency( 0 );
// announce Spies
if ( m_class == TF_CLASS_SPY )
{
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_PVE_INVADERS, COLLECT_ONLY_LIVING_PLAYERS );
int spyCount = 0;
for( int i=0; i<playerVector.Count(); ++i )
{
if ( playerVector[i]->IsPlayerClass( TF_CLASS_SPY ) )
{
++spyCount;
}
}
IGameEvent *event = gameeventmanager->CreateEvent( "mvm_mission_update" );
if ( event )
{
event->SetInt( "class", TF_CLASS_SPY );
event->SetInt( "count", spyCount );
gameeventmanager->FireEvent( event );
}
}
}
newBot->SetScaleOverride( m_scale );
int nHealth = m_health;
if ( nHealth <= 0.0f )
{
nHealth = newBot->GetMaxHealth();
}
nHealth *= g_pPopulationManager->GetHealthMultiplier( false );
newBot->ModifyMaxHealth( nHealth );
newBot->StartIdleSound();
// Add our items first, they'll get replaced below by the normal MvM items if any are needed
if ( TFGameRules()->IsMannVsMachineMode() && ( newBot->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) )
{
// Apply the Rome 2 promo items to each bot. They'll be
// filtered out for clients that do not have Romevision.
CMissionPopulator *pMission = dynamic_cast< CMissionPopulator* >( GetPopulator() );
if ( pMission && ( pMission->GetMissionType() == CTFBot::MISSION_DESTROY_SENTRIES ) )
{
newBot->AddItem( "tw_sentrybuster" );
}
else
{
newBot->AddItem( g_szRomePromoItems_Hat[m_class] );
newBot->AddItem( g_szRomePromoItems_Misc[m_class] );
}
}
// apply default attributes
const CTFBot::EventChangeAttributes_t* pEventChangeAttributes = newBot->GetEventChangeAttributes( g_pPopulationManager->GetDefaultEventChangeAttributesName() );
if ( !pEventChangeAttributes )
{
pEventChangeAttributes = &m_defaultAttributes;
}
newBot->OnEventChangeAttributes( pEventChangeAttributes );
CCaptureFlag *pFlag = newBot->GetFlagToFetch();
if ( pFlag )
{
newBot->SetFlagTarget( pFlag );
}
if ( newBot->HasAttribute( CTFBot::SPAWN_WITH_FULL_CHARGE ) )
{
// charge up our weapons
// Medigun Ubercharge
CWeaponMedigun *medigun = dynamic_cast< CWeaponMedigun * >( newBot->Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
if ( medigun )
{
medigun->AddCharge( 1.0f );
}
newBot->m_Shared.SetRageMeter( 100.0f );
}
int nClassIndex = ( newBot->GetPlayerClass() ? newBot->GetPlayerClass()->GetClassIndex() : TF_CLASS_UNDEFINED );
if ( GetPopulator()->GetManager()->IsPopFileEventType( MVM_EVENT_POPFILE_HALLOWEEN ) )
{
// zombies use the original player models
newBot->m_nSkin = 4;
const char *name = g_aRawPlayerClassNamesShort[ nClassIndex ];
newBot->AddItem( CFmtStr( "Zombie %s", name ) );
}
else
{
// use the nifty new robot model
if ( nClassIndex >= TF_CLASS_SCOUT && nClassIndex <= TF_CLASS_ENGINEER )
{
if ( ( m_scale >= tf_mvm_miniboss_scale.GetFloat() || newBot->IsMiniBoss() ) && g_pFullFileSystem->FileExists( g_szBotBossModels[ nClassIndex ] ) )
{
newBot->GetPlayerClass()->SetCustomModel( g_szBotBossModels[ nClassIndex ], USE_CLASS_ANIMATIONS );
newBot->UpdateModel();
newBot->SetBloodColor( DONT_BLEED );
}
else if ( g_pFullFileSystem->FileExists( g_szBotModels[ nClassIndex ] ) )
{
newBot->GetPlayerClass()->SetCustomModel( g_szBotModels[ nClassIndex ], USE_CLASS_ANIMATIONS );
newBot->UpdateModel();
newBot->SetBloodColor( DONT_BLEED );
}
}
}
if ( result )
{
result->AddToTail( newBot );
}
if ( TFGameRules()->IsMannVsMachineMode() )
{
if ( newBot->IsMiniBoss() )
{
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_GIANT_CALLOUT, TF_TEAM_PVE_DEFENDERS );
}
}
if ( tf_populator_debug.GetBool() )
{
if ( tf_populator_debug.GetBool() )
{
DevMsg( "%3.2f: Spawned TFBot '%s'\n", gpGlobals->curtime, m_name.IsEmpty() ? newBot->GetPlayerClass()->GetName() : m_name.Get() );
}
}
}
else
{
if ( tf_populator_debug.GetBool() )
{
DevMsg( "CTFBotSpawner: %3.2f: *** Can't create TFBot to spawn.\n", gpGlobals->curtime );
}
return false;
}
return true;
}
//-----------------------------------------------------------------------
int CTFBotSpawner::GetClass( int nSpawnNum /*= -1*/ )
{
return m_class;
}
//-----------------------------------------------------------------------
string_t CTFBotSpawner::GetClassIcon( int nSpawnNum /*= -1*/ )
{
if ( m_iszClassIcon != NULL_STRING )
return m_iszClassIcon;
return AllocPooledString( g_aRawPlayerClassNamesShort[ m_class ] );
}
//-----------------------------------------------------------------------
int CTFBotSpawner::GetHealth( int nSpawnNum /*= -1*/ )
{
return m_health;
}
//-----------------------------------------------------------------------
bool CTFBotSpawner::IsMiniBoss( int nSpawnNum /*= -1*/ )
{
return ( m_defaultAttributes.m_attributeFlags & CTFBot::MINIBOSS ) != 0;
}
//-----------------------------------------------------------------------
bool CTFBotSpawner::HasAttribute( CTFBot::AttributeType type, int nSpawnNum /*= -1*/ )
{
return ( m_defaultAttributes.m_attributeFlags & type ) != 0;
}
//-----------------------------------------------------------------------
bool CTFBotSpawner::HasEventChangeAttributes( const char* pszEventName ) const
{
for ( int i=0; i<m_eventChangeAttributes.Count(); ++i )
{
if ( FStrEq( pszEventName, m_eventChangeAttributes[i].m_eventName ) )
{
return true;
}
}
return false;
}
//-----------------------------------------------------------------------
// CTankSpawner
//-----------------------------------------------------------------------
CTankSpawner::CTankSpawner( IPopulator *populator ) : IPopulationSpawner( populator )
{
m_health = 50000;
m_speed = 75;
m_name = "Tank";
m_skin = 0;
m_startingPathTrackNodeName = NULL;
m_onKilledOutput = NULL;
m_onBombDroppedOutput = NULL;
}
//-----------------------------------------------------------------------
bool CTankSpawner::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, "Health" ) )
{
m_health = data->GetInt();
}
else if ( !Q_stricmp( name, "Speed" ) )
{
m_speed = data->GetFloat();
}
else if ( !Q_stricmp( name, "Name" ) )
{
m_name = data->GetString();
}
else if ( !Q_stricmp( name, "Skin" ) )
{
m_skin = data->GetInt();
}
else if ( !Q_stricmp( name, "StartingPathTrackNode" ) )
{
m_startingPathTrackNodeName = data->GetString();
}
else if ( !Q_stricmp( name, "OnKilledOutput" ) )
{
m_onKilledOutput = ParseEvent( data );
}
else if ( !Q_stricmp( name, "OnBombDroppedOutput" ) )
{
m_onBombDroppedOutput = ParseEvent( data );
}
else
{
Warning( "Invalid attribute '%s' in Tank definition\n", name );
return false;
}
}
return true;
}
//-----------------------------------------------------------------------
bool CTankSpawner::Spawn( const Vector &here, EntityHandleVector_t *result )
{
CTFTankBoss *tank = (CTFTankBoss *)CreateEntityByName( "tank_boss" );
if ( tank )
{
tank->SetAbsOrigin( here );
tank->SetAbsAngles( vec3_angle );
int nHealth = m_health * g_pPopulationManager->GetHealthMultiplier( true );
tank->SetInitialHealth( nHealth );
tank->SetMaxSpeed( m_speed );
tank->SetName( MAKE_STRING( m_name ) );
tank->SetSkin( m_skin );
tank->SetStartingPathTrackNode( m_startingPathTrackNodeName.GetForModify() );
tank->Spawn();
tank->DefineOnKilledOutput( m_onKilledOutput );
tank->DefineOnBombDroppedOutput( m_onBombDroppedOutput );
if ( result )
{
result->AddToTail( tank );
}
return true;
}
if ( tf_populator_debug.GetBool() )
{
DevMsg( "CTankSpawner: %3.2f: Failed to create base_boss\n", gpGlobals->curtime );
}
return false;
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
CSentryGunSpawner::CSentryGunSpawner( IPopulator *populator ) : IPopulationSpawner( populator )
{
m_level = 0;
}
//-----------------------------------------------------------------------
bool CSentryGunSpawner::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, "Level" ) )
{
m_level = data->GetInt();
}
else
{
Warning( "Invalid attribute '%s' in SentryGun definition\n", name );
return false;
}
}
return true;
}
//-----------------------------------------------------------------------
bool CSentryGunSpawner::Spawn( const Vector &here, EntityHandleVector_t *result )
{
// directly create a sentry gun at the precise position and orientation desired
CObjectSentrygun *sentry = (CObjectSentrygun *)CreateEntityByName( "obj_sentrygun" );
if ( sentry )
{
sentry->SetAbsOrigin( here );
sentry->SetAbsAngles( vec3_angle );
sentry->Spawn();
sentry->ChangeTeam( TF_TEAM_RED );
sentry->m_nDefaultUpgradeLevel = m_level+1;
sentry->InitializeMapPlacedObject();
if ( result )
{
result->AddToTail( sentry );
}
return true;
}
if ( tf_populator_debug.GetBool() )
{
DevMsg( "CSentryGunSpawner: %3.2f: Failed to create obj_sentrygun\n", gpGlobals->curtime );
}
return false;
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
CSquadSpawner::CSquadSpawner( IPopulator *populator ) : IPopulationSpawner( populator )
{
m_memberSpawnerVector.RemoveAll();
m_formationSize = -1.0f;
m_bShouldPreserveSquad = false;
}
//-----------------------------------------------------------------------
CSquadSpawner::~CSquadSpawner()
{
for( int i=0; i<m_memberSpawnerVector.Count(); ++i )
{
delete m_memberSpawnerVector[i];
}
m_memberSpawnerVector.RemoveAll();
}
//-----------------------------------------------------------------------
bool CSquadSpawner::Parse( KeyValues *values )
{
for ( KeyValues *data = values->GetFirstSubKey(); data != NULL; data = data->GetNextKey() )
{
const char *name = data->GetName();
if ( V_strlen( name ) <= 0 )
{
continue;
}
if ( !V_stricmp( name, "FormationSize" ) )
{
m_formationSize = data->GetFloat();
continue;
}
else if ( !V_stricmp( name, "ShouldPreserveSquad" ) )
{
m_bShouldPreserveSquad = data->GetBool();
continue;
}
// NOTE: It doesn't make sense for Squads to contain SentryGun or Mobs, but this
// allows for interesting trees of RandomChoice, etc.
IPopulationSpawner *spawner = ParseSpawner( GetPopulator(), data );
if ( spawner == NULL )
{
Warning( "Unknown attribute '%s' in Squad definition.\n", name );
}
else
{
m_memberSpawnerVector.AddToTail( spawner );
}
}
return true;
}
//-----------------------------------------------------------------------
bool CSquadSpawner::Spawn( const Vector &here, EntityHandleVector_t *result )
{
VPROF_BUDGET( "CSquadSpawner::Spawn", "NextBot" );
if ( tf_populator_debug.GetBool() )
{
DevMsg( "CSquadSpawner: %3.2f: <<<< Spawning Squad >>>>\n", gpGlobals->curtime );
}
// Is there enough slots to spawn?
CTeam *deadTeam = GetGlobalTeam( TEAM_SPECTATOR );
if ( deadTeam->GetNumPlayers() < m_memberSpawnerVector.Count() )
{
return false;
}
// spawn all of the squad members
bool isComplete = true;
EntityHandleVector_t squadVector;
for( int i=0; i<m_memberSpawnerVector.Count(); ++i )
{
if ( m_memberSpawnerVector[i]->Spawn( here, &squadVector ) == false )
{
isComplete = false;
break;
}
}
if ( !isComplete )
{
// unable to spawn entire squad
if ( tf_populator_debug.GetBool() )
{
DevMsg( "%3.2f: CSquadSpawner: Unable to spawn entire squad\n", gpGlobals->curtime );
}
// unspawn partial squad
// TODO: Respect TFBot attributes
for( int i=0; i<squadVector.Count(); ++i )
{
CTFPlayer *player = ToTFPlayer( squadVector[i] );
if ( player )
{
player->ChangeTeam( TEAM_SPECTATOR, false, true );
}
else
{
UTIL_Remove( squadVector[i] );
}
}
return false;
}
// create the squad
CTFBotSquad *squad = new CTFBotSquad;
if ( squad )
{
squad->SetFormationSize( m_formationSize );
squad->SetShouldPreserveSquad( m_bShouldPreserveSquad );
for( int i=0; i<squadVector.Count(); ++i )
{
CTFBot *bot = ToTFBot( squadVector[i] );
if ( bot )
{
bot->JoinSquad( squad );
}
}
}
if ( result )
{
result->AddVectorToTail( squadVector );
}
return true;
}
int CSquadSpawner::GetClass( int nSpawnNum /*= -1*/ )
{
if ( nSpawnNum < 0 || m_memberSpawnerVector.Count() == 0 )
{
return TF_CLASS_UNDEFINED;
}
int nSpawner = nSpawnNum % m_memberSpawnerVector.Count();
if ( !m_memberSpawnerVector[ nSpawner ]->IsVarious() )
{
return m_memberSpawnerVector[ nSpawner ]->GetClass();
}
else
{
// FIXME: Nested complex spawner types... need a method for counting these.
Assert( 1 );
DevWarning( "Nested complex spawner types... need a method for counting these." );
return TF_CLASS_UNDEFINED;
}
}
string_t CSquadSpawner::GetClassIcon( int nSpawnNum /*= -1*/ )
{
if ( nSpawnNum < 0 || m_memberSpawnerVector.Count() == 0 )
{
return NULL_STRING;
}
int nSpawner = nSpawnNum % m_memberSpawnerVector.Count();
if ( !m_memberSpawnerVector[ nSpawner ]->IsVarious() )
{
return m_memberSpawnerVector[ nSpawner ]->GetClassIcon();
}
else
{
// FIXME: Nested complex spawner types... need a method for counting these.
Assert( 1 );
DevWarning( "Nested complex spawner types... need a method for counting these." );
return NULL_STRING;
}
}
int CSquadSpawner::GetHealth( int nSpawnNum /*= -1*/ )
{
if ( nSpawnNum < 0 || m_memberSpawnerVector.Count() == 0 )
{
return 0;
}
int nSpawner = nSpawnNum % m_memberSpawnerVector.Count();
if ( !m_memberSpawnerVector[ nSpawner ]->IsVarious() )
{
return m_memberSpawnerVector[ nSpawner ]->GetHealth();
}
else
{
// FIXME: Nested complex spawner types... need a method for counting these.
Assert( 1 );
DevWarning( "Nested complex spawner types... need a method for counting these." );
return 0;
}
}
bool CSquadSpawner::IsMiniBoss( int nSpawnNum /*= -1*/ )
{
if ( nSpawnNum < 0 || m_memberSpawnerVector.Count() == 0 )
{
return false;
}
int nSpawner = nSpawnNum % m_memberSpawnerVector.Count();
if ( !m_memberSpawnerVector[ nSpawner ]->IsVarious() )
{
return m_memberSpawnerVector[ nSpawner ]->IsMiniBoss();
}
else
{
// FIXME: Nested complex spawner types... need a method for counting these.
Assert( 1 );
DevWarning( "Nested complex spawner types... need a method for counting these." );
return false;
}
}
bool CSquadSpawner::HasAttribute( CTFBot::AttributeType type, int nSpawnNum /*= -1*/ )
{
if ( nSpawnNum < 0 || m_memberSpawnerVector.Count() == 0 )
{
return false;
}
int nSpawner = nSpawnNum % m_memberSpawnerVector.Count();
if ( !m_memberSpawnerVector[ nSpawner ]->IsVarious() )
{
return m_memberSpawnerVector[ nSpawner ]->HasAttribute( type, nSpawnNum );
}
else
{
// FIXME: Nested complex spawner types... need a method for counting these.
Assert( 1 );
DevWarning( "Nested complex spawner types... need a method for counting these." );
return false;
}
}
bool CSquadSpawner::HasEventChangeAttributes( const char* pszEventName ) const
{
for ( int i=0; i<m_memberSpawnerVector.Count(); ++i )
{
if ( m_memberSpawnerVector[i]->HasEventChangeAttributes( pszEventName ) )
{
return true;
}
}
return false;
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
CMobSpawner::CMobSpawner( IPopulator *populator ) : IPopulationSpawner( populator )
{
m_count = 0;
m_spawner = NULL;
m_mobSpawnTimer.Invalidate();
m_mobLifetimeTimer.Invalidate();
m_mobArea = NULL;
m_mobCountRemaining = 0;
}
//-----------------------------------------------------------------------
CMobSpawner::~CMobSpawner()
{
if ( m_spawner )
{
delete m_spawner;
}
m_spawner = NULL;
}
//-----------------------------------------------------------------------
bool CMobSpawner::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
{
// NOTE: It doesn't make sense for Mobs to contain SentryGuns, but this
// allows for interesting trees of RandomChoice, etc.
IPopulationSpawner *spawner = ParseSpawner( GetPopulator(), data );
if ( spawner && m_spawner )
{
Warning( "CMobSpawner: Duplicate spawner encountered - discarding!\n" );
delete spawner;
}
else if ( spawner == NULL )
{
Warning( "Unknown attribute '%s' in Mob definition.\n", name );
}
else
{
m_spawner = spawner;
}
}
}
return true;
}
//-----------------------------------------------------------------------
bool CMobSpawner::Spawn( const Vector &here, EntityHandleVector_t *result )
{
if ( m_spawner == NULL )
return false;
// spawn the mob
for( int i=0; i<m_count; ++i )
{
if ( m_spawner->Spawn( here, result ) == false )
{
return false;
}
}
return true;
}
//-----------------------------------------------------------------------
bool CMobSpawner::HasEventChangeAttributes( const char* pszEventName ) const
{
if ( m_spawner == NULL )
return false;
return m_spawner->HasEventChangeAttributes( pszEventName );
}