//========= 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; iGetFirstSubKey(); 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; iHasEventChangeAttributes( 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 vecStaticAttribs; FOR_EACH_SUBKEY( data, pKVAttribute ) { CUtlVector 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 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 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 ) { 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; iGetNumPlayers(); ++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 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 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; iAddEventChangeAttributes( &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; iIsPlayerClass( 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; iGetFirstSubKey(); 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; iGetFirstSubKey(); 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; iSpawn( 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; iChangeTeam( 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; iJoinSquad( 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; iHasEventChangeAttributes( 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; iSpawn( 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 ); }