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