summaryrefslogtreecommitdiff
path: root/game/server/tf/bot/behavior/spy
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/bot/behavior/spy
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/tf/bot/behavior/spy')
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_attack.cpp412
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_attack.h49
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_backstab.cpp31
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_backstab.h24
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_escape.cpp31
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_escape.h24
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_hide.cpp236
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_hide.h45
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_infiltrate.cpp335
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_infiltrate.h42
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_leave_spawn_room.cpp160
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_leave_spawn_room.h26
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_lurk.cpp91
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_lurk.h26
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_sap.cpp250
-rw-r--r--game/server/tf/bot/behavior/spy/tf_bot_spy_sap.h42
16 files changed, 1824 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_attack.cpp b/game/server/tf/bot/behavior/spy/tf_bot_spy_attack.cpp
new file mode 100644
index 0000000..8eab11c
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_attack.cpp
@@ -0,0 +1,412 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_attack.cpp
+// Backstab or pistol, as appropriate
+// Michael Booth, June 2010
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/spy/tf_bot_spy_attack.h"
+#include "bot/behavior/tf_bot_retreat_to_cover.h"
+#include "bot/behavior/spy/tf_bot_spy_sap.h"
+
+#include "nav_mesh.h"
+
+extern ConVar tf_bot_path_lookahead_range;
+
+ConVar tf_bot_spy_knife_range( "tf_bot_spy_knife_range", "300", FCVAR_CHEAT, "If threat is closer than this, prefer our knife" );
+ConVar tf_bot_spy_change_target_range_threshold( "tf_bot_spy_change_target_range_threshold", "300", FCVAR_CHEAT );
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotSpyAttack::CTFBotSpyAttack( CTFPlayer *victim ) : m_path( ChasePath::LEAD_SUBJECT )
+{
+ m_victim = victim;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyAttack::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
+ m_isCoverBlown = false;
+
+ if ( m_victim.Get() )
+ {
+ me->GetVisionInterface()->AddKnownEntity( m_victim );
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyAttack::Update( CTFBot *me, float interval )
+{
+ const CKnownEntity *threat = me->GetVisionInterface()->GetKnown( m_victim );
+
+ // opportunistically attack closer threat if they are much closer to us than our existing threat
+ const CKnownEntity *closestThreat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+
+ if ( !threat )
+ {
+ threat = closestThreat;
+ m_isCoverBlown = false;
+ if ( closestThreat )
+ {
+ m_victim = ToTFPlayer( closestThreat->GetEntity() );
+ }
+ }
+ else if ( closestThreat &&
+ closestThreat->GetEntity() &&
+ closestThreat != threat )
+ {
+ float rangeToCurrentThreat = me->GetRangeTo( threat->GetLastKnownPosition() );
+ float rangeToNewThreat = me->GetRangeTo( closestThreat->GetLastKnownPosition() );
+
+ if ( rangeToCurrentThreat - rangeToNewThreat > tf_bot_spy_change_target_range_threshold.GetFloat() )
+ {
+ if ( closestThreat->GetEntity()->IsPlayer() )
+ {
+ threat = closestThreat;
+ m_victim = ToTFPlayer( closestThreat->GetEntity() );
+ m_isCoverBlown = false;
+ }
+ }
+ }
+
+ if ( !threat || threat->IsObsolete() )
+ {
+ return Done( "No threat" );
+ }
+
+
+ CBaseObject *sapTarget = me->GetNearestKnownSappableTarget();
+ if ( sapTarget && me->IsEntityBetweenTargetAndSelf( sapTarget, threat->GetEntity() ) )
+ {
+ return ChangeTo( new CTFBotSpySap( sapTarget ), "Opportunistically sapping an enemy object between my victim and I" );
+ }
+
+ if ( me->IsAnyEnemySentryAbleToAttackMe() )
+ {
+ m_isCoverBlown = true;
+
+ CBaseCombatWeapon *myGun = me->Weapon_GetWeaponByType( TF_WPN_TYPE_PRIMARY );
+ me->Weapon_Switch( myGun );
+
+ return ChangeTo( new CTFBotRetreatToCover, "Escaping sentry fire!" );
+ }
+
+ CTFPlayer *playerThreat = ToTFPlayer( threat->GetEntity() );
+ if ( !playerThreat )
+ {
+ return Done( "Current 'threat' is not a player or a building?" );
+ }
+
+ // remember who we are attacking (in case we changed our minds)
+ m_victim = playerThreat;
+
+ // uncloak so we can attack
+ if ( me->m_Shared.IsStealthed() && m_decloakTimer.IsElapsed() )
+ {
+ me->PressAltFireButton();
+ m_decloakTimer.Start( 1.0f );
+ }
+
+ bool isKnifeFight = false;
+
+ if ( me->m_Shared.InCond( TF_COND_DISGUISED ) ||
+ me->m_Shared.InCond( TF_COND_DISGUISING ) ||
+ me->m_Shared.IsStealthed() )
+ {
+ isKnifeFight = true;
+ }
+
+ Vector playerThreatForward;
+ playerThreat->EyeVectors( &playerThreatForward );
+
+ Vector toPlayerThreat = playerThreat->GetAbsOrigin() - me->GetAbsOrigin();
+ float threatRange = toPlayerThreat.NormalizeInPlace();
+
+ float behindTolerance = 0.0f;
+
+ switch( me->GetDifficulty() )
+ {
+ case CTFBot::EASY: behindTolerance = 0.9f; break;
+ case CTFBot::NORMAL: behindTolerance = 0.7071f; break;
+ case CTFBot::HARD: behindTolerance = 0.2f; break;
+ case CTFBot::EXPERT: behindTolerance = 0.0f; break;
+ }
+
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ behindTolerance = 0.7071f;
+ }
+
+ bool isBehindVictim = DotProduct( playerThreatForward, toPlayerThreat ) > behindTolerance;
+
+ // easy Spies always think they're in position to backstab
+ if ( me->GetDifficulty() == CTFBot::EASY )
+ {
+ isBehindVictim = true;
+ }
+
+ if ( threatRange < tf_bot_spy_knife_range.GetFloat() )
+ {
+ isKnifeFight = true;
+ }
+ else if ( threat->IsVisibleInFOVNow() && isBehindVictim )
+ {
+ // they are facing away from us - go for the backstab
+ isKnifeFight = true;
+ }
+
+ // does my threat know I'm a Spy?
+ if ( me->IsThreatAimingTowardMe( playerThreat, 0.99f ) && me->GetTimeSinceLastInjury( GetEnemyTeam( me->GetTeamNumber() ) ) < 1.0f )
+ {
+ m_isCoverBlown |= ( playerThreat->GetTimeSinceWeaponFired() < 0.25f );
+ }
+
+ if ( m_isCoverBlown ||
+ me->m_Shared.InCond( TF_COND_BURNING ) ||
+ me->m_Shared.InCond( TF_COND_URINE ) ||
+ me->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
+ me->m_Shared.InCond( TF_COND_BLEEDING ) )
+ {
+ isKnifeFight = false;
+ }
+
+ CBaseCombatWeapon *myGun = me->Weapon_GetWeaponByType( isKnifeFight ? TF_WPN_TYPE_MELEE : TF_WPN_TYPE_PRIMARY );
+ me->Weapon_Switch( myGun );
+
+ CTFWeaponBase *myWeapon = me->m_Shared.GetActiveTFWeapon();
+
+ bool isMovingTowardVictim = true;
+
+ if ( myWeapon && myWeapon->IsMeleeWeapon() )
+ {
+ if ( threat->IsVisibleInFOVNow() )
+ {
+ const float circleStrafeRange = 250.0f;
+
+ if ( threatRange < circleStrafeRange )
+ {
+ // we're close - aim our stab attack
+ me->GetBodyInterface()->AimHeadTowards( playerThreat, IBody::MANDATORY, 0.1f, NULL, "Aiming my stab!" );
+
+ if ( !isBehindVictim )
+ {
+ // circle around our victim to get behind them
+ Vector myForward;
+ me->EyeVectors( &myForward );
+
+ Vector cross;
+ CrossProduct( playerThreatForward, myForward, cross );
+
+ if ( cross.z < 0.0f )
+ {
+ me->PressRightButton();
+ }
+ else
+ {
+ me->PressLeftButton();
+ }
+
+ // don't continue to close in if we're already very close so we don't bump them and give ourselves away
+ if ( threatRange < 100.0f )
+ {
+ isMovingTowardVictim = false;
+ }
+ }
+ else if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ if ( m_chuckleTimer.IsElapsed() )
+ {
+ m_chuckleTimer.Start( 1.0f );
+ me->EmitSound( "Spy.MVM_Chuckle" );
+ }
+ }
+ }
+
+ if ( threatRange < me->GetDesiredAttackRange() )
+ {
+ // if we're still disguised, go for the backstab
+ if ( me->m_Shared.InCond( TF_COND_DISGUISED ) )
+ {
+ if ( isBehindVictim || m_isCoverBlown )
+ {
+ // we're behind them (or they're onto us) - backstab!
+ me->PressFireButton();
+ }
+ }
+ else
+ {
+ // we're exposed - stab! stab! stab!
+ me->PressFireButton();
+ }
+ }
+ }
+ }
+ else
+ {
+ // aim our pistol
+ me->GetBodyInterface()->AimHeadTowards( playerThreat, IBody::MANDATORY, 0.1f, NULL, "Aiming my pistol" );
+ }
+
+ if ( isMovingTowardVictim )
+ {
+ // pursue the threat. if not visible, go to the last known position
+ if ( !threat->IsVisibleRecently() ||
+ me->IsRangeGreaterThan( threat->GetEntity()->GetAbsOrigin(), me->GetDesiredAttackRange() ) ||
+ !me->IsLineOfFireClear( threat->GetEntity()->EyePosition() ) )
+ {
+ // if we're at the threat's last known position and he's still not visible, we lost him
+ if ( !threat->IsVisibleRecently() )
+ {
+ if ( me->IsRangeLessThan( threat->GetLastKnownPosition(), 20.0f ) )
+ {
+ me->GetVisionInterface()->ForgetEntity( threat->GetEntity() );
+ return Done( "I lost my target!" );
+ }
+ }
+
+ CTFBotPathCost cost( me, FASTEST_ROUTE );
+ m_path.Update( me, threat->GetEntity(), cost );
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyAttack::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
+{
+ m_victim = NULL;
+ m_path.Invalidate();
+ m_isCoverBlown = false;
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotSpyAttack::OnStuck( CTFBot *me )
+{
+ return TryContinue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotSpyAttack::OnInjured( CTFBot *me, const CTakeDamageInfo &info )
+{
+ if ( me->IsEnemy( info.GetAttacker() ) )
+ {
+ if ( !me->m_Shared.InCond( TF_COND_DISGUISED ) )
+ {
+ // hurt by an enemy and exposed as a spy - flee!
+ m_isCoverBlown = true;
+
+ CBaseCombatWeapon *myGun = me->Weapon_GetWeaponByType( TF_WPN_TYPE_PRIMARY );
+ me->Weapon_Switch( myGun );
+
+ return TryChangeTo( new CTFBotRetreatToCover, RESULT_IMPORTANT, "Time to get out of here!" );
+ }
+ }
+
+ return TryContinue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotSpyAttack::OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result )
+{
+ if ( me->IsEnemy( other ) && other->MyCombatCharacterPointer() )
+ {
+ if ( other->MyCombatCharacterPointer()->IsLookingTowards( me ) )
+ {
+ m_isCoverBlown = true;
+ }
+ }
+
+ return TryContinue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotSpyAttack::ShouldRetreat( const INextBot *me ) const
+{
+ return ANSWER_UNDEFINED;
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotSpyAttack::ShouldHurry( const INextBot *me ) const
+{
+ return ANSWER_YES;
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotSpyAttack::ShouldAttack( const INextBot *meBot, const CKnownEntity *them ) const
+{
+ CTFBot *me = ToTFBot( meBot->GetEntity() );
+
+ if ( m_isCoverBlown ||
+ me->m_Shared.InCond( TF_COND_BURNING ) ||
+ me->m_Shared.InCond( TF_COND_URINE ) ||
+ me->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
+ me->m_Shared.InCond( TF_COND_BLEEDING ) )
+ {
+ // our cover is blown anyway
+ return ANSWER_YES;
+ }
+
+ return ANSWER_NO;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// Use this to signal the enemy we are focusing on, so we dont avoid them
+QueryResultType CTFBotSpyAttack::IsHindrance( const INextBot *me, CBaseEntity *blocker ) const
+{
+ if ( blocker != IS_ANY_HINDRANCE_POSSIBLE )
+ {
+ if ( blocker && m_victim.Get() && blocker->entindex() == m_victim->entindex() )
+ {
+ // don't avoid this guy
+ return ANSWER_NO;
+ }
+ }
+
+ return ANSWER_UNDEFINED;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// Return the more dangerous of the two threats to 'subject', or NULL if we have no opinion
+const CKnownEntity * CTFBotSpyAttack::SelectMoreDangerousThreat( const INextBot *meBot,
+ const CBaseCombatCharacter *subject,
+ const CKnownEntity *threat1,
+ const CKnownEntity *threat2 ) const
+{
+ CTFBot *me = ToTFBot( meBot->GetEntity() );
+
+ if ( me->IsSelf( subject ) )
+ {
+ CTFWeaponBase *myWeapon = me->m_Shared.GetActiveTFWeapon();
+ if ( myWeapon && myWeapon->IsMeleeWeapon() )
+ {
+ // attack the closest victim with my knife
+ if ( me->GetRangeSquaredTo( threat1->GetEntity() ) < me->GetRangeSquaredTo( threat2->GetEntity() ) )
+ {
+ return threat1;
+ }
+
+ return threat2;
+ }
+ }
+
+ return NULL;
+}
+
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_attack.h b/game/server/tf/bot/behavior/spy/tf_bot_spy_attack.h
new file mode 100644
index 0000000..085d043
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_attack.h
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_attack.h
+// Backstab or pistol, as appropriate
+// Michael Booth, June 2010
+
+#ifndef TF_BOT_SPY_ATTACK_H
+#define TF_BOT_SPY_ATTACK_H
+
+#include "Path/NextBotChasePath.h"
+
+
+//-------------------------------------------------------------------------------
+class CTFBotSpyAttack : public Action< CTFBot >
+{
+public:
+ CTFBotSpyAttack( CTFPlayer *victim );
+ virtual ~CTFBotSpyAttack() { }
+
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+
+ virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
+
+ virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
+ virtual EventDesiredResult< CTFBot > OnInjured( CTFBot *me, const CTakeDamageInfo &info );
+ virtual EventDesiredResult< CTFBot > OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result = NULL );
+
+ virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
+ virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
+ virtual QueryResultType ShouldAttack( const INextBot *meBot, const CKnownEntity *them ) const;
+ virtual QueryResultType IsHindrance( const INextBot *me, CBaseEntity *blocker ) const; // use this to signal the enemy we are focusing on, so we dont avoid them
+
+ virtual const CKnownEntity * SelectMoreDangerousThreat( const INextBot *me,
+ const CBaseCombatCharacter *subject,
+ const CKnownEntity *threat1,
+ const CKnownEntity *threat2 ) const; // return the more dangerous of the two threats to 'subject', or NULL if we have no opinion
+
+ virtual const char *GetName( void ) const { return "SpyAttack"; };
+
+private:
+ CHandle< CTFPlayer > m_victim;
+ ChasePath m_path;
+ bool m_isCoverBlown;
+ CountdownTimer m_chuckleTimer;
+ CountdownTimer m_decloakTimer;
+};
+
+
+#endif // TF_BOT_SPY_ATTACK_H
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_backstab.cpp b/game/server/tf/bot/behavior/spy/tf_bot_spy_backstab.cpp
new file mode 100644
index 0000000..db5c69e
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_backstab.cpp
@@ -0,0 +1,31 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_backstab.cpp
+// Chase behind a victim and backstab them
+// Michael Booth, June 2010
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/spy/tf_bot_spy_backstab.h"
+
+extern ConVar tf_bot_path_lookahead_range;
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyBackstab::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyBackstab::Update( CTFBot *me, float interval )
+{
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotSpyBackstab::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
+{
+ return ANSWER_NO;
+}
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_backstab.h b/game/server/tf/bot/behavior/spy/tf_bot_spy_backstab.h
new file mode 100644
index 0000000..1efd0a0
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_backstab.h
@@ -0,0 +1,24 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_backstab.h
+// Chase behind a victim and backstab them
+// Michael Booth, June 2010
+
+#ifndef TF_BOT_SPY_BACKSTAB_H
+#define TF_BOT_SPY_BACKSTAB_H
+
+#include "Path/NextBotPathFollow.h"
+
+class CTFBotSpyBackstab : public Action< CTFBot >
+{
+public:
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+
+ virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
+
+ virtual const char *GetName( void ) const { return "SpyBackstab"; };
+
+private:
+};
+
+#endif // TF_BOT_SPY_BACKSTAB_H
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_escape.cpp b/game/server/tf/bot/behavior/spy/tf_bot_spy_escape.cpp
new file mode 100644
index 0000000..828da5d
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_escape.cpp
@@ -0,0 +1,31 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_escape.cpp
+// Flee!
+// Michael Booth, June 2010
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/spy/tf_bot_spy_escape.h"
+
+extern ConVar tf_bot_path_lookahead_range;
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyEscape::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyEscape::Update( CTFBot *me, float interval )
+{
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotSpyEscape::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
+{
+ return ANSWER_NO;
+}
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_escape.h b/game/server/tf/bot/behavior/spy/tf_bot_spy_escape.h
new file mode 100644
index 0000000..cfd887e
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_escape.h
@@ -0,0 +1,24 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_escape.h
+// Flee!
+// Michael Booth, June 2010
+
+#ifndef TF_BOT_SPY_ESCAPE_H
+#define TF_BOT_SPY_ESCAPE_H
+
+#include "Path/NextBotPathFollow.h"
+
+class CTFBotSpyEscape : public Action< CTFBot >
+{
+public:
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+
+ virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
+
+ virtual const char *GetName( void ) const { return "SpyEscape"; };
+
+private:
+};
+
+#endif // TF_BOT_SPY_ESCAPE_H
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_hide.cpp b/game/server/tf/bot/behavior/spy/tf_bot_spy_hide.cpp
new file mode 100644
index 0000000..2a35b31
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_hide.cpp
@@ -0,0 +1,236 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_hide.cpp
+// Move to a hiding spot
+// Michael Booth, September 2011
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/spy/tf_bot_spy_hide.h"
+#include "bot/behavior/spy/tf_bot_spy_lurk.h"
+#include "bot/behavior/spy/tf_bot_spy_attack.h"
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotSpyHide::CTFBotSpyHide( CTFPlayer *victim )
+{
+ m_initialVictim = victim;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyHide::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ m_hidingSpot = NULL;
+ m_findTimer.Invalidate();
+ m_isAtGoal = false;
+
+ CTFNavArea *myArea = me->GetLastKnownArea();
+
+ int enemyTeam = GetEnemyTeam( me->GetTeamNumber() );
+
+ m_incursionThreshold = myArea ? myArea->GetIncursionDistance( enemyTeam ) : FLT_MAX;
+ if ( m_incursionThreshold < 0.0f )
+ {
+ m_incursionThreshold = FLT_MAX;
+ }
+
+ m_talkTimer.Start( RandomFloat( 5.0f, 10.0f ) );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyHide::Update( CTFBot *me, float interval )
+{
+ if ( m_initialVictim != NULL && !me->GetVisionInterface()->IsIgnored( m_initialVictim ) )
+ {
+ return SuspendFor( new CTFBotSpyAttack( m_initialVictim ), "Going after our initial victim" );
+ }
+
+ // go after victims we've gotten behind
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+ if ( threat && threat->GetTimeSinceLastKnown() < 3.0f )
+ {
+ CTFPlayer *victim = ToTFPlayer( threat->GetEntity() );
+ if ( victim )
+ {
+ const float attackRange = 750.0f;
+ if ( me->IsRangeLessThan( victim, attackRange ) )
+ {
+ if ( !victim->IsLookingTowards( me ) || victim->IsFiringWeapon() )
+ {
+ return SuspendFor( new CTFBotSpyAttack( victim ), "Opportunistic attack or self defense!" );
+ }
+ }
+ }
+ }
+
+ if ( m_talkTimer.IsElapsed() )
+ {
+ m_talkTimer.Start( RandomFloat( 5.0f, 10.0f ) );
+ me->EmitSound( "Spy.TeaseVictim" );
+ }
+
+ if ( m_isAtGoal )
+ {
+ // Quiet everyone! We are hiding now!
+ CTFNavArea *myArea = me->GetLastKnownArea();
+ if ( myArea )
+ {
+ int enemyTeam = GetEnemyTeam( me->GetTeamNumber() );
+
+ m_incursionThreshold = myArea->GetIncursionDistance( enemyTeam );
+ }
+
+ return SuspendFor( new CTFBotSpyLurk, "Reached hiding spot - lurking" );
+ }
+
+ if ( m_hidingSpot == NULL && m_findTimer.IsElapsed() )
+ {
+ FindHidingSpot( me );
+ }
+
+ // move to our hiding spot
+ m_path.Update( me );
+
+ // path following may invalidate our hiding spot (OnMoveToFailure())
+ if ( m_hidingSpot == NULL )
+ {
+ return Continue();
+ }
+
+ if ( m_repathTimer.IsElapsed() )
+ {
+ m_repathTimer.Start( RandomFloat( 0.3f, 0.5f ) );
+
+ CTFBotPathCost cost( me, SAFEST_ROUTE );
+ m_path.Compute( me, m_hidingSpot->GetPosition(), cost );
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyHide::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
+{
+ m_hidingSpot = NULL;
+ m_isAtGoal = false;
+ m_initialVictim = NULL;
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotSpyHide::OnMoveToSuccess( CTFBot *me, const Path *path )
+{
+ m_isAtGoal = true;
+
+ return TryContinue( RESULT_CRITICAL );
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotSpyHide::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
+{
+ m_hidingSpot = NULL;
+ m_isAtGoal = false;
+
+ return TryContinue( RESULT_IMPORTANT );
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotSpyHide::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
+{
+ return ANSWER_NO;
+}
+
+struct IncursionEntry_t
+{
+ int team;
+ CTFNavArea *area;
+};
+
+//---------------------------------------------------------------------------------------------
+class SpyHideIncursionDistanceLess
+{
+public:
+ bool Less( const IncursionEntry_t &src1, const IncursionEntry_t &src2, void *pCtx )
+ {
+ return src1.area->GetIncursionDistance( src1.team ) < src2.area->GetIncursionDistance( src2.team );
+ }
+};
+
+
+//---------------------------------------------------------------------------------------------
+bool CTFBotSpyHide::FindHidingSpot( CTFBot *me )
+{
+ CTFNavArea *myArea = me->GetLastKnownArea();
+ if ( !myArea )
+ {
+ return false;
+ }
+
+ m_hidingSpot = NULL;
+
+ // find a spot to hide
+ const float maxRange = 3500.0f;
+ CUtlVector< CNavArea * > nearbyVector;
+ CollectSurroundingAreas( &nearbyVector, me->GetLastKnownArea(), maxRange,
+ 500.0f, 500.0f );
+
+ CUtlSortVector< IncursionEntry_t, SpyHideIncursionDistanceLess > hidingSpotVector;
+
+ float maxIncursion = m_incursionThreshold + 1000.0f;
+
+ int enemyTeam = GetEnemyTeam( me->GetTeamNumber() );
+
+ // if we are standing in an area the defenders can't reach, don't limit
+ if ( myArea->GetIncursionDistance( enemyTeam ) < 0.0f )
+ {
+ maxIncursion = 9999999;
+ }
+
+ for( int i=0; i<nearbyVector.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)nearbyVector[i];
+
+ if ( area->GetHidingSpots()->Count() <= 0 )
+ {
+ continue;
+ }
+
+ if ( area->GetIncursionDistance( enemyTeam ) < 0 )
+ {
+ continue;
+ }
+
+ // keep pushing inwards towards defender's spawn
+ if ( area->GetIncursionDistance( enemyTeam ) > maxIncursion )
+ {
+ continue;
+ }
+
+ IncursionEntry_t entry = { enemyTeam, area };
+ hidingSpotVector.Insert( entry );
+ }
+
+ if ( hidingSpotVector.Count() <= 0 )
+ {
+ return false;
+ }
+
+ // penetrate as far as we can
+ int which = RandomInt( 0, hidingSpotVector.Count()/2 );
+ CTFNavArea *whichArea = hidingSpotVector[ which ].area;
+
+ const HidingSpotVector *hidingSpots = whichArea->GetHidingSpots();
+
+ m_hidingSpot = hidingSpots->Element( RandomInt( 0, hidingSpots->Count()-1 ) );
+
+ return true;
+}
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_hide.h b/game/server/tf/bot/behavior/spy/tf_bot_spy_hide.h
new file mode 100644
index 0000000..dedd670
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_hide.h
@@ -0,0 +1,45 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_hide.h
+// Move to a hiding spot
+// Michael Booth, September 2011
+
+#ifndef TF_BOT_SPY_HIDE
+#define TF_BOT_SPY_HIDE
+
+#include "Path/NextBotPathFollow.h"
+
+class CTFBotSpyHide : public Action< CTFBot >
+{
+public:
+ CTFBotSpyHide( CTFPlayer *victim = NULL );
+ virtual ~CTFBotSpyHide() { }
+
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+
+ virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
+
+ virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
+ virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
+
+ virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
+
+ virtual const char *GetName( void ) const { return "SpyHide"; };
+
+private:
+ CHandle< CTFPlayer > m_initialVictim;
+
+ HidingSpot *m_hidingSpot;
+ bool FindHidingSpot( CTFBot *me );
+ CountdownTimer m_findTimer;
+
+ PathFollower m_path;
+ CountdownTimer m_repathTimer;
+ bool m_isAtGoal;
+
+ float m_incursionThreshold;
+
+ CountdownTimer m_talkTimer;
+};
+
+#endif // TF_BOT_SPY_HIDE
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_infiltrate.cpp b/game/server/tf/bot/behavior/spy/tf_bot_spy_infiltrate.cpp
new file mode 100644
index 0000000..ae0de20
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_infiltrate.cpp
@@ -0,0 +1,335 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_infiltrate.cpp
+// Move into position behind enemy lines and wait for victims
+// Michael Booth, June 2010
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_obj_sentrygun.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/spy/tf_bot_spy_infiltrate.h"
+#include "bot/behavior/spy/tf_bot_spy_sap.h"
+#include "bot/behavior/spy/tf_bot_spy_attack.h"
+#include "bot/behavior/tf_bot_retreat_to_cover.h"
+
+#include "nav_mesh.h"
+
+extern ConVar tf_bot_path_lookahead_range;
+
+ConVar tf_bot_debug_spy( "tf_bot_debug_spy", "0", FCVAR_CHEAT );
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyInfiltrate::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ m_hideArea = NULL;
+
+ m_hasEnteredCombatZone = false;
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyInfiltrate::Update( CTFBot *me, float interval )
+{
+ // switch to our pistol
+ CBaseCombatWeapon *myGun = me->Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
+ if ( myGun )
+ {
+ me->Weapon_Switch( myGun );
+ }
+
+ CTFNavArea *myArea = me->GetLastKnownArea();
+
+ if ( !myArea )
+ {
+ return Continue();
+ }
+
+ bool isInMySpawn = myArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED );
+ if ( myArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_EXIT ) )
+ {
+ // don't count exits so we cloak as we leave
+ isInMySpawn = false;
+ }
+
+ // cloak when we first enter an area of active combat
+ if ( !me->m_Shared.IsStealthed() &&
+ !isInMySpawn &&
+ myArea->IsInCombat() &&
+ !m_hasEnteredCombatZone )
+ {
+ m_hasEnteredCombatZone = true;
+ me->PressAltFireButton();
+ }
+
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+ if ( threat && threat->GetEntity() && threat->GetEntity()->IsBaseObject() )
+ {
+ CBaseObject *enemyObject = (CBaseObject *)threat->GetEntity();
+ if ( !enemyObject->HasSapper() && me->IsEnemy( enemyObject ) )
+ {
+ return SuspendFor( new CTFBotSpySap( enemyObject ), "Sapping an enemy object" );
+ }
+ }
+
+ if ( me->GetEnemySentry() && !me->GetEnemySentry()->HasSapper() )
+ {
+ return SuspendFor( new CTFBotSpySap( me->GetEnemySentry() ), "Sapping a Sentry" );
+ }
+
+ if ( !m_hideArea && m_findHidingSpotTimer.IsElapsed() )
+ {
+ FindHidingSpot( me );
+ m_findHidingSpotTimer.Start( 3.0f );
+ }
+
+ if ( !TFGameRules()->InSetup() )
+ {
+ // go after victims we've gotten behind
+ if ( threat && threat->GetTimeSinceLastKnown() < 3.0f )
+ {
+ CTFPlayer *victim = ToTFPlayer( threat->GetEntity() );
+ if ( victim )
+ {
+ CTFNavArea *victimArea = (CTFNavArea *)victim->GetLastKnownArea();
+ if ( victimArea )
+ {
+ int victimTeam = victim->GetTeamNumber();
+
+ if ( victimArea->GetIncursionDistance( victimTeam ) > myArea->GetIncursionDistance( victimTeam ) )
+ {
+ if ( me->m_Shared.IsStealthed() )
+ {
+ return SuspendFor( new CTFBotRetreatToCover( new CTFBotSpyAttack( victim ) ), "Hiding to decloak before going after a backstab victim" );
+ }
+ else
+ {
+ return SuspendFor( new CTFBotSpyAttack( victim ), "Going after a backstab victim" );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( m_hideArea )
+ {
+ if ( tf_bot_debug_spy.GetBool() )
+ {
+ m_hideArea->DrawFilled( 255, 255, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ }
+
+ if ( myArea == m_hideArea )
+ {
+ // stay hidden during setup time
+ if ( TFGameRules()->InSetup() )
+ {
+ m_waitTimer.Start( RandomFloat( 0.0f, 5.0f ) );
+ }
+ else
+ {
+ // wait in our hiding spot for a bit, then try another
+ if ( !m_waitTimer.HasStarted() )
+ {
+ m_waitTimer.Start( RandomFloat( 5.0f, 10.0f ) );
+ }
+ else if ( m_waitTimer.IsElapsed() )
+ {
+ // time to find a new hiding spot
+ m_hideArea = NULL;
+ }
+ }
+ }
+ else
+ {
+ // move to our ambush position
+ if ( m_repathTimer.IsElapsed() )
+ {
+ m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+
+ // we may not be able to path to our hiding spot, but get as close as we can
+ // (dropdown mid spawn in cp_gorge)
+ CTFBotPathCost cost( me, SAFEST_ROUTE );
+ m_path.Compute( me, m_hideArea->GetCenter(), cost );
+ }
+
+ m_path.Update( me );
+
+ m_waitTimer.Invalidate();
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CTFBotSpyInfiltrate::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
+{
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyInfiltrate::OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction )
+{
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyInfiltrate::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
+{
+ m_repathTimer.Invalidate();
+ m_hideArea = NULL;
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+bool CTFBotSpyInfiltrate::FindHidingSpot( CTFBot *me )
+{
+ m_hideArea = NULL;
+
+ if ( me->GetAliveDuration() < 5.0f && TFGameRules()->InSetup() )
+ {
+ // wait a bit until the nav mesh has updated itself
+ return false;
+ }
+
+ int myTeam = me->GetTeamNumber();
+ const CUtlVector< CTFNavArea * > *enemySpawnExitVector = TheTFNavMesh()->GetSpawnRoomExitAreas( GetEnemyTeam( myTeam ) );
+
+#ifdef TF_RAID_MODE
+ if ( TFGameRules()->IsRaidMode() )
+ {
+ // for now, just lurk where we are
+ return false;
+ }
+#endif
+
+ if ( !enemySpawnExitVector || enemySpawnExitVector->Count() == 0 )
+ {
+ if ( tf_bot_debug_spy.GetBool() )
+ {
+ DevMsg( "%3.2f: No enemy spawn room exit areas found\n", gpGlobals->curtime );
+ }
+ return false;
+ }
+
+ // find nearby place to hide hear enemy spawn exit(s)
+ CUtlVector< CNavArea * > nearbyAreaVector;
+ const float nearbyHideRange = 2500.0f;
+ for( int x=0; x<enemySpawnExitVector->Count(); ++x )
+ {
+ CTFNavArea *enemySpawnExitArea = enemySpawnExitVector->Element( x );
+
+ CUtlVector< CNavArea * > nearbyThisExitAreaVector;
+ CollectSurroundingAreas( &nearbyThisExitAreaVector, enemySpawnExitArea, nearbyHideRange, me->GetLocomotionInterface()->GetStepHeight(), me->GetLocomotionInterface()->GetStepHeight() );
+
+ // concat vectors (assuming N^2 unique search would cost more than ripping through some duplicates)
+ nearbyAreaVector.AddVectorToTail( nearbyThisExitAreaVector );
+ }
+
+ // find area not visible to any enemy spawn exits
+ CUtlVector< CTFNavArea * > hideAreaVector;
+ int i;
+
+ for( i=0; i<nearbyAreaVector.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)nearbyAreaVector[i];
+
+ if ( !me->GetLocomotionInterface()->IsAreaTraversable( area ) )
+ continue;
+
+ bool isHidden = true;
+ for( int j=0; j<enemySpawnExitVector->Count(); ++j )
+ {
+ if ( area->IsPotentiallyVisible( enemySpawnExitVector->Element(j) ) )
+ {
+ isHidden = false;
+ break;
+ }
+ }
+
+ if ( isHidden )
+ {
+ hideAreaVector.AddToTail( area );
+ }
+ }
+
+ if ( hideAreaVector.Count() == 0 )
+ {
+ if ( tf_bot_debug_spy.GetBool() )
+ {
+ DevMsg( "%3.2f: Can't find any non-visible hiding areas, trying for anything near the spawn exit...\n", gpGlobals->curtime );
+ }
+
+ for( i=0; i<nearbyAreaVector.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)nearbyAreaVector[i];
+
+ if ( !me->GetLocomotionInterface()->IsAreaTraversable( area ) )
+ continue;
+
+ hideAreaVector.AddToTail( area );
+ }
+ }
+
+ if ( hideAreaVector.Count() == 0 )
+ {
+ if ( tf_bot_debug_spy.GetBool() )
+ {
+ DevMsg( "%3.2f: Can't find any areas near the enemy spawn exit - just heading to the enemy spawn and hoping...\n", gpGlobals->curtime );
+ }
+
+ m_hideArea = enemySpawnExitVector->Element( RandomInt( 0, enemySpawnExitVector->Count()-1 ) );
+
+ return false;
+ }
+
+ // pick a specific hiding spot
+ m_hideArea = hideAreaVector[ RandomInt( 0, hideAreaVector.Count()-1 ) ];
+
+ return true;
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotSpyInfiltrate::OnStuck( CTFBot *me )
+{
+ m_hideArea = NULL;
+ m_findHidingSpotTimer.Invalidate();
+
+ return TryContinue( RESULT_TRY );
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotSpyInfiltrate::OnTerritoryCaptured( CTFBot *me, int territoryID )
+{
+ // enemy spawn likely changed - find new hiding spot after internal data has updated
+ m_hideArea = NULL;
+ m_findHidingSpotTimer.Start( 5.0f );
+
+ return TryContinue( RESULT_TRY );
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotSpyInfiltrate::OnTerritoryLost( CTFBot *me, int territoryID )
+{
+ // enemy spawn likely changed - find new hiding spot after internal data has updated
+ m_hideArea = NULL;
+ m_findHidingSpotTimer.Start( 5.0f );
+
+ return TryContinue( RESULT_TRY );
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotSpyInfiltrate::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
+{
+ return ANSWER_NO;
+}
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_infiltrate.h b/game/server/tf/bot/behavior/spy/tf_bot_spy_infiltrate.h
new file mode 100644
index 0000000..db0be5a
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_infiltrate.h
@@ -0,0 +1,42 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_infiltrate.h
+// Move into position behind enemy lines and wait for victims
+// Michael Booth, June 2010
+
+#ifndef TF_BOT_SPY_INFILTRATE_H
+#define TF_BOT_SPY_INFILTRATE_H
+
+#include "Path/NextBotPathFollow.h"
+
+class CTFBotSpyInfiltrate : public Action< CTFBot >
+{
+public:
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+ virtual void OnEnd( CTFBot *me, Action< CTFBot > *nextAction );
+ virtual ActionResult< CTFBot > OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction );
+ virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
+
+ virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
+ virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID );
+ virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
+
+ virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
+
+ virtual const char *GetName( void ) const { return "SpyInfiltrate"; };
+
+private:
+ CountdownTimer m_repathTimer;
+ PathFollower m_path;
+
+ CTFNavArea *m_hideArea;
+ bool FindHidingSpot( CTFBot *me );
+ CountdownTimer m_findHidingSpotTimer;
+
+ CountdownTimer m_waitTimer;
+
+ bool m_hasEnteredCombatZone;
+};
+
+
+#endif // TF_BOT_SPY_INFILTRATE_H
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_leave_spawn_room.cpp b/game/server/tf/bot/behavior/spy/tf_bot_spy_leave_spawn_room.cpp
new file mode 100644
index 0000000..7ee3d9c
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_leave_spawn_room.cpp
@@ -0,0 +1,160 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_leave_spawn_room.cpp
+// Assume the enemy is watching our spawn - escape it
+// Michael Booth, September 2011
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/spy/tf_bot_spy_leave_spawn_room.h"
+#include "bot/behavior/spy/tf_bot_spy_hide.h"
+
+
+extern bool IsSpaceToSpawnHere( const Vector &where );
+
+//---------------------------------------------------------------------------------------------
+bool TeleportNearVictim( CTFBot *me, CTFPlayer *victim, int attempt )
+{
+ VPROF_BUDGET( "CTFBotSpyLeaveSpawnRoom::TeleportNearVictim", "NextBot" );
+
+ if ( !victim )
+ {
+ return false;
+ }
+
+ CUtlVector< CTFNavArea * > ambushVector; // vector of hidden but near-to-victim areas
+
+ if ( !victim->GetLastKnownArea() )
+ {
+ return false;
+ }
+
+ const float maxSurroundTravelRange = 6000.0f;
+
+ float surroundTravelRange = 1500.0f + 500.0f * attempt;
+ if ( surroundTravelRange > maxSurroundTravelRange )
+ {
+ surroundTravelRange = maxSurroundTravelRange;
+ }
+
+ CUtlVector< CNavArea * > areaVector;
+
+ // collect walkable areas surrounding this victim
+ CollectSurroundingAreas( &areaVector, victim->GetLastKnownArea(), surroundTravelRange, StepHeight, StepHeight );
+
+ // keep subset that isn't visible to the victim's team
+ for( int j=0; j<areaVector.Count(); ++j )
+ {
+ CTFNavArea *area = (CTFNavArea *)areaVector[j];
+
+ if ( !area->IsValidForWanderingPopulation() )
+ {
+ continue;
+ }
+
+ if ( area->IsPotentiallyVisibleToTeam( victim->GetTeamNumber() ) )
+ {
+ continue;
+ }
+
+ ambushVector.AddToTail( area );
+ }
+
+ if ( ambushVector.Count() == 0 )
+ {
+ return false;
+ }
+
+ int maxTries = MIN( 10, ambushVector.Count() );
+
+ for( int retry=0; retry<maxTries; ++retry )
+ {
+ int which = RandomInt( 0, ambushVector.Count()-1 );
+ Vector where = ambushVector[ which ]->GetCenter() + Vector( 0, 0, StepHeight );
+
+ if ( IsSpaceToSpawnHere( where ) )
+ {
+ me->Teleport( &where, &vec3_angle, &vec3_origin );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyLeaveSpawnRoom::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ // disguise as enemy team
+ me->DisguiseAsMemberOfEnemyTeam();
+
+ // cloak
+ me->PressAltFireButton();
+
+ // wait a few moments to guarantee a minimum time between announcing Spies and their attack
+ m_waitTimer.Start( 2.0f + RandomFloat( 0.0f, 1.0f ) );
+
+ m_attempt = 0;
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyLeaveSpawnRoom::Update( CTFBot *me, float interval )
+{
+ VPROF_BUDGET( "CTFBotSpyLeaveSpawnRoom::Update", "NextBot" );
+
+ if ( m_waitTimer.IsElapsed() )
+ {
+ CTFPlayer *victim = NULL;
+
+ CUtlVector< CTFPlayer * > enemyVector;
+ CollectPlayers( &enemyVector, GetEnemyTeam( me->GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS );
+
+ // randomly shuffle our enemies
+ CUtlVector< CTFPlayer * > shuffleVector;
+ shuffleVector = enemyVector;
+ int n = shuffleVector.Count();
+ while( n > 1 )
+ {
+ int k = RandomInt( 0, n-1 );
+ n--;
+
+ CTFPlayer *tmp = shuffleVector[n];
+ shuffleVector[n] = shuffleVector[k];
+ shuffleVector[k] = tmp;
+ }
+
+ for( int i=0; i<shuffleVector.Count(); ++i )
+ {
+ if ( TeleportNearVictim( me, shuffleVector[i], m_attempt ) )
+ {
+ victim = shuffleVector[i];
+ break;
+ }
+ }
+
+ // if we didn't find a victim, try again in a bit
+ if ( !victim )
+ {
+ m_waitTimer.Start( 1.0f );
+
+ ++m_attempt;
+
+ return Continue();
+ }
+
+ return ChangeTo( new CTFBotSpyHide( victim ), "Hiding!" );
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotSpyLeaveSpawnRoom::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
+{
+ return ANSWER_NO;
+}
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_leave_spawn_room.h b/game/server/tf/bot/behavior/spy/tf_bot_spy_leave_spawn_room.h
new file mode 100644
index 0000000..3471749
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_leave_spawn_room.h
@@ -0,0 +1,26 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_leave_spawn_room.h
+// Assume the enemy is watching our spawn - escape it
+// Michael Booth, September 2011
+
+#ifndef TF_BOT_LEAVE_SPAWN_ROOM_H
+#define TF_BOT_LEAVE_SPAWN_ROOM_H
+
+#include "Path/NextBotPathFollow.h"
+
+class CTFBotSpyLeaveSpawnRoom : public Action< CTFBot >
+{
+public:
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+
+ virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
+
+ virtual const char *GetName( void ) const { return "SpyLeaveSpawnRoom"; };
+
+private:
+ CountdownTimer m_waitTimer;
+ int m_attempt;
+};
+
+#endif // TF_BOT_LEAVE_SPAWN_ROOM_H
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_lurk.cpp b/game/server/tf/bot/behavior/spy/tf_bot_spy_lurk.cpp
new file mode 100644
index 0000000..a9379be
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_lurk.cpp
@@ -0,0 +1,91 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_lurk.cpp
+// Wait for victims
+// Michael Booth, September 2011
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_obj_sentrygun.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/spy/tf_bot_spy_lurk.h"
+#include "bot/behavior/spy/tf_bot_spy_sap.h"
+#include "bot/behavior/spy/tf_bot_spy_attack.h"
+#include "bot/behavior/tf_bot_retreat_to_cover.h"
+#include "bot/behavior/spy/tf_bot_spy_sap.h"
+
+#include "nav_mesh.h"
+
+extern ConVar tf_bot_path_lookahead_range;
+extern ConVar tf_bot_debug_spy;
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyLurk::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ // cloak
+ if ( !me->m_Shared.IsStealthed() )
+ {
+ me->PressAltFireButton();
+ }
+
+ // disguise as the enemy team
+ me->DisguiseAsMemberOfEnemyTeam();
+
+ m_lurkTimer.Start( RandomFloat( 3.0f, 5.0f ) );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpyLurk::Update( CTFBot *me, float interval )
+{
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+ if ( threat && threat->GetEntity() )
+ {
+ CBaseObject *enemyObject = dynamic_cast< CBaseObject * >( threat->GetEntity() );
+ if ( enemyObject && !enemyObject->HasSapper() && me->IsEnemy( enemyObject ) )
+ {
+ return SuspendFor( new CTFBotSpySap( enemyObject ), "Sapping an enemy object" );
+ }
+ }
+
+ if ( me->GetEnemySentry() != NULL && !me->GetEnemySentry()->HasSapper() )
+ {
+ return SuspendFor( new CTFBotSpySap( me->GetEnemySentry() ), "Sapping a Sentry" );
+ }
+
+ if ( m_lurkTimer.IsElapsed() )
+ {
+ return Done( "Lost patience with my hiding spot" );
+ }
+
+ CTFNavArea *myArea = me->GetLastKnownArea();
+
+ if ( !myArea )
+ {
+ return Continue();
+ }
+
+ // go after victims we've gotten behind
+ if ( threat && threat->GetTimeSinceLastKnown() < 3.0f )
+ {
+ CTFPlayer *victim = ToTFPlayer( threat->GetEntity() );
+ if ( victim )
+ {
+ if ( !victim->IsLookingTowards( me ) )
+ {
+ return ChangeTo( new CTFBotSpyAttack( victim ), "Going after a backstab victim" );
+ }
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotSpyLurk::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
+{
+ return ANSWER_NO;
+}
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_lurk.h b/game/server/tf/bot/behavior/spy/tf_bot_spy_lurk.h
new file mode 100644
index 0000000..73e2d44
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_lurk.h
@@ -0,0 +1,26 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_lurk.h
+// Wait for victims
+// Michael Booth, September 2011
+
+#ifndef TF_BOT_SPY_LURK_H
+#define TF_BOT_SPY_LURK_H
+
+#include "Path/NextBotPathFollow.h"
+
+class CTFBotSpyLurk : public Action< CTFBot >
+{
+public:
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+
+ virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
+
+ virtual const char *GetName( void ) const { return "SpyLurk"; };
+
+private:
+ CountdownTimer m_lurkTimer;
+};
+
+
+#endif // TF_BOT_SPY_LURK_H
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_sap.cpp b/game/server/tf/bot/behavior/spy/tf_bot_spy_sap.cpp
new file mode 100644
index 0000000..776339a
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_sap.cpp
@@ -0,0 +1,250 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_sap.cpp
+// Sap nearby enemy buildings
+// Michael Booth, June 2010
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_obj_sentrygun.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/spy/tf_bot_spy_sap.h"
+#include "bot/behavior/tf_bot_approach_object.h"
+#include "bot/behavior/spy/tf_bot_spy_attack.h"
+
+extern ConVar tf_bot_path_lookahead_range;
+extern ConVar tf_bot_debug_spy;
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotSpySap::CTFBotSpySap( CBaseObject *sapTarget )
+{
+ m_sapTarget = sapTarget;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpySap::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ me->StopLookingAroundForEnemies();
+
+ // uncloak so we can sap
+ if ( me->m_Shared.IsStealthed() )
+ {
+ me->PressAltFireButton();
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpySap::Update( CTFBot *me, float interval )
+{
+ CBaseObject *newSapTarget = me->GetNearestKnownSappableTarget();
+
+ if ( newSapTarget )
+ {
+ m_sapTarget = newSapTarget;
+ }
+
+ if ( m_sapTarget == NULL )
+ {
+ return Done( "Sap target gone" );
+ }
+
+ CTFPlayer *victim = NULL;
+
+ CUtlVector< CKnownEntity > knownVector;
+ me->GetVisionInterface()->CollectKnownEntities( &knownVector );
+
+ for( int i=0; i<knownVector.Count(); ++i )
+ {
+ CTFPlayer *playerThreat = ToTFPlayer( knownVector[i].GetEntity() );
+ if ( playerThreat && me->IsEnemy( playerThreat ) )
+ {
+ victim = playerThreat;
+ break;
+ }
+ }
+
+ // opportunistic backstab if engineer is between me and my sap target
+ if ( victim && victim->IsPlayerClass( TF_CLASS_ENGINEER ) )
+ {
+ const float nearbyRange = 150.0f;
+ if ( m_sapTarget->GetOwner() == victim && me->IsRangeLessThan( victim, nearbyRange ) )
+ {
+ if ( me->IsEntityBetweenTargetAndSelf( victim, m_sapTarget ) )
+ {
+ return SuspendFor( new CTFBotSpyAttack( victim ), "Backstabbing the engineer before I sap his buildings" );
+ }
+ }
+ }
+
+ const float sapRange = 40.0f;
+
+ if ( me->IsRangeLessThan( m_sapTarget, 2.0f * sapRange ) )
+ {
+ // switch to our sapper and spam it
+ CBaseCombatWeapon *mySapper = me->Weapon_GetWeaponByType( TF_WPN_TYPE_BUILDING );
+ if ( !mySapper )
+ {
+ return Done( "I have no sapper" );
+ }
+
+ me->Weapon_Switch( mySapper );
+
+ // uncloak
+ if ( me->m_Shared.IsStealthed() )
+ {
+ me->PressAltFireButton();
+ }
+
+ // sap our target
+ me->GetBodyInterface()->AimHeadTowards( m_sapTarget, IBody::MANDATORY, 0.1f, NULL, "Aiming my sapper" );
+
+ me->PressFireButton();
+ }
+
+ if ( me->IsRangeGreaterThan( m_sapTarget, sapRange ) )
+ {
+ if ( m_repathTimer.IsElapsed() )
+ {
+ m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+
+ CTFBotPathCost cost( me, FASTEST_ROUTE );
+ if ( m_path.Compute( me, m_sapTarget, cost ) == false )
+ {
+ return Done( "No path to sap target!" );
+ }
+ }
+
+ m_path.Update( me );
+
+ return Continue();
+ }
+
+ // if our target is sapped, look for other nearby buildings to sap
+ if ( m_sapTarget->HasSapper() )
+ {
+ CBaseObject *nextTarget = me->GetNearestKnownSappableTarget();
+ if ( nextTarget )
+ {
+ m_sapTarget = nextTarget;
+ }
+ else
+ {
+ // everything is sapped - explicitly attack nearby enemy Engineers
+ if ( victim && victim->IsPlayerClass( TF_CLASS_ENGINEER ) )
+ {
+ return SuspendFor( new CTFBotSpyAttack( victim ), "Attacking an engineer" );
+ }
+
+ return Done( "All targets sapped" );
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CTFBotSpySap::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
+{
+ me->StartLookingAroundForEnemies();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpySap::OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction )
+{
+ me->StartLookingAroundForEnemies();
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSpySap::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
+{
+ me->StopLookingAroundForEnemies();
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotSpySap::OnStuck( CTFBot *me )
+{
+ return TryDone( RESULT_CRITICAL, "I'm stuck, probably on a sapped building that hasn't exploded yet" );
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotSpySap::ShouldAttack( const INextBot *meBot, const CKnownEntity *them ) const
+{
+ CTFBot *me = ToTFBot( meBot->GetEntity() );
+
+ if ( m_sapTarget && !m_sapTarget->HasSapper() )
+ {
+ // mission not accomplished
+ return ANSWER_NO;
+ }
+
+ if ( !me->m_Shared.InCond( TF_COND_DISGUISED ) &&
+ !me->m_Shared.InCond( TF_COND_DISGUISING ) &&
+ !me->m_Shared.IsStealthed() )
+ {
+ // our cover is blown!
+ return ANSWER_YES;
+ }
+
+ // if we've sapped, attack
+ return AreAllDangerousSentriesSapped( me ) ? ANSWER_YES : ANSWER_NO;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// Don't avoid enemies when we're going in for the sap
+QueryResultType CTFBotSpySap::IsHindrance( const INextBot *me, CBaseEntity *blocker ) const
+{
+ if ( m_sapTarget.Get() && me->IsRangeLessThan( m_sapTarget, 300.0f ) )
+ {
+ // we're almost to our sap target - don't avoid anyone
+ return ANSWER_NO;
+ }
+
+ // avoid everyone while we move to our sap target
+ return ANSWER_UNDEFINED;
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotSpySap::ShouldRetreat( const INextBot *me ) const
+{
+ return ANSWER_NO;
+}
+
+
+//---------------------------------------------------------------------------------------------
+bool CTFBotSpySap::AreAllDangerousSentriesSapped( CTFBot *me ) const
+{
+ CUtlVector< CKnownEntity > knownVector;
+ me->GetVisionInterface()->CollectKnownEntities( &knownVector );
+
+ for( int i=0; i<knownVector.Count(); ++i )
+ {
+ CBaseObject *enemyObject = dynamic_cast< CBaseObject * >( knownVector[i].GetEntity() );
+ if ( enemyObject && enemyObject->ObjectType() == OBJ_SENTRYGUN && !enemyObject->HasSapper() && me->IsEnemy( enemyObject ) )
+ {
+ // this is an active enemy sentry, are we in range and line of fire?
+ if ( me->IsRangeLessThan( enemyObject, SENTRY_MAX_RANGE ) && me->IsLineOfFireClear( enemyObject ) )
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
diff --git a/game/server/tf/bot/behavior/spy/tf_bot_spy_sap.h b/game/server/tf/bot/behavior/spy/tf_bot_spy_sap.h
new file mode 100644
index 0000000..4c8fe77
--- /dev/null
+++ b/game/server/tf/bot/behavior/spy/tf_bot_spy_sap.h
@@ -0,0 +1,42 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_spy_sap.h
+// Sap nearby enemy buildings
+// Michael Booth, June 2010
+
+#ifndef TF_BOT_SPY_SAP_H
+#define TF_BOT_SPY_SAP_H
+
+#include "Path/NextBotPathFollow.h"
+
+class CTFBotSpySap : public Action< CTFBot >
+{
+public:
+ CTFBotSpySap( CBaseObject *sapTarget );
+ virtual ~CTFBotSpySap() { }
+
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+ virtual void OnEnd( CTFBot *me, Action< CTFBot > *nextAction );
+
+ virtual ActionResult< CTFBot > OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction );
+ virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
+
+ virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
+
+ virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
+ virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
+ virtual QueryResultType IsHindrance( const INextBot *me, CBaseEntity *blocker ) const; // use this to signal the enemy we are focusing on, so we dont avoid them
+
+ virtual const char *GetName( void ) const { return "SpySap"; };
+
+private:
+ CHandle< CBaseObject > m_sapTarget;
+
+ CountdownTimer m_repathTimer;
+ PathFollower m_path;
+
+ CBaseObject *GetNearestKnownSappableTarget( CTFBot *me );
+ bool AreAllDangerousSentriesSapped( CTFBot *me ) const;
+};
+
+#endif // TF_BOT_SPY_SAP_H