This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions
@@ -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;
}
@@ -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
@@ -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;
}
@@ -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
@@ -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;
}
@@ -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
@@ -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;
}
@@ -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
@@ -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;
}
@@ -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
@@ -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;
}
@@ -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
@@ -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;
}
@@ -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
@@ -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;
}
@@ -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