diff options
Diffstat (limited to 'game/server/tf/bot/behavior/spy/tf_bot_spy_attack.cpp')
| -rw-r--r-- | game/server/tf/bot/behavior/spy/tf_bot_spy_attack.cpp | 412 |
1 files changed, 412 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; +} + |