Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

412 lines
11 KiB

//========= 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;
}