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// ---------------------------------------------------------------------------------------------
1819CNEOBotAttack::CNEOBotAttack ( void ) : m_chasePath( ChasePath::LEAD_SUBJECT )
1920{
21+ m_attackCoverArea = nullptr ;
2022}
2123
2224
2325// ---------------------------------------------------------------------------------------------
2426ActionResult< 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
34135ActionResult< 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 );
0 commit comments