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.
 
 
 
 
 
 

959 lines
25 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_destroy_enemy_sentry.cpp
// Destroy an enemy sentry gun
// Michael Booth, June 2010
#include "cbase.h"
#include "tf_player.h"
#include "tf_obj_sentrygun.h"
#include "tf_weaponbase_gun.h"
#include "bot/tf_bot.h"
#include "bot/behavior/tf_bot_destroy_enemy_sentry.h"
#include "bot/behavior/tf_bot_attack.h"
#include "bot/behavior/tf_bot_retreat_to_cover.h"
#include "bot/behavior/tf_bot_get_ammo.h"
#include "bot/behavior/demoman/tf_bot_stickybomb_sentrygun.h"
#include "nav_mesh.h"
extern ConVar tf_bot_path_lookahead_range;
extern ConVar tf_bot_sticky_base_range;
ConVar tf_bot_debug_destroy_enemy_sentry( "tf_bot_debug_destroy_enemy_sentry", "0", FCVAR_CHEAT );
ConVar tf_bot_max_grenade_launch_at_sentry_range( "tf_bot_max_grenade_launch_at_sentry_range", "1500", FCVAR_CHEAT );
ConVar tf_bot_max_sticky_launch_at_sentry_range( "tf_bot_max_sticky_launch_at_sentry_range", "1500", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
// Search for angle to land grenade near target
bool FindGrenadeAim( CTFBot *me, CBaseEntity *target, float *aimYaw, float *aimPitch )
{
Vector toTarget = target->WorldSpaceCenter() - me->EyePosition();
if ( toTarget.IsLengthGreaterThan( tf_bot_max_grenade_launch_at_sentry_range.GetFloat() ) )
{
return false;
}
QAngle anglesToTarget;
VectorAngles( toTarget, anglesToTarget );
// start with current aim, in case we're already on target
const QAngle &eyeAngles = me->EyeAngles();
float yaw = eyeAngles.y;
float pitch = eyeAngles.x;
const int trials = 10;
for( int t=0; t<trials; ++t )
{
// estimate impact spot
const float pipebombInitVel = 900.0f;
Vector impactSpot = me->EstimateProjectileImpactPosition( pitch, yaw, pipebombInitVel );
// check if impactSpot landed near sentry
const float explosionRadius = 75.0f;
if ( ( target->WorldSpaceCenter() - impactSpot ).IsLengthLessThan( explosionRadius ) )
{
trace_t trace;
NextBotTraceFilterIgnoreActors filter( target, COLLISION_GROUP_NONE );
UTIL_TraceLine( target->WorldSpaceCenter(), impactSpot, MASK_SOLID_BRUSHONLY, &filter, &trace );
if ( !trace.DidHit() )
{
*aimYaw = yaw;
*aimPitch = pitch;
return true;
}
}
yaw = anglesToTarget.y + RandomFloat( -30.0f, 30.0f );
pitch = RandomFloat( -85.0f, 85.0f );
}
return false;
}
//---------------------------------------------------------------------------------------------
// Search for angle to land sticky near target
bool FindStickybombAim( CTFBot *me, CBaseEntity *target, float *aimYaw, float *aimPitch, float *aimCharge )
{
Vector toTarget = target->WorldSpaceCenter() - me->EyePosition();
if ( toTarget.IsLengthGreaterThan( tf_bot_max_sticky_launch_at_sentry_range.GetFloat() ) )
{
return false;
}
QAngle anglesToTarget;
VectorAngles( toTarget, anglesToTarget );
// start with current aim, in case we're already on target
const QAngle &eyeAngles = me->EyeAngles();
float yaw = eyeAngles.y;
float pitch = eyeAngles.x;
*aimCharge = 1.0f;
bool hasTarget = false;
const int trials = 100;
for( int t=0; t<trials; ++t )
{
float charge = 0.0f;
// if ( toTarget.IsLengthGreaterThan( tf_bot_sticky_base_range.GetBool() ) )
// {
// charge = RandomFloat( 0.1f, 1.0f );
//
// // skew towards zero - full charge shots are seldom required
// charge *= charge;
// }
// estimate impact spot
Vector impactSpot = me->EstimateStickybombProjectileImpactPosition( pitch, yaw, charge );
// check if impactSpot landed near target
const float explosionRadius = 75.0f;
if ( ( target->WorldSpaceCenter() - impactSpot ).IsLengthLessThan( explosionRadius ) )
{
trace_t trace;
NextBotTraceFilterIgnoreActors filter( target, COLLISION_GROUP_NONE );
UTIL_TraceLine( target->WorldSpaceCenter(), impactSpot, MASK_SOLID_BRUSHONLY, &filter, &trace );
if ( !trace.DidHit() )
{
// found target aim - keep one we find with least required
// charge, because we need to be fast in combat
if ( charge < (*aimCharge) )
{
hasTarget = true;
*aimCharge = charge;
*aimYaw = yaw;
*aimPitch = pitch;
if ( *aimCharge < 0.01 )
{
// as quick as possible - no need to search further
break;
}
}
}
}
yaw = anglesToTarget.y + RandomFloat( -30.0f, 30.0f );
pitch = RandomFloat( -85.0f, 85.0f );
}
return hasTarget;
}
//---------------------------------------------------------------------------------------------
// Return true if this Action has what it needs to perform right now
bool CTFBotDestroyEnemySentry::IsPossible( CTFBot *me )
{
if ( me->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ||
me->IsPlayerClass( TF_CLASS_SNIPER ) ||
me->IsPlayerClass( TF_CLASS_MEDIC ) ||
me->IsPlayerClass( TF_CLASS_ENGINEER ) ||
me->IsPlayerClass( TF_CLASS_PYRO ) )
{
// these classes have no way to kill a sentry at long range
return false;
}
// don't go after a sentry if we're out of ammo
if ( me->GetAmmoCount( TF_AMMO_PRIMARY ) <= 0 || me->GetAmmoCount( TF_AMMO_SECONDARY ) <= 0 )
{
return false;
}
// if we're a spy, we have better ways of destroying sentries that shooting at it
if ( me->IsPlayerClass( TF_CLASS_SPY ) )
{
return false;
}
#ifdef TF_RAID_MODE
if ( TFGameRules()->IsRaidMode() )
{
if ( me->GetTeamNumber() == TF_TEAM_PVE_INVADERS )
{
return false;
}
}
#endif
if ( TFGameRules()->IsMannVsMachineMode() )
{
if ( me->GetTeamNumber() == TF_TEAM_PVE_INVADERS )
{
return false;
}
}
return true;
}
//---------------------------------------------------------------------------------------------
class CFindSafeAttackArea : public ISearchSurroundingAreasFunctor
{
public:
CFindSafeAttackArea( CTFBot *me )
{
m_me = me;
m_attackSpot = me->GetAbsOrigin();
m_foundAttackSpot = false;
CObjectSentrygun *sentry = me->GetEnemySentry();
if ( sentry )
{
sentry->UpdateLastKnownArea();
m_sentryArea = (CTFNavArea *)sentry->GetLastKnownArea();
}
else
{
m_sentryArea = NULL;
}
}
virtual bool operator() ( CNavArea *area, CNavArea *priorArea, float travelDistanceSoFar )
{
if ( !m_sentryArea )
{
return false;
}
if ( area->IsPotentiallyVisible( m_sentryArea ) )
{
// try the center first
m_attackSpot = area->GetCenter();
const int maxTries = 5;
for( int i=0; i<maxTries; ++i )
{
if ( m_me->IsLineOfFireClear( m_attackSpot + m_me->GetClassEyeHeight(), m_me->GetEnemySentry() ) )
{
if ( ( m_attackSpot - m_me->GetEnemySentry()->GetAbsOrigin() ).IsLengthGreaterThan( 1.1f * SENTRY_MAX_RANGE ) )
{
// found our attack spot
m_foundAttackSpot = true;
return false;
}
}
m_attackSpot = area->GetRandomPoint();
}
}
return true;
}
CTFBot *m_me;
CTFNavArea *m_sentryArea;
Vector m_attackSpot;
bool m_foundAttackSpot;
Vector m_splashFromSpot;
Vector m_splashToSpot;
bool m_foundSplashSpot;
};
//---------------------------------------------------------------------------------------------
void CTFBotDestroyEnemySentry::ComputeSafeAttackSpot( CTFBot *me )
{
m_hasSafeAttackSpot = false;
CObjectSentrygun *sentry = me->GetEnemySentry();
if ( sentry == NULL )
{
return;
}
sentry->UpdateLastKnownArea();
CTFNavArea *sentryArea = (CTFNavArea *)sentry->GetLastKnownArea();
if ( sentryArea == NULL )
{
return;
}
NavAreaCollector collector( true );
sentryArea->ForAllPotentiallyVisibleAreas( collector );
int i;
CUtlVector< CTFNavArea * > beyondSentryRangeVector;
for( i=0; i<collector.m_area.Count(); ++i )
{
CTFNavArea *area = (CTFNavArea *)collector.m_area[i];
Vector wayOut = ( area->GetCenter() - sentryArea->GetCenter() ) + area->GetCenter();
Vector farthestFromSentry;
area->GetClosestPointOnArea( wayOut, &farthestFromSentry );
if ( ( farthestFromSentry - sentry->GetAbsOrigin() ).IsLengthGreaterThan( SENTRY_MAX_RANGE ) )
{
// at least some of this area is out of sentry range
beyondSentryRangeVector.AddToTail( area );
if ( tf_bot_debug_destroy_enemy_sentry.GetBool() )
{
area->DrawFilled( 0, 255, 0, 255, 60.0f, true, 1.0f );
}
}
}
CUtlVector< CTFNavArea * > attackSentryVector;
for( i=0; i<beyondSentryRangeVector.Count(); ++i )
{
CTFNavArea *area = beyondSentryRangeVector[i];
Vector closestToSentry;
area->GetClosestPointOnArea( sentry->GetAbsOrigin(), &closestToSentry );
if ( ( closestToSentry - sentry->GetAbsOrigin() ).IsLengthLessThan( 1.5f * SENTRY_MAX_RANGE ) )
{
// good attack range
attackSentryVector.AddToTail( area );
if ( tf_bot_debug_destroy_enemy_sentry.GetBool() )
{
area->DrawFilled( 100, 255, 0, 255, 60.0f );
}
}
}
if ( beyondSentryRangeVector.Count() == 0 )
{
// no safe areas at all
m_hasSafeAttackSpot = false;
return;
}
CUtlVector< CTFNavArea * > *safeAreaVector;
if ( attackSentryVector.Count() == 0 )
{
// no good close-in attack areas, choose from farther away set
safeAreaVector = &beyondSentryRangeVector;
}
else
{
// for now, just pick a random spot
safeAreaVector = &attackSentryVector;
}
// TODO: find closest and least combat-hot area
CTFNavArea *safeArea = safeAreaVector->Element( RandomInt( 0, safeAreaVector->Count()-1 ) );
m_safeAttackSpot = safeArea->GetRandomPoint();
m_hasSafeAttackSpot = true;
if ( tf_bot_debug_destroy_enemy_sentry.GetBool() )
{
safeArea->DrawFilled( 255, 255, 0, 255, 60.0f );
NDebugOverlay::Cross3D( m_safeAttackSpot, 10.0f, 255, 0, 0, true, 60.0f );
}
}
//---------------------------------------------------------------------------------------------
class FindSafeSentryApproachAreaScan : public ISearchSurroundingAreasFunctor
{
public:
FindSafeSentryApproachAreaScan( CTFBot *me )
{
m_me = me;
m_isEscaping = false;
CTFNavArea *myArea = me->GetLastKnownArea();
if ( myArea && myArea->IsTFMarked() )
{
// I'm standing in a danger area - escape!
m_isEscaping = true;
}
}
virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
{
CTFNavArea *area = (CTFNavArea *)baseArea;
if ( m_isEscaping )
{
if ( !area->IsTFMarked() )
{
// found safe area - use it
m_approachAreaVector.AddToTail( area );
return false;
}
}
else
{
if ( area->IsTFMarked() && priorArea )
{
// we just stepped into sentry fire - keep the area one step prior
m_approachAreaVector.AddToTail( (CTFNavArea *)priorArea );
}
}
return true;
}
// return true if 'adjArea' should be included in the ongoing search
virtual bool ShouldSearch( CNavArea *baseAdjArea, CNavArea *baseCurrentArea, float travelDistanceSoFar )
{
CTFNavArea *area = (CTFNavArea *)baseCurrentArea;
if ( !m_isEscaping )
{
// don't search beyond sentry danger areas (but step into them)
if ( area->IsTFMarked() )
{
return false;
}
}
return m_me->GetLocomotionInterface()->IsAreaTraversable( baseAdjArea );
}
// Invoked after the search has completed
virtual void PostSearch( void )
{
if ( tf_bot_debug_destroy_enemy_sentry.GetBool() )
{
for( int i=0; i<m_approachAreaVector.Count(); ++i )
{
m_approachAreaVector[i]->DrawFilled( 0, 255, 0, 255, 60.0f );
}
}
}
CTFBot *m_me;
CUtlVector< CTFNavArea * > m_approachAreaVector;
bool m_isEscaping;
};
//---------------------------------------------------------------------------------------------
void CTFBotDestroyEnemySentry::ComputeCornerAttackSpot( CTFBot *me )
{
m_safeAttackSpot = vec3_origin;
m_hasSafeAttackSpot = false;
CObjectSentrygun *sentry = me->GetEnemySentry();
if ( !sentry )
{
return;
}
sentry->UpdateLastKnownArea();
CTFNavArea *sentryArea = (CTFNavArea *)sentry->GetLastKnownArea();
if ( !sentryArea )
{
return;
}
// mark all areas this sentry can potentially fire upon
// need to use completely visible so the partially visible areas are used as corner-fighting spots
NavAreaCollector sentryDanger;
sentryArea->ForAllCompletelyVisibleAreas( sentryDanger );
CTFNavArea::MakeNewTFMarker();
for( int i=0; i<sentryDanger.m_area.Count(); ++i )
{
CTFNavArea *area = (CTFNavArea *)sentryDanger.m_area[i];
Vector close;
area->GetClosestPointOnArea( sentry->GetAbsOrigin(), &close );
if ( ( sentry->GetAbsOrigin() - close ).IsLengthLessThan( SENTRY_MAX_RANGE ) )
{
area->TFMark();
if ( tf_bot_debug_destroy_enemy_sentry.GetBool() )
{
area->DrawFilled( 255, 0, 0, 255, 60.0f );
}
}
}
// find nearby area adjacent to area that is in enemy sentry fire field
FindSafeSentryApproachAreaScan scan( me );
SearchSurroundingAreas( me->GetLastKnownArea(), scan );
if ( scan.m_approachAreaVector.Count() > 0 )
{
CTFNavArea *safeArea = scan.m_approachAreaVector[ RandomInt( 0, scan.m_approachAreaVector.Count()-1 ) ];
// try to avoid picking a spot where sentry can attack us
const int retryCount = 25;
for( int r=0; r<retryCount; ++r )
{
m_safeAttackSpot = safeArea->GetRandomPoint();
if ( ( sentry->WorldSpaceCenter() - m_safeAttackSpot ).IsLengthGreaterThan( SENTRY_MAX_RANGE ) ||
!me->IsLineOfFireClear( sentry->WorldSpaceCenter(), m_safeAttackSpot ) )
{
break;
}
}
m_hasSafeAttackSpot = true;
if ( tf_bot_debug_destroy_enemy_sentry.GetBool() )
{
NDebugOverlay::Cross3D( m_safeAttackSpot, 5.0f, 255, 255, 0, true, 60.0f );
}
}
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotDestroyEnemySentry::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_path.Invalidate();
m_repathTimer.Invalidate();
m_isAttackingSentry = false;
m_wasUber = false;
/*
// find a spot to attack the sentry out of its range
CFindSafeAttackArea find( me );
SearchSurroundingAreas( me->GetLastKnownArea(), find, 1.5f * SENTRY_MAX_RANGE );
m_hasSafeAttackSpot = find.m_foundAttackSpot;
m_safeAttackSpot = find.m_attackSpot;
*/
if ( me->IsPlayerClass( TF_CLASS_DEMOMAN ) )
{
ComputeCornerAttackSpot( me );
}
else
{
ComputeSafeAttackSpot( me );
}
/*
if ( !m_hasSafeAttackSpot )
{
return Done( "No safe attack spot found" );
}
*/
m_targetSentry = me->GetEnemySentry();
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotDestroyEnemySentry::Update( CTFBot *me, float interval )
{
if ( me->GetEnemySentry() == NULL )
{
return Done( "Enemy sentry is destroyed" );
}
// if the sentry changes, re-evaluate
if ( me->GetEnemySentry() != m_targetSentry )
{
return ChangeTo( new CTFBotDestroyEnemySentry, "Changed sentry target" );
}
if ( me->m_Shared.IsInvulnerable() )
{
if ( !m_wasUber )
{
m_wasUber = true;
// we just became uber - are we close enough to rush the sentry?
const float maxRushDistance = 500.0f;
CTFBotPathCost cost( me, FASTEST_ROUTE );
float travelDistance = NavAreaTravelDistance( me->GetLastKnownArea(),
m_targetSentry->GetLastKnownArea(),
cost, maxRushDistance );
if ( travelDistance >= 0.0f )
{
return SuspendFor( new CTFBotUberAttackEnemySentry( m_targetSentry ), "Go get it!" );
}
}
}
else
{
m_wasUber = false;
}
if ( !me->HasAttribute( CTFBot::IGNORE_ENEMIES ) )
{
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleInFOVNow() )
{
float threatRange = me->GetRangeTo( threat->GetLastKnownPosition() );
float sentryRange = me->GetRangeTo( me->GetEnemySentry() );
if ( threatRange < 0.5f * sentryRange )
{
return Done( "Enemy near" );
}
}
}
bool isSentryFiringOnMe = false;
if ( me->GetEnemySentry()->GetTimeSinceLastFired() < 1.0f )
{
Vector sentryForward;
AngleVectors( me->GetEnemySentry()->GetTurretAngles(), &sentryForward );
Vector to = me->GetAbsOrigin() - me->GetEnemySentry()->GetAbsOrigin();
to.NormalizeInPlace();
if ( DotProduct( to, sentryForward ) > 0.8f )
{
isSentryFiringOnMe = true;
}
}
if ( me->IsPlayerClass( TF_CLASS_DEMOMAN ) )
{
// a demoman wants to get close to the sentry but just out of range or line of sight so
// he can pepper the area with stickies and destroy it
Vector attackSpot = m_hasSafeAttackSpot ? m_safeAttackSpot : m_targetSentry->GetAbsOrigin();
// move into position
if ( !m_path.IsValid() || m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( 1.0f );
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, attackSpot, cost );
}
float aimPitch, aimYaw, aimCharge;
if ( isSentryFiringOnMe )
{
// the sentry is firing on me - might as well shoot back!
me->EquipLongRangeWeapon();
me->PressFireButton();
}
else if ( FindStickybombAim( me, m_targetSentry, &aimYaw, &aimPitch, &aimCharge ) )
{
// found an opportunistic spot to sticky the sentry from
return ChangeTo( new CTFBotStickybombSentrygun( me->GetEnemySentry(), aimYaw, aimPitch, aimCharge ), "Destroying sentry with opportunistic sticky shot" );
}
// move towards sentry
if ( m_canMove )
{
m_path.Update( me );
}
if ( ( me->IsRangeLessThan( attackSpot, 50.0f ) &&
( me->GetAbsOrigin() - attackSpot ).AsVector2D().IsLengthLessThan( 25.0f ) ) ||
( me->IsLineOfFireClear( me->GetEnemySentry() ) && me->IsRangeLessThan( m_targetSentry, 1000.0f ) ) ) // opportunistic shot
{
// reached attack spot
return ChangeTo( new CTFBotStickybombSentrygun( me->GetEnemySentry() ), "Destroying sentry with stickies" );
}
if ( me->IsRangeLessThan( attackSpot, 200.0f ) )
{
#ifdef TF_CREEP_MODE
if ( m_creepTimer.IsElapsed() )
{
m_canMove = !m_canMove;
if ( m_canMove )
{
m_creepTimer.Start( 0.1f );
}
else
{
m_creepTimer.Start( RandomFloat( 0.2f, 0.5f ) );
}
}
#endif
}
else
{
m_canMove = true;
}
return Continue();
}
bool isInAttackPosition = ( m_hasSafeAttackSpot && me->IsRangeLessThan( m_safeAttackSpot, 20.0f ) );
if ( isInAttackPosition || me->IsLineOfFireClear( me->GetEnemySentry() ) )
{
// must look at sentry entity to make use of SelectTargetPoint()
me->GetBodyInterface()->AimHeadTowards( me->GetEnemySentry(), IBody::MANDATORY, 1.0f, NULL, "Aiming at enemy sentry" );
// because sentries are stationary, check if XY is on target to allow SelectTargetPoint() to adjust Z for grenades
Vector toSentry = me->GetEnemySentry()->WorldSpaceCenter() - me->EyePosition();
toSentry.NormalizeInPlace();
Vector forward;
me->EyeVectors( &forward );
if ( ( forward.x * toSentry.x + forward.y * toSentry.y ) > 0.95f )
{
if ( me->EquipLongRangeWeapon() == false )
{
return SuspendFor( new CTFBotRetreatToCover( 0.1f ), "No suitable range weapon available right now" );
}
me->PressFireButton();
m_isAttackingSentry = true;
}
else
{
m_isAttackingSentry = false;
}
if ( me->IsRangeGreaterThan( me->GetEnemySentry(), 1.1f * SENTRY_MAX_RANGE ) )
{
// safely out of range of the gun - hold here and fire at it
return Continue();
}
// we are in range of the gun - if it is pointed at us and firing, retreat to cover
if ( me->GetEnemySentry()->GetTimeSinceLastFired() < 1.0f )
{
Vector sentryForward;
AngleVectors( me->GetEnemySentry()->GetTurretAngles(), &sentryForward );
Vector to = me->GetAbsOrigin() - me->GetEnemySentry()->GetAbsOrigin();
to.NormalizeInPlace();
if ( DotProduct( to, sentryForward ) > 0.8f )
{
return SuspendFor( new CTFBotRetreatToCover( 0.1f ), "Taking cover from sentry fire" );
}
}
if ( isInAttackPosition )
{
// we're at our attack position, hold here
return Continue();
}
}
// move into position
if ( !m_path.IsValid() || m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( 1.0f );
CTFBotPathCost cost( me, SAFEST_ROUTE );
Vector moveGoal = m_hasSafeAttackSpot ? m_safeAttackSpot : me->GetEnemySentry()->GetAbsOrigin();
if ( !m_path.Compute( me, moveGoal, cost ) )
{
return Done( "No path" );
}
}
// move along path to vantage point
m_path.Update( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotDestroyEnemySentry::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_path.Invalidate();
m_repathTimer.Invalidate();
if ( me->IsPlayerClass( TF_CLASS_DEMOMAN ) )
{
ComputeCornerAttackSpot( me );
}
else
{
ComputeSafeAttackSpot( me );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotDestroyEnemySentry::ShouldHurry( const INextBot *me ) const
{
// while killing a sentry we're "hurrying" so we don't dodge
return m_isAttackingSentry ? ANSWER_YES : ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotDestroyEnemySentry::ShouldRetreat( const INextBot *me ) const
{
// push in to kill the sentry
return ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotDestroyEnemySentry::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
// if we're in range to attack the sentry, we handle firing directly
return m_isAttackingSentry ? ANSWER_NO : ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
CTFBotUberAttackEnemySentry::CTFBotUberAttackEnemySentry( CObjectSentrygun *sentryTarget )
{
m_targetSentry = sentryTarget;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotUberAttackEnemySentry::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_wasIgnoringEnemies = me->HasAttribute( CTFBot::IGNORE_ENEMIES );
me->SetAttribute( CTFBot::IGNORE_ENEMIES );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotUberAttackEnemySentry::Update( CTFBot *me, float interval )
{
if ( !me->m_Shared.InCond( TF_COND_INVULNERABLE ) )
{
return Done( "No longer uber" );
}
if ( m_targetSentry == NULL )
{
return Done( "Target sentry destroyed" );
}
float aimYaw, aimPitch;
if ( me->IsPlayerClass( TF_CLASS_DEMOMAN ) && FindGrenadeAim( me, m_targetSentry, &aimYaw, &aimPitch ) )
{
QAngle aimAngles;
aimAngles.x = aimPitch;
aimAngles.y = aimYaw;
aimAngles.z = 0.0f;
Vector aimForward;
AngleVectors( aimAngles, &aimForward );
// always recompute eye aim target so we can update our view
Vector eyeAimTarget = me->EyePosition() + 5000.0f * aimForward;
me->GetBodyInterface()->AimHeadTowards( eyeAimTarget, IBody::CRITICAL, 0.3f, NULL, "Aiming at opportunistic grenade shot" );
Vector eyeForward;
me->EyeVectors( &eyeForward );
if ( DotProduct( aimForward, eyeForward ) > 0.9f )
{
if ( me->EquipLongRangeWeapon() == false )
{
return SuspendFor( new CTFBotRetreatToCover( 0.1f ), "No suitable range weapon available right now" );
}
me->PressFireButton();
}
}
else if ( me->IsLineOfFireClear( m_targetSentry ) )
{
// must look at sentry entity to make use of SelectTargetPoint()
me->GetBodyInterface()->AimHeadTowards( m_targetSentry, IBody::MANDATORY, 1.0f, NULL, "Aiming at target sentry" );
// because sentries are stationary, check if XY is on target to allow SelectTargetPoint() to adjust Z for grenades
Vector toSentry = m_targetSentry->WorldSpaceCenter() - me->EyePosition();
toSentry.NormalizeInPlace();
Vector eyeForward;
me->EyeVectors( &eyeForward );
if ( ( eyeForward.x * toSentry.x + eyeForward.y * toSentry.y ) > 0.95f )
{
if ( me->EquipLongRangeWeapon() == false )
{
return SuspendFor( new CTFBotRetreatToCover( 0.1f ), "No suitable range weapon available right now" );
}
me->PressFireButton();
}
if ( me->IsRangeLessThan( m_targetSentry, 100.0f ) )
{
// we have a clear line of fire and are close enough
return Continue();
}
}
// move into position
if ( !m_path.IsValid() || m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( 1.0f );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, m_targetSentry->WorldSpaceCenter(), cost );
}
m_path.Update( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotUberAttackEnemySentry::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
if ( !m_wasIgnoringEnemies )
{
me->ClearAttribute( CTFBot::IGNORE_ENEMIES );
}
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotUberAttackEnemySentry::ShouldHurry( const INextBot *me ) const
{
return ANSWER_YES;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotUberAttackEnemySentry::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotUberAttackEnemySentry::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
return ANSWER_YES;
}