Skip to content

Commit 4ff2d3b

Browse files
committed
Bots leapfrog between cover areas when attacking
1 parent 69e0228 commit 4ff2d3b

4 files changed

Lines changed: 172 additions & 8 deletions

File tree

src/game/server/neo/bot/behavior/neo_bot_attack.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "cbase.h"
22
#include "neo_player.h"
33
#include "neo_gamerules.h"
4+
#include "neo_smokelineofsightblocker.h"
45
#include "team_control_point_master.h"
56
#include "bot/neo_bot.h"
67
#include "bot/behavior/neo_bot_attack.h"
@@ -17,18 +18,118 @@ ConVar neo_bot_aggressive( "neo_bot_aggressive", "0", FCVAR_NONE );
1718
//---------------------------------------------------------------------------------------------
1819
CNEOBotAttack::CNEOBotAttack( void ) : m_chasePath( ChasePath::LEAD_SUBJECT )
1920
{
21+
m_attackCoverArea = nullptr;
2022
}
2123

2224

2325
//---------------------------------------------------------------------------------------------
2426
ActionResult< CNEOBot > CNEOBotAttack::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction )
2527
{
2628
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
29+
m_attackCoverTimer.Invalidate();
2730

2831
return Continue();
2932
}
3033

3134

35+
//---------------------------------------------------------------------------------------------
36+
// for finding cover closer to our threat
37+
class CSearchForAttackCover : public ISearchSurroundingAreasFunctor
38+
{
39+
public:
40+
CSearchForAttackCover( CNEOBot *me, const CKnownEntity *threat ) : m_me( me ), m_threat( threat )
41+
{
42+
m_attackCoverArea = nullptr;
43+
m_threatArea = threat->GetLastKnownArea();
44+
m_distToThreatSq = ( threat->GetLastKnownPosition() - me->GetAbsOrigin() ).LengthSqr();
45+
}
46+
47+
virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
48+
{
49+
// return true to keep searching, false when suitable cover is found
50+
CNavArea *area = static_cast<CNavArea *>(baseArea);
51+
52+
if ( !m_threatArea )
53+
{
54+
return false; // threat area is unknown
55+
}
56+
57+
constexpr float distanceThresholdRatio = 1.2f;
58+
float candidateDistFromMeSq = ( m_me->GetAbsOrigin() - area->GetCenter() ).LengthSqr();
59+
if ( candidateDistFromMeSq > m_distToThreatSq * distanceThresholdRatio )
60+
{
61+
return true; // not close enough to justify rerouting
62+
}
63+
64+
if ( area->IsPotentiallyVisible( m_threatArea ) )
65+
{
66+
// Even if area does not have hard cover, see if Support can use smoke concealment
67+
if ( m_me->GetClass() == NEO_CLASS_SUPPORT )
68+
{
69+
CNEO_Player *pThreatPlayer = ToNEOPlayer( m_threat->GetEntity() );
70+
if ( pThreatPlayer && ( pThreatPlayer->GetClass() != NEO_CLASS_SUPPORT ) )
71+
{
72+
ScopedSmokeLOS smokeScope( false );
73+
74+
Vector vecThreatEye = m_threat->GetLastKnownPosition() + pThreatPlayer->GetViewOffset();
75+
Vector vecCandidateArea = area->GetCenter() + m_me->GetViewOffset();
76+
77+
trace_t tr;
78+
CTraceFilterSimple filter( m_threat->GetEntity(), COLLISION_GROUP_NONE );
79+
UTIL_TraceLine( vecThreatEye, vecCandidateArea, MASK_BLOCKLOS, &filter, &tr );
80+
81+
if ( tr.fraction < 1.0f )
82+
{
83+
m_attackCoverArea = area;
84+
return false; // found smoke as concealment
85+
}
86+
}
87+
}
88+
89+
return true; // not cover
90+
}
91+
92+
// found hard cover
93+
m_attackCoverArea = area;
94+
return false; // found suitable cover
95+
}
96+
97+
virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
98+
{
99+
if ( travelDistanceSoFar > 1000.0f )
100+
{
101+
return false;
102+
}
103+
104+
// For considering areas off to the side of current area
105+
constexpr float distanceThresholdRatio = 0.9f;
106+
107+
// The adjacent area to search should not be farther from the threat
108+
float adjThreatDistance = ( m_threatArea->GetCenter() - adjArea->GetCenter() ).LengthSqr();
109+
float curThreatDistance = ( m_threatArea->GetCenter() - currentArea->GetCenter() ).LengthSqr();
110+
if ( adjThreatDistance * distanceThresholdRatio > curThreatDistance )
111+
{
112+
return false; // Candidate adjacent area veers farther from threat
113+
}
114+
115+
// The adjacent area to search should not be beyond the threat
116+
if ( adjThreatDistance > m_distToThreatSq )
117+
{
118+
return false; // Candidate adjacent area is beyond threat distance
119+
}
120+
121+
// Don't consider areas that require jumping when engaging enemy
122+
return ( currentArea->ComputeAdjacentConnectionHeightChange( adjArea ) < m_me->GetLocomotionInterface()->GetStepHeight() );
123+
}
124+
125+
CNEOBot *m_me;
126+
const CKnownEntity *m_threat;
127+
CNavArea *m_attackCoverArea;
128+
const CNavArea *m_threatArea; // reference point of the threat
129+
float m_distToThreatSq; // the bot's current distance to the threat
130+
};
131+
132+
32133
//---------------------------------------------------------------------------------------------
33134
// head aiming and weapon firing is handled elsewhere - we just need to get into position to fight
34135
ActionResult< CNEOBot > CNEOBotAttack::Update( CNEOBot *me, float interval )
@@ -91,6 +192,55 @@ ActionResult< CNEOBot > CNEOBotAttack::Update( CNEOBot *me, float interval )
91192
}
92193
}
93194

