summaryrefslogtreecommitdiff
path: root/game/server/tf/bot/behavior/tf_bot_tactical_monitor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/bot/behavior/tf_bot_tactical_monitor.cpp')
-rw-r--r--game/server/tf/bot/behavior/tf_bot_tactical_monitor.cpp659
1 files changed, 659 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/tf_bot_tactical_monitor.cpp b/game/server/tf/bot/behavior/tf_bot_tactical_monitor.cpp
new file mode 100644
index 0000000..e68cd9a
--- /dev/null
+++ b/game/server/tf/bot/behavior/tf_bot_tactical_monitor.cpp
@@ -0,0 +1,659 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_tactical_monitor.cpp
+// Behavior layer that interrupts for ammo/health/retreat/etc
+// Michael Booth, June 2009
+
+#include "cbase.h"
+#include "fmtstr.h"
+
+#include "tf_gamerules.h"
+#include "tf_weapon_pipebomblauncher.h"
+#include "NextBot/NavMeshEntities/func_nav_prerequisite.h"
+
+#include "bot/tf_bot.h"
+#include "bot/tf_bot_manager.h"
+
+#include "bot/behavior/tf_bot_tactical_monitor.h"
+#include "bot/behavior/tf_bot_scenario_monitor.h"
+
+#include "bot/behavior/tf_bot_seek_and_destroy.h"
+#include "bot/behavior/tf_bot_retreat_to_cover.h"
+#include "bot/behavior/tf_bot_taunt.h"
+#include "bot/behavior/tf_bot_get_health.h"
+#include "bot/behavior/tf_bot_get_ammo.h"
+#include "bot/behavior/tf_bot_destroy_enemy_sentry.h"
+#include "bot/behavior/tf_bot_use_teleporter.h"
+#include "bot/behavior/nav_entities/tf_bot_nav_ent_destroy_entity.h"
+#include "bot/behavior/nav_entities/tf_bot_nav_ent_move_to.h"
+#include "bot/behavior/nav_entities/tf_bot_nav_ent_wait.h"
+#include "bot/behavior/engineer/tf_bot_engineer_building.h"
+#include "bot/behavior/squad/tf_bot_escort_squad_leader.h"
+
+#include "bot/behavior/training/tf_bot_training.h"
+#include "bot/map_entities/tf_bot_hint_sentrygun.h"
+
+#include "tf_obj_sentrygun.h"
+#include "tf_item_system.h"
+
+extern ConVar tf_bot_health_ok_ratio;
+extern ConVar tf_bot_health_critical_ratio;
+
+ConVar tf_bot_force_jump( "tf_bot_force_jump", "0", FCVAR_CHEAT, "Force bots to continuously jump" );
+
+
+Action< CTFBot > *CTFBotTacticalMonitor::InitialContainedAction( CTFBot *me )
+{
+ return new CTFBotScenarioMonitor;
+}
+
+
+//-----------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotTacticalMonitor::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ return Continue();
+}
+
+
+//-----------------------------------------------------------------------------------------
+void CTFBotTacticalMonitor::MonitorArmedStickyBombs( CTFBot *me )
+{
+ if ( m_stickyBombCheckTimer.IsElapsed() )
+ {
+ m_stickyBombCheckTimer.Start( RandomFloat( 0.3f, 1.0f ) );
+
+ // are there any enemies on/near my sticky bombs?
+ CTFPipebombLauncher *gun = dynamic_cast< CTFPipebombLauncher * >( me->Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
+ if ( gun )
+ {
+ const CUtlVector< CHandle< CTFGrenadePipebombProjectile > > &pipeBombVector = gun->GetPipeBombVector();
+
+ if ( pipeBombVector.Count() > 0 )
+ {
+ CUtlVector< CKnownEntity > knownVector;
+ me->GetVisionInterface()->CollectKnownEntities( &knownVector );
+
+ for( int p=0; p<pipeBombVector.Count(); ++p )
+ {
+ CTFGrenadePipebombProjectile *sticky = pipeBombVector[p];
+ if ( !sticky )
+ {
+ continue;
+ }
+
+ for( int k=0; k<knownVector.Count(); ++k )
+ {
+ if ( knownVector[k].IsObsolete() )
+ {
+ continue;
+ }
+
+ if ( knownVector[k].GetEntity()->IsBaseObject() )
+ {
+ // we want to put several stickies on a sentry and det at once
+ continue;
+ }
+
+ if ( sticky->GetTeamNumber() != GetEnemyTeam( knownVector[k].GetEntity()->GetTeamNumber() ) )
+ {
+ // "known" is either a spectator, or on our team
+ continue;
+ }
+
+ const float closeRange = 150.0f;
+ if ( ( knownVector[k].GetLastKnownPosition() - sticky->GetAbsOrigin() ).IsLengthLessThan( closeRange ) )
+ {
+ // they are close - blow it!
+ me->PressAltFireButton();
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------
+void CTFBotTacticalMonitor::AvoidBumpingEnemies( CTFBot *me )
+{
+ if ( me->GetDifficulty() < CTFBot::HARD )
+ return;
+
+ const float avoidRange = 200.0f;
+
+ CUtlVector< CTFPlayer * > enemyVector;
+ CollectPlayers( &enemyVector, GetEnemyTeam( me->GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS );
+
+ CTFPlayer *closestEnemy = NULL;
+ float closestRangeSq = avoidRange * avoidRange;
+
+ for( int i=0; i<enemyVector.Count(); ++i )
+ {
+ CTFPlayer *enemy = enemyVector[i];
+
+ if ( enemy->m_Shared.IsStealthed() || enemy->m_Shared.InCond( TF_COND_DISGUISED ) )
+ continue;
+
+ float rangeSq = ( enemy->GetAbsOrigin() - me->GetAbsOrigin() ).LengthSqr();
+ if ( rangeSq < closestRangeSq )
+ {
+ closestEnemy = enemy;
+ closestRangeSq = rangeSq;
+ }
+ }
+
+ if ( !closestEnemy )
+ return;
+
+ // avoid unless hindrance returns a definitive "no"
+ if ( me->GetIntentionInterface()->IsHindrance( me, closestEnemy ) == ANSWER_UNDEFINED )
+ {
+ me->ReleaseForwardButton();
+ me->ReleaseLeftButton();
+ me->ReleaseRightButton();
+ me->ReleaseBackwardButton();
+
+ Vector away = me->GetAbsOrigin() - closestEnemy->GetAbsOrigin();
+
+ me->GetLocomotionInterface()->Approach( me->GetLocomotionInterface()->GetFeet() + away );
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotTacticalMonitor::Update( CTFBot *me, float interval )
+{
+ if ( TFGameRules()->RoundHasBeenWon() )
+ {
+#ifdef TF_RAID_MODE
+ if ( TFGameRules()->IsBossBattleMode() )
+ {
+ return Continue();
+ }
+#endif // TF_RAID_MODE
+ if ( TFGameRules()->GetWinningTeam() == me->GetTeamNumber() )
+ {
+ // we won - kill all losers we see
+ return SuspendFor( new CTFBotSeekAndDestroy, "Get the losers!" );
+ }
+ else
+ {
+ // lost - run and hide
+ if ( me->GetVisionInterface()->GetPrimaryKnownThreat( true ) )
+ {
+ return SuspendFor( new CTFBotRetreatToCover, "Run away from threat!" );
+ }
+
+ me->PressCrouchButton();
+ }
+
+ return Continue();
+ }
+
+ if ( tf_bot_force_jump.GetBool() )
+ {
+ if ( !me->GetLocomotionInterface()->IsClimbingOrJumping() )
+ {
+ me->GetLocomotionInterface()->Jump();
+ }
+ }
+
+ if ( TFGameRules()->State_Get() == GR_STATE_PREROUND )
+ {
+ // clear stuck monitor so we dont jump when the preround elapses
+ me->GetLocomotionInterface()->ClearStuckStatus( "In preround" );
+ }
+
+ Action< CTFBot > *result = me->OpportunisticallyUseWeaponAbilities();
+ if ( result )
+ {
+ return SuspendFor( result, "Opportunistically using buff item" );
+ }
+
+ if ( TFGameRules()->InSetup() )
+ {
+ // if a human is staring at us, face them and taunt
+ if ( m_acknowledgeRetryTimer.IsElapsed() )
+ {
+ CTFPlayer *watcher = me->GetClosestHumanLookingAtMe();
+ if ( watcher )
+ {
+ if ( !m_attentionTimer.HasStarted() )
+ m_attentionTimer.Start( 0.5f );
+
+ if ( m_attentionTimer.HasStarted() && m_attentionTimer.IsElapsed() )
+ {
+ // a human has been staring at us - acknowledge them
+ if ( !m_acknowledgeAttentionTimer.HasStarted() )
+ {
+ // look toward them
+ me->GetBodyInterface()->AimHeadTowards( watcher, IBody::IMPORTANT, 3.0f, NULL, "Acknowledging friendly human attention" );
+ m_acknowledgeAttentionTimer.Start( RandomFloat( 0.0f, 2.0f ) );
+ }
+ else if ( m_acknowledgeAttentionTimer.IsElapsed() )
+ {
+ m_acknowledgeAttentionTimer.Invalidate();
+
+ // don't ack again for awhile
+ m_acknowledgeRetryTimer.Start( RandomFloat( 10.0f, 20.0f ) );
+
+ return SuspendFor( new CTFBotTaunt, "Acknowledging friendly human attention" );
+ }
+ }
+ }
+ else
+ {
+ // no-one is looking at me
+ m_attentionTimer.Invalidate();
+ }
+ }
+ }
+
+ // check if we need to get to cover
+ QueryResultType shouldRetreat = me->GetIntentionInterface()->ShouldRetreat( me );
+
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ // never retreat in MvM mode
+ shouldRetreat = ANSWER_NO;
+ }
+
+ if ( shouldRetreat == ANSWER_YES )
+ {
+ return SuspendFor( new CTFBotRetreatToCover, "Backing off" );
+ }
+ else if ( shouldRetreat != ANSWER_NO )
+ {
+ // retreat if we need to do a full reload (ie: soldiers shot all their rockets)
+ if ( !me->m_Shared.InCond( TF_COND_INVULNERABLE ) )
+ {
+ if ( me->IsDifficulty( CTFBot::HARD ) || me->IsDifficulty( CTFBot::EXPERT ) )
+ {
+ CTFWeaponBase *myPrimary = (CTFWeaponBase *)me->Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
+ if ( myPrimary && me->GetAmmoCount( TF_AMMO_PRIMARY ) > 0 && me->IsBarrageAndReloadWeapon( myPrimary ) )
+ {
+ if ( myPrimary->Clip1() <= 1 )
+ {
+ return SuspendFor( new CTFBotRetreatToCover, "Moving to cover to reload" );
+ }
+ }
+ }
+ }
+ }
+
+ bool isAvailable = ( me->GetIntentionInterface()->ShouldHurry( me ) != ANSWER_YES );
+
+ if ( TFGameRules()->IsMannVsMachineMode() && me->HasTheFlag() )
+ {
+ isAvailable = false;
+ }
+
+ // collect ammo and health kits, unless we're in a big hurry
+ if ( isAvailable && m_maintainTimer.IsElapsed() )
+ {
+ m_maintainTimer.Start( RandomFloat( 0.3f, 0.5f ) );
+
+ bool isHurt = false;
+
+ if ( me->IsInCombat() || me->IsPlayerClass( TF_CLASS_SNIPER ) )
+ {
+ // stay in the fight until we're nearly dead
+ isHurt = ( (float)me->GetHealth() / (float)me->GetMaxHealth() ) < tf_bot_health_critical_ratio.GetFloat();
+ }
+ else
+ {
+ isHurt = me->m_Shared.InCond( TF_COND_BURNING ) || ( (float)me->GetHealth() / (float)me->GetMaxHealth() ) < tf_bot_health_ok_ratio.GetFloat();
+ }
+
+ if ( isHurt && CTFBotGetHealth::IsPossible( me ) )
+ {
+ return SuspendFor( new CTFBotGetHealth, "Grabbing nearby health" );
+ }
+
+ if ( me->IsAmmoLow() && CTFBotGetAmmo::IsPossible( me ) )
+ {
+ return SuspendFor( new CTFBotGetAmmo, "Grabbing nearby ammo" );
+ }
+
+ bool shouldDestroySentries = true;
+
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ shouldDestroySentries = false;
+ }
+
+ // destroy enemy sentry guns we've encountered
+ if ( shouldDestroySentries && me->GetEnemySentry() && CTFBotDestroyEnemySentry::IsPossible( me ) )
+ {
+ return SuspendFor( new CTFBotDestroyEnemySentry, "Going after an enemy sentry to destroy it" );
+ }
+ }
+
+ // opportunistically use nearby teleporters
+ if ( ShouldOpportunisticallyTeleport( me ) )
+ {
+ CObjectTeleporter *nearbyTeleporter = FindNearbyTeleporter( me );
+ if ( nearbyTeleporter )
+ {
+ CTFNavArea *teleporterArea = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( nearbyTeleporter );
+ CTFNavArea *myArea = (CTFNavArea *)me->GetLastKnownArea();
+
+ // only use teleporter if it is ahead of us
+ if ( teleporterArea && myArea && myArea->GetIncursionDistance( me->GetTeamNumber() ) < 350.0f + teleporterArea->GetIncursionDistance( me->GetTeamNumber() ) )
+ {
+ return SuspendFor( new CTFBotUseTeleporter( nearbyTeleporter ), "Using nearby teleporter" );
+ }
+ }
+ }
+
+ // detonate sticky bomb traps when victims are near
+ MonitorArmedStickyBombs( me );
+
+ // if we're a Spy, avoid bumping into enemies and giving ourselves away
+ if ( me->IsPlayerClass( TF_CLASS_SPY ) )
+ {
+ AvoidBumpingEnemies( me );
+ }
+
+ me->UpdateDelayedThreatNotices();
+
+ // if I'm a squad leader, wait for out of position squadmates
+ if ( me->IsInASquad() && me->GetSquad()->IsLeader( me ) && me->GetSquad()->ShouldSquadLeaderWaitForFormation() )
+ {
+ return SuspendFor( new CTFBotWaitForOutOfPositionSquadMember, "Waiting for squadmates to get back into formation" );
+ }
+
+
+ return Continue();
+}
+
+
+//-----------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotTacticalMonitor::OnOtherKilled( CTFBot *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
+{
+ return TryContinue();
+}
+
+
+//-----------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotTacticalMonitor::OnNavAreaChanged( CTFBot *me, CNavArea *newArea, CNavArea *oldArea )
+{
+ // does the area we are entering have a prerequisite?
+ if ( newArea && newArea->HasPrerequisite( me ) && !me->HasAttribute( CTFBot::AGGRESSIVE ) )
+ {
+ const CUtlVector< CHandle< CFuncNavPrerequisite > > &prereqVector = newArea->GetPrerequisiteVector();
+
+ for( int i=0; i<prereqVector.Count(); ++i )
+ {
+ const CFuncNavPrerequisite *prereq = prereqVector[i];
+ if ( prereq && prereq->IsEnabled() && const_cast< CFuncNavPrerequisite * >( prereq )->PassesTriggerFilters( me ) )
+ {
+ // this prerequisite applies to me
+ if ( prereq->IsTask( CFuncNavPrerequisite::TASK_WAIT ) )
+ {
+ return TrySuspendFor( new CTFBotNavEntWait( prereq ), RESULT_IMPORTANT, "Prerequisite commands me to wait" );
+ }
+ else if ( prereq->IsTask( CFuncNavPrerequisite::TASK_MOVE_TO_ENTITY ) )
+ {
+ return TrySuspendFor( new CTFBotNavEntMoveTo( prereq ), RESULT_IMPORTANT, "Prerequisite commands me to move to an entity" );
+ }
+ }
+ }
+ }
+
+
+ return TryContinue();
+}
+
+//-----------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotTacticalMonitor::OnCommandString( CTFBot *me, const char *command )
+{
+ if ( FStrEq( command, "goto action point" ) )
+ {
+ return TrySuspendFor( new CTFGotoActionPoint(), RESULT_IMPORTANT, "Received command to go to action point" );
+ }
+ else if ( FStrEq( command, "despawn" ) )
+ {
+ return TrySuspendFor( new CTFDespawn(), RESULT_CRITICAL, "Received command to go to de-spawn" );
+ }
+ else if ( FStrEq( command, "taunt" ) )
+ {
+ return TrySuspendFor( new CTFBotTaunt(), RESULT_TRY, "Received command to taunt" );
+ }
+ else if ( FStrEq( command, "cloak" ) )
+ {
+ if ( me->IsPlayerClass( TF_CLASS_SPY ) && me->m_Shared.IsStealthed() == false )
+ {
+ me->PressAltFireButton();
+ }
+ }
+ else if ( FStrEq( command, "uncloak" ) )
+ {
+ if ( me->IsPlayerClass( TF_CLASS_SPY ) && me->m_Shared.IsStealthed() == true )
+ {
+ me->PressAltFireButton();
+ }
+ }
+ else if ( FStrEq( command, "disguise") )
+ {
+ if ( me->IsPlayerClass( TF_CLASS_SPY ) )
+ {
+ if ( me->CanDisguise() )
+ {
+ me->m_Shared.Disguise( GetEnemyTeam( me->GetTeamNumber() ), RandomInt( TF_FIRST_NORMAL_CLASS, TF_LAST_NORMAL_CLASS-1 ) );
+ }
+ }
+ }
+ else if ( FStrEq( command, "build sentry at nearest sentry hint" ) )
+ {
+ if ( me->IsPlayerClass( TF_CLASS_ENGINEER ) )
+ {
+ CTFBotHintSentrygun *bestSentryHint = NULL;
+ float bestDist2 = FLT_MAX;
+ CTFBotHintSentrygun *sentryHint;
+ for( sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( NULL, "bot_hint_sentrygun" ) );
+ sentryHint;
+ sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( sentryHint, "bot_hint_sentrygun" ) ) )
+ {
+ // clear the previous owner if it is us
+ if ( sentryHint->GetPlayerOwner() == me )
+ {
+ sentryHint->SetPlayerOwner( NULL );
+ }
+ if ( sentryHint->IsAvailableForSelection( me ) )
+ {
+ Vector toMe = me->GetAbsOrigin() - sentryHint->GetAbsOrigin();
+ float dist2 = toMe.LengthSqr();
+ if ( dist2 < bestDist2 )
+ {
+ bestSentryHint = sentryHint;
+ bestDist2 = dist2;
+ }
+ }
+ }
+ if ( bestSentryHint )
+ {
+ bestSentryHint->SetPlayerOwner( me );
+ return TrySuspendFor( new CTFBotEngineerBuilding( bestSentryHint ), RESULT_CRITICAL, "Building a Sentry at a hint location" );
+ }
+ }
+ }
+ else if ( FStrEq( command, "attack sentry at next action point" ) )
+ {
+ return TrySuspendFor( new CTFTrainingAttackSentryActionPoint(), RESULT_CRITICAL, "Received command to attack sentry gun at next action point" );
+ }
+#ifdef STAGING_ONLY
+ // !!! BountyMode prototype evaluation hacks below - this code will most likely be deleted soon
+ else if ( FStrEq( command, "become raider" ) )
+ {
+ me->SetIsMiniBoss( true );
+ me->SetScaleOverride( 1.75f );
+ me->ModifyMaxHealth( 5000 );
+ me->SetWeaponRestriction( CTFBot::PRIMARY_ONLY );
+ me->GetPlayerClass()->SetCustomModel( g_szBotBossModels[ me->GetPlayerClass()->GetClassIndex() ], USE_CLASS_ANIMATIONS );
+ me->UpdateModel();
+ me->SetBloodColor( DONT_BLEED );
+ engine->SetFakeClientConVarValue( me->edict(), "name", "Raider" );
+
+ // Custom attribs
+ struct botAttribs_t
+ {
+ char szName[MAX_ATTRIBUTE_DESCRIPTION_LENGTH];
+ float flValue;
+ };
+
+ botAttribs_t sAttribs[] =
+ {
+ { "move speed bonus", 0.5f },
+ { "damage bonus", 1.5f },
+ { "damage force reduction", 0.3f },
+ { "airblast vulnerability multiplier", 0.3f },
+ { "override footstep sound set", 2.f },
+ };
+
+ CAttributeList *pAttribList = me->GetAttributeList();
+ if ( pAttribList )
+ {
+ for ( int i = 0; i < ARRAYSIZE( sAttribs ); i++ )
+ {
+ const CEconItemAttributeDefinition *pDef = ItemSystem()->GetItemSchema()->GetAttributeDefinitionByName( sAttribs[i].szName );
+ if ( pDef )
+ {
+ pAttribList->SetRuntimeAttributeValue( pDef, sAttribs[i].flValue );
+ }
+ }
+ me->NetworkStateChanged();
+ }
+ }
+ // !!! BountyMode prototype evaluation hacks below - this code will most likely be deleted soon
+ else if ( FStrEq( command, "become guardian" ) )
+ {
+ me->SetIsMiniBoss( true );
+ me->SetScaleOverride( 1.75f );
+ me->ModifyMaxHealth( 3300 );
+ me->SetWeaponRestriction( CTFBot::PRIMARY_ONLY );
+ me->GetPlayerClass()->SetCustomModel( g_szBotBossModels[ me->GetPlayerClass()->GetClassIndex() ], USE_CLASS_ANIMATIONS );
+ me->UpdateModel();
+ me->SetBloodColor( DONT_BLEED );
+ engine->SetFakeClientConVarValue( me->edict(), "name", "Guardian" );
+ me->SetAttribute( CTFBot::PRIORITIZE_DEFENSE );
+
+ // Custom attribs
+ struct botAttribs_t
+ {
+ char szName[MAX_ATTRIBUTE_DESCRIPTION_LENGTH];
+ float flValue;
+ };
+
+ botAttribs_t sAttribs[] =
+ {
+ { "move speed bonus", 0.5f },
+ { "faster reload rate", -0.4f },
+ { "fire rate bonus", 0.75f },
+ { "damage force reduction", 0.5f },
+ { "airblast vulnerability multiplier", 0.5f },
+ { "override footstep sound set", 4.f },
+ };
+
+ CAttributeList *pAttribList = me->GetAttributeList();
+ if ( pAttribList )
+ {
+ for ( int i = 0; i < ARRAYSIZE( sAttribs ); i++ )
+ {
+ const CEconItemAttributeDefinition *pDef = ItemSystem()->GetItemSchema()->GetAttributeDefinitionByName( sAttribs[i].szName );
+ if ( pDef )
+ {
+ pAttribList->SetRuntimeAttributeValue( pDef, sAttribs[i].flValue );
+ }
+ }
+ me->NetworkStateChanged();
+ }
+ }
+#endif // STAGING_ONLY
+
+ return TryContinue();
+}
+
+
+//-----------------------------------------------------------------------------------------
+bool CTFBotTacticalMonitor::ShouldOpportunisticallyTeleport( CTFBot *me ) const
+{
+ // if I'm an engineer who hasn't placed his teleport entrance yet, don't use friend's teleporter
+ if ( me->IsPlayerClass( TF_CLASS_ENGINEER ) )
+ {
+ CBaseObject *teleporterEntrance = me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE );
+
+ return ( teleporterEntrance != NULL );
+ }
+
+ // Medics don't automatically take teleporters unless they actively decide to follow their patient through
+ if ( me->IsPlayerClass( TF_CLASS_MEDIC ) )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------------------
+CObjectTeleporter *CTFBotTacticalMonitor::FindNearbyTeleporter( CTFBot *me )
+{
+ if ( !m_findTeleporterTimer.IsElapsed() )
+ {
+ return NULL;
+ }
+
+ m_findTeleporterTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+
+ CTFNavArea *myArea = (CTFNavArea *)me->GetLastKnownArea();
+ if ( myArea == NULL )
+ {
+ return NULL;
+ }
+
+ CUtlVector< CNavArea * > nearbyAreaVector;
+ CUtlVector< CBaseObject * > objVector;
+ CUtlVector< CObjectTeleporter * > nearbyTeleporterEntranceVector;
+
+ CollectSurroundingAreas( &nearbyAreaVector, myArea, 1000.0f );
+ TheTFNavMesh()->CollectBuiltObjects( &objVector, me->GetTeamNumber() );
+
+ for( int j=0; j<objVector.Count(); ++j )
+ {
+ if ( objVector[j]->GetType() == OBJ_TELEPORTER )
+ {
+ CObjectTeleporter *teleporter = (CObjectTeleporter *)objVector[j];
+
+ teleporter->UpdateLastKnownArea();
+
+ CNavArea *teleporterArea = teleporter->GetLastKnownArea();
+
+ if ( teleporter->IsEntrance() && teleporter->IsReady() && teleporterArea )
+ {
+ // we've found a functional teleporter entrance - is it in our nearby area set?
+ for( int i=0; i<nearbyAreaVector.Count(); ++i )
+ {
+ CNavArea *nearbyArea = nearbyAreaVector[i];
+
+ if ( nearbyArea->GetID() == teleporterArea->GetID() )
+ {
+ // yes, it's nearby
+ nearbyTeleporterEntranceVector.AddToTail( teleporter );
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if ( nearbyTeleporterEntranceVector.Count() > 0 )
+ {
+ int which = RandomInt( 0, nearbyTeleporterEntranceVector.Count()-1 );
+
+ return nearbyTeleporterEntranceVector[ which ];
+ }
+
+ return NULL;
+}