summaryrefslogtreecommitdiff
path: root/game/server/tf/bot/behavior/tf_bot_destroy_enemy_sentry.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/bot/behavior/tf_bot_destroy_enemy_sentry.cpp')
-rw-r--r--game/server/tf/bot/behavior/tf_bot_destroy_enemy_sentry.cpp959
1 files changed, 959 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/tf_bot_destroy_enemy_sentry.cpp b/game/server/tf/bot/behavior/tf_bot_destroy_enemy_sentry.cpp
new file mode 100644
index 0000000..e539436
--- /dev/null
+++ b/game/server/tf/bot/behavior/tf_bot_destroy_enemy_sentry.cpp
@@ -0,0 +1,959 @@
+//========= 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;
+}