195+
// Look for opportunities to leap frog from cover to cover
196+
if ( m_attackCoverTimer.IsElapsed() )
197+
{
198+
m_attackCoverTimer.Start( 5.0f );
199+
200+
CSearchForAttackCover search( me, threat );
201+
SearchSurroundingAreas( me->GetLastKnownArea(), search );
202+
203+
if ( search.m_attackCoverArea )
204+
{
205+
m_attackCoverArea = search.m_attackCoverArea;
206+
m_path.Invalidate(); // recompute path
207+
m_chasePath.Invalidate();
208+
}
209+
}
210+
211+
if ( m_attackCoverArea )
212+
{
213+
if ( me->GetLastKnownArea() == m_attackCoverArea )
214+
{
215+
// Immediately look for new cover
216+
m_attackCoverArea = nullptr;
217+
m_attackCoverTimer.Invalidate();
218+
}
219+
else
220+
{
221+
if ( !m_path.IsValid() )
222+
{
223+
if ( !CNEOBotPathCompute( me, m_path, m_attackCoverArea->GetCenter(), DEFAULT_ROUTE ) )
224+
{
225+
// If no valid path, fallback to chasing threat
226+
m_attackCoverArea = nullptr;
227+
m_path.Invalidate();
228+
}
229+
}
230+
231+
if ( m_attackCoverArea )
232+
{
233+
m_path.Update( me );
234+
return Continue();
235+
}
236+
}
237+
}
238+
else
239+
{
240+
m_path.Invalidate();
241+
}
242+
243+
// Fallback when there is no advancing cover
94244
if ( isUsingCloseRangeWeapon )
95245
{
96246
CNEOBotPathUpdateChase( me, m_chasePath, threat->GetEntity(), FASTEST_ROUTE );

src/game/server/neo/bot/behavior/neo_bot_attack.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class CNEOBotAttack : public Action< CNEOBot >
2525
private:
2626
PathFollower m_path;
2727
ChasePath m_chasePath;
28+
CountdownTimer m_attackCoverTimer;
2829
CountdownTimer m_grenadeThrowCooldownTimer;
2930
CountdownTimer m_repathTimer;
31+
CNavArea *m_attackCoverArea;
3032
};

src/game/server/neo/bot/behavior/neo_bot_ctg_escort.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include "neo_ghost_cap_point.h"
99
#include "weapons/weapon_ghost.h"
1010

11+
extern ConVar sv_neo_grenade_blast_radius;
12+
1113
//---------------------------------------------------------------------------------------------
1214
CNEOBotCtgEscort::CNEOBotCtgEscort( void ) :
1315
m_role( ROLE_SCREEN ),
@@ -86,6 +88,12 @@ ActionResult< CNEOBot > CNEOBotCtgEscort::Update( CNEOBot *me, float interval )
8688
{
8789
m_lostSightOfCarrierTimer.Invalidate();
8890

91+
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(true);
92+
if ( threat && threat->GetEntity() && threat->GetEntity()->IsAlive() )
93+
{
94+
return SuspendFor( new CNEOBotAttack, "Breaking away from ghoster to engage threat" );
95+
}
96+
8997
if ( !m_bHasGoal )
9098
{
9199
// Asymmetric defense: No goal cap zone, so defend the carrier.
@@ -131,10 +139,13 @@ ActionResult< CNEOBot > CNEOBotCtgEscort::Update( CNEOBot *me, float interval )
131139
}
132140
else
133141
{
142+
const float flSafeRadius = sv_neo_grenade_blast_radius.GetFloat();
143+
const float flSafeRadiusSq = flSafeRadius * flSafeRadius;
144+
134145
if ( m_role == ROLE_BODYGUARD )
135146
{
136147
// Dont crowd the carrier
137-
if ( me->GetAbsOrigin().DistToSqr( pGhostCarrier->GetAbsOrigin() ) < ( 100.0f * 100.0f ) )
148+
if ( me->GetAbsOrigin().DistToSqr( pGhostCarrier->GetAbsOrigin() ) < flSafeRadiusSq )
138149
{
139150
m_path.Invalidate();
140151
m_chasePath.Invalidate();
@@ -160,8 +171,7 @@ ActionResult< CNEOBot > CNEOBotCtgEscort::Update( CNEOBot *me, float interval )
160171

161172
// No active threats to carrier
162173
float flDistToCarrierSq = me->GetAbsOrigin().DistToSqr( pGhostCarrier->GetAbsOrigin() );
163-
constexpr float regroupDistanceSq = 300.0f * 300.0f;
164-
if ( flDistToCarrierSq > regroupDistanceSq )
174+
if ( flDistToCarrierSq > flSafeRadiusSq )
165175
{
166176
// Regroup
167177
CNEOBotPathUpdateChase( me, m_chasePath, pGhostCarrier, SAFEST_ROUTE );

src/game/server/neo/bot/behavior/neo_bot_jgr_escort.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
#include "bot/neo_bot.h"
44
#include "bot/behavior/neo_bot_attack.h"
55
#include "bot/behavior/neo_bot_jgr_escort.h"
6-
#include "bot/behavior/neo_bot_retreat_to_cover.h"
76
#include "bot/neo_bot_path_compute.h"
87
#include "neo_gamerules.h"
98

9+
extern ConVar sv_neo_grenade_blast_radius;
10+
1011
//---------------------------------------------------------------------------------------------
1112
CNEOBotJgrEscort::CNEOBotJgrEscort( void )
1213
{
@@ -63,17 +64,18 @@ ActionResult< CNEOBot > CNEOBotJgrEscort::Update( CNEOBot *me, float interval )
6364

6465
// Check for own threats
6566
const CKnownEntity *myThreat = me->GetVisionInterface()->GetPrimaryKnownThreat();
66-
if ( myThreat )
67+
if ( myThreat && myThreat->GetEntity() && myThreat->GetEntity()->IsAlive() )
6768
{
68-
return SuspendFor( new CNEOBotRetreatToCover, "Retreating to let Juggernaut get the kill" );
69+
return SuspendFor( new CNEOBotAttack, "Breaking away from Juggernaut to engage threat" );
6970
}
7071

7172
// Just follow the Juggernaut
7273
// If too close, stop moving to avoid crowding
73-
constexpr float flMinFollowDistSq = 200.0f * 200.0f;
7474
float flDistSq = me->GetAbsOrigin().DistToSqr( pJuggernaut->GetAbsOrigin() );
75+
float flSafeRadius = sv_neo_grenade_blast_radius.GetFloat();
76+
float flSafeRadiusSq = flSafeRadius * flSafeRadius;
7577

76-
if ( flDistSq < flMinFollowDistSq )
78+
if ( flDistSq < flSafeRadiusSq )
7779
{
7880
// Too close, stop moving
7981
if ( m_chasePath.IsValid() )

0 commit comments

Comments
 (0)