summaryrefslogtreecommitdiff
path: root/game/server/tf/player_vs_environment/tf_populator_spawners.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/player_vs_environment/tf_populator_spawners.cpp')
-rw-r--r--game/server/tf/player_vs_environment/tf_populator_spawners.cpp1890
1 files changed, 1890 insertions, 0 deletions
diff --git a/game/server/tf/player_vs_environment/tf_populator_spawners.cpp b/game/server/tf/player_vs_environment/tf_populator_spawners.cpp
new file mode 100644
index 0000000..53bb61d
--- /dev/null
+++ b/game/server/tf/player_vs_environment/tf_populator_spawners.cpp
@@ -0,0 +1,1890 @@
+//========= 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 );
+}