diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/bot/behavior/spy | |
| download | archived-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')
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 |