77#include " weapon_smokegrenade.h"
88#include " nav_mesh.h"
99#include " nav_pathfind.h"
10+ #include " bot/neo_bot_path_compute.h"
1011
1112ConVar sv_neo_bot_grenade_debug_behavior (" sv_neo_bot_grenade_debug_behavior" , " 0" , FCVAR_CHEAT,
1213 " Draw debug overlays for bot grenade behavior" , true , 0 , true , 1 );
@@ -90,6 +91,17 @@ const Vector& CNEOBotGrenadeThrow::FindEmergencePointAlongPath( const CNEOBot *m
9091// ---------------------------------------------------------------------------------------------
9192ActionResult< CNEOBot > CNEOBotGrenadeThrow::OnStart ( CNEOBot *me, Action< CNEOBot > *priorAction )
9293{
94+ CNEOBaseCombatWeapon *pWep = m_hGrenadeWeapon.Get ();
95+ if ( pWep )
96+ {
97+ Assert ( ( pWep->GetNeoWepBits () & NEO_WEP_FRAG_GRENADE ) || ( pWep->GetNeoWepBits () & NEO_WEP_SMOKE_GRENADE ) );
98+ me->PushRequiredWeapon ( m_hGrenadeWeapon );
99+ }
100+ else
101+ {
102+ return Done ( " Grenade input invalid" );
103+ }
104+
93105 if ( !m_hThreatGrenadeTarget.Get () )
94106 {
95107 return Done ( " Targeted Threat is null" );
@@ -99,16 +111,19 @@ ActionResult< CNEOBot > CNEOBotGrenadeThrow::OnStart( CNEOBot *me, Action< CNEOB
99111 {
100112 return Done ( " Targeted threat is dead" );
101113 }
102-
103- if ( m_hGrenadeWeapon.Get () )
104- {
105- me->PushRequiredWeapon ( m_hGrenadeWeapon );
106- }
107114
108115 m_giveUpTimer.Start ( sv_neo_bot_grenade_give_up_time.GetFloat () );
109116 m_bPinPulled = false ;
110117
111- me->StopLookingAroundForEnemies ();
118+ m_PathFollower.SetMinLookAheadDistance ( me->GetDesiredPathLookAheadRange () );
119+ if ( m_vecThreatLastKnownPos != vec3_invalid )
120+ {
121+ CNEOBotPathCompute ( me, m_PathFollower, m_vecThreatLastKnownPos, FASTEST_ROUTE );
122+ }
123+
124+ // Stop distracting looking or weapon handling behavior that could interrupt the throw
125+ me->StopLookingAroundForEnemies (); // reduce distractions for manual look aiming
126+ me->SetAttribute ( CNEOBot::IGNORE_ENEMIES ); // suppress reaction to swap back to firearm
112127
113128 return Continue ();
114129}
@@ -147,6 +162,12 @@ ActionResult< CNEOBot > CNEOBotGrenadeThrow::Update( CNEOBot *me, float interval
147162 return Done ( " Grenade throw timed out" );
148163 }
149164
165+ CNEO_Player* pNeoMe = ToNEOPlayer (me);
166+ if (pNeoMe && (gpGlobals->curtime - pNeoMe->GetLastDamageTime () < 0 .5f ))
167+ {
168+ return Done ( " Actively getting hurt, too dangerous to throw grenade" );
169+ }
170+
150171 if ( sv_neo_bot_grenade_debug_behavior.GetBool () && m_hThreatGrenadeTarget.Get () )
151172 {
152173 // DEBUG Colors: Red = Frag, Gray = Smoke, Purple = Unknown
@@ -176,17 +197,16 @@ ActionResult< CNEOBot > CNEOBotGrenadeThrow::Update( CNEOBot *me, float interval
176197 return Continue ();
177198 }
178199
179- // Wait for weapon switch to complete fully
180- if ( pWep->m_flNextPrimaryAttack > gpGlobals->curtime )
181- {
182- return Continue ();
183- }
184-
185200 // NEOJANK: The bots struggle to throw grenades with PressFireButton due to control quirks.
186201 // As a workaround, we decompose the action into different phases of the bot behavior.
187202 // This also allows us to run aiming logic in parallel with the pin-pull animation.
188203 if (!m_bPinPulled)
189204 {
205+ // Wait for weapon switch to complete fully
206+ if ( pWep->m_flNextPrimaryAttack > gpGlobals->curtime )
207+ {
208+ return Continue ();
209+ }
190210 // Just play the animation. Do NOT call PrimaryAttack, as that sets up the weapon to
191211 // auto-throw via ItemPostFrame, which we want to control manually here.
192212 pWep->SendWeaponAnim ( ACT_VM_PULLPIN );
@@ -238,7 +258,36 @@ ActionResult< CNEOBot > CNEOBotGrenadeThrow::Update( CNEOBot *me, float interval
238258
239259 if ( vecForward.Dot ( vecToTarget ) >= flAimThreshold )
240260 {
241- bAimOnTarget = true ;
261+ if ( me->IsLineOfFireClear ( m_vecTarget, CNEOBot::LINE_OF_FIRE_FLAGS_SHOTGUN ) )
262+ {
263+ bAimOnTarget = true ;
264+ }
265+ else
266+ {
267+ // Blocked by an obstruction
268+ if ( sv_neo_bot_grenade_debug_behavior.GetBool () )
269+ {
270+ NDebugOverlay::Line ( me->GetEntity ()->EyePosition (), m_vecTarget, 0 , 0 , 255 , true , 0 .1f );
271+ }
272+
273+ m_PathFollower.Update ( me );
274+
275+ if ( m_repathTimer.IsElapsed () )
276+ {
277+ m_repathTimer.Start ( RandomFloat ( 0 .3f , 0 .5f ) );
278+ CNEOBotPathCompute ( me, m_PathFollower, m_vecTarget, FASTEST_ROUTE );
279+ }
280+ }
281+ }
282+ }
283+ else if ( result == THROW_TARGET_WAIT )
284+ {
285+ m_PathFollower.Update ( me );
286+
287+ if ( m_repathTimer.IsElapsed () )
288+ {
289+ m_repathTimer.Start ( RandomFloat ( 0 .3f , 0 .5f ) );
290+ CNEOBotPathCompute ( me, m_PathFollower, m_vecTarget != vec3_invalid ? m_vecTarget : m_vecThreatLastKnownPos, FASTEST_ROUTE );
242291 }
243292 }
244293
@@ -285,14 +334,23 @@ ActionResult< CNEOBot > CNEOBotGrenadeThrow::Update( CNEOBot *me, float interval
285334// ---------------------------------------------------------------------------------------------
286335void CNEOBotGrenadeThrow::OnEnd ( CNEOBot *me, Action< CNEOBot > *nextAction )
287336{
337+ // Restore looking and weapon handling behaviors
288338 me->PopRequiredWeapon ();
339+ me->StartLookingAroundForEnemies ();
340+ me->ClearAttribute ( CNEOBot::IGNORE_ENEMIES );
289341 const CKnownEntity *threat = me->GetVisionInterface ()->GetPrimaryKnownThreat ();
290342 me->EquipBestWeaponForThreat ( threat );
291- me->StartLookingAroundForEnemies ();
292343}
293344
294345// ---------------------------------------------------------------------------------------------
295- ActionResult<CNEOBot> CNEOBotGrenadeThrow::OnSuspend ( CNEOBot *me, Action<CNEOBot> *interruptingAction )
346+ QueryResultType CNEOBotGrenadeThrow::ShouldRetreat ( const INextBot *me ) const
347+ {
348+ // Avoid tactical monitor interrupting the grenade throw behavior
349+ return ANSWER_NO;
350+ }
351+
352+ // ---------------------------------------------------------------------------------------------
353+ ActionResult<CNEOBot> CNEOBotGrenadeThrow::OnSuspend ( CNEOBot *me, Action<CNEOBot> *interruptingAction )
296354{
297355 return Done ( " OnSuspend: Cancel out of grenade throw behavior, situation will likely become stale." );
298356 // OnEnd will get called after Done
0 commit comments