1010#include " nav_pathfind.h"
1111#include " bot/neo_bot_path_compute.h"
1212
13+ extern ConVar sv_neo_bot_grenade_frag_safety_range_multiplier;
14+ extern ConVar sv_neo_grenade_blast_radius;
15+
1316ConVar sv_neo_bot_grenade_debug_behavior (" sv_neo_bot_grenade_debug_behavior" , " 0" , FCVAR_CHEAT,
1417 " Draw debug overlays for bot grenade behavior" , true , 0 , true , 1 );
1518
1619ConVar sv_neo_bot_grenade_give_up_time (" sv_neo_bot_grenade_give_up_time" , " 5.0" , FCVAR_NONE,
1720 " Time in seconds before bot gives up on grenade throw" , true , 1 , false , 0 );
1821
22+ ConVar sv_neo_bot_grenade_search_range (" sv_neo_bot_grenade_search_range" , " 1000" , FCVAR_CHEAT,
23+ " Travel distance range for bot to search for a grenade throw vantage point" , true , 0 , false , 0 );
24+
1925// ---------------------------------------------------------------------------------------------
2026CNEOBotGrenadeThrow::CNEOBotGrenadeThrow ( CNEOBaseCombatWeapon *pWeapon, const CKnownEntity *threat )
2127{
2228 m_hGrenadeWeapon = pWeapon;
29+ m_bVantagePointBlocked = false ;
30+ m_vantageArea = nullptr ;
2331 m_vecTarget = vec3_invalid;
2432
2533 if ( threat )
@@ -95,6 +103,100 @@ const Vector& CNEOBotGrenadeThrow::FindEmergencePointAlongPath( const CNEOBot *m
95103 return vec3_invalid;
96104}
97105
106+
107+ // ---------------------------------------------------------------------------------------------
108+ class CFindVantagePointTargetPos : public ISearchSurroundingAreasFunctor
109+ {
110+ public:
111+ CFindVantagePointTargetPos ( CNEOBot *me, const Vector &targetPos, CBaseEntity *pThreat )
112+ {
113+ m_me = me;
114+ m_targetPos = targetPos;
115+ m_vantageArea = nullptr ;
116+ m_threatArea = nullptr ;
117+ m_targetArea = TheNavMesh->GetNavArea ( m_targetPos );
118+
119+ if ( pThreat )
120+ {
121+ m_threatArea = TheNavMesh->GetNavArea ( pThreat->GetAbsOrigin () );
122+ }
123+
124+ if ( !m_threatArea )
125+ {
126+ const CKnownEntity *primaryThreat = me->GetVisionInterface ()->GetPrimaryKnownThreat ();
127+ if ( primaryThreat )
128+ {
129+ m_threatArea = primaryThreat->GetLastKnownArea ();
130+ }
131+ }
132+
133+ float flRadius = sv_neo_grenade_blast_radius.GetFloat () * sv_neo_bot_grenade_frag_safety_range_multiplier.GetFloat ();
134+ m_flSafetyRadiusSq = flRadius * flRadius;
135+ }
136+
137+ virtual bool operator () ( CNavArea* baseArea, CNavArea* priorArea, float travelDistanceSoFar )
138+ {
139+ CNavArea* area = (CNavArea*)baseArea;
140+
141+ if (!m_targetArea)
142+ {
143+ return false ; // can't search
144+ }
145+
146+ if ( area->IsPotentiallyVisible ( m_targetArea ) )
147+ {
148+ // nearby area from which we can see the last known position
149+ m_vantageArea = area;
150+ return false ; // stop searching
151+ }
152+
153+ return true ; // continue searching
154+ }
155+
156+ // return true if 'adjArea' should be included in the ongoing search
157+ virtual bool ShouldSearch ( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
158+ {
159+ if ( travelDistanceSoFar > sv_neo_bot_grenade_search_range.GetFloat () )
160+ {
161+ return false ;
162+ }
163+
164+ // For considering areas off to the side of current area
165+ constexpr float distanceThresholdRatio = 0 .8f ;
166+
167+ if ( m_threatArea )
168+ {
169+ // The adjacent area to search should not be farther from the threat
170+ float adjThreatDistance = ( m_threatArea->GetCenter () - adjArea->GetCenter () ).LengthSqr ();
171+ float curThreatDistance = ( m_threatArea->GetCenter () - currentArea->GetCenter () ).LengthSqr ();
172+ if ( adjThreatDistance * distanceThresholdRatio > curThreatDistance )
173+ {
174+ return false ; // Candidate adjacent area veers farther from threat
175+ }
176+
177+ // The adjacent area to search should not be closer than the safety range
178+ if ( adjThreatDistance < m_flSafetyRadiusSq )
179+ {
180+ // NEO JANK: While this range is based on frag grenades,
181+ // it's still likely a bad idea to throw smoke grenades this close to the threat
182+ // Though logic at time of comment just throws smoke safely from behind cover
183+ return false ; // Candidate adjacent area is too close to threat
184+ }
185+ }
186+
187+ // allow falling off ledges, but don't jump up - too slow
188+ return ( currentArea->ComputeAdjacentConnectionHeightChange ( adjArea ) < m_me->GetLocomotionInterface ()->GetStepHeight () );
189+ }
190+
191+ CNEOBot *m_me;
192+ Vector m_targetPos;
193+ CNavArea *m_targetArea;
194+ CNavArea *m_vantageArea;
195+ const CNavArea *m_threatArea;
196+ float m_flSafetyRadiusSq;
197+ };
198+
199+
98200// ---------------------------------------------------------------------------------------------
99201ActionResult< CNEOBot > CNEOBotGrenadeThrow::OnStart ( CNEOBot *me, Action< CNEOBot > *priorAction )
100202{
@@ -135,6 +237,7 @@ ActionResult< CNEOBot > CNEOBotGrenadeThrow::OnStart( CNEOBot *me, Action< CNEOB
135237 return Continue ();
136238}
137239
240+
138241// ---------------------------------------------------------------------------------------------
139242ActionResult< CNEOBot > CNEOBotGrenadeThrow::Update ( CNEOBot *me, float interval )
140243{
@@ -292,10 +395,53 @@ ActionResult< CNEOBot > CNEOBotGrenadeThrow::Update( CNEOBot *me, float interval
292395 {
293396 m_PathFollower.Update ( me );
294397
398+ // Watch for threats emerging from known position
399+ if ( m_vecThreatLastKnownPos != vec3_invalid )
400+ {
401+ me->GetBodyInterface ()->AimHeadTowards (
402+ m_vecThreatLastKnownPos, IBody::IMPORTANT, 0 .1f , nullptr ,
403+ " Looking towards last known threat position while moving to vantage point" );
404+ }
405+
406+ if ( m_vantageArea && me->GetLastKnownArea () == m_vantageArea )
407+ {
408+ // Arrived at vantage point but still blocked - fallback to chase
409+ m_bVantagePointBlocked = true ;
410+ m_vantageArea = nullptr ;
411+ }
412+
295413 if ( m_repathTimer.IsElapsed () )
296414 {
297415 m_repathTimer.Start ( RandomFloat ( 0 .3f , 0 .5f ) );
298- CNEOBotPathCompute ( me, m_PathFollower, m_vecTarget != vec3_invalid ? m_vecTarget : m_vecThreatLastKnownPos, FASTEST_ROUTE );
416+
417+ if ( !m_vantageArea && !m_bVantagePointBlocked )
418+ {
419+ CFindVantagePointTargetPos find ( me, m_vecThreatLastKnownPos, m_hThreatGrenadeTarget.Get () );
420+ SearchSurroundingAreas ( me->GetLastKnownArea (), find, sv_neo_bot_grenade_search_range.GetFloat () );
421+ m_vantageArea = find.m_vantageArea ;
422+
423+ if ( !m_vantageArea )
424+ {
425+ return Done ( " Failed to find a vantage area to throw grenade from" );
426+ }
427+ }
428+
429+ bool bGoingToVantagePoint = false ;
430+ if ( m_vantageArea )
431+ {
432+ bGoingToVantagePoint = CNEOBotPathCompute ( me, m_PathFollower, m_vantageArea->GetCenter (), FASTEST_ROUTE );
433+ }
434+
435+ if ( !bGoingToVantagePoint )
436+ {
437+ // Fallback to direct chase behavior if vantage point is blocked
438+ if ( m_vantageArea )
439+ {
440+ m_vantageArea = nullptr ;
441+ m_bVantagePointBlocked = true ;
442+ }
443+ CNEOBotPathCompute ( me, m_PathFollower, m_vecTarget != vec3_invalid ? m_vecTarget : m_vecThreatLastKnownPos, FASTEST_ROUTE );
444+ }
299445 }
300446 }
301447
@@ -309,6 +455,12 @@ ActionResult< CNEOBot > CNEOBotGrenadeThrow::Update( CNEOBot *me, float interval
309455 NDebugOverlay::HorzArrow ( vecStart, m_vecTarget, 2 .0f , 255 , 128 , 0 , 255 , true , 2 .0f );
310456 NDebugOverlay::Box ( m_vecTarget, Vector (-16 ,-16 ,-16 ), Vector (16 ,16 ,16 ), 255 , 128 , 0 , 30 , 2 .0f );
311457 }
458+
459+ if ( m_vantageArea )
460+ {
461+ NDebugOverlay::Box ( m_vantageArea->GetCenter (), Vector (-16 ,-16 ,0 ), Vector (16 ,16 ,16 ), 0 , 255 , 0 , 30 , 0 .1f );
462+ NDebugOverlay::Line ( me->EyePosition (), m_vantageArea->GetCenter (), 0 , 255 , 0 , true , 0 .1f );
463+ }
312464 }
313465
314466 // NEO JANK: Force the throwing phase if aimed.
0 commit comments