Skip to content

Commit 66b30a0

Browse files
committed
More grenade safety checks
1 parent 90cf9c9 commit 66b30a0

8 files changed

Lines changed: 117 additions & 41 deletions

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,10 @@ ActionResult< CNEOBot > CNEOBotAttack::Update( CNEOBot *me, float interval )
8383
// Consider throwing a grenade
8484
if ( !m_grenadeThrowCooldownTimer.HasStarted() || m_grenadeThrowCooldownTimer.IsElapsed() )
8585
{
86-
m_grenadeThrowCooldownTimer.Start( sv_neo_bot_grenade_throw_cooldown.GetFloat() );
87-
8886
Action<CNEOBot> *pGrenadeBehavior = CNEOBotGrenadeDispatch::ChooseGrenadeThrowBehavior( me, threat );
8987
if ( pGrenadeBehavior )
9088
{
89+
m_grenadeThrowCooldownTimer.Start( sv_neo_bot_grenade_throw_cooldown.GetFloat() );
9190
return SuspendFor( pGrenadeBehavior, "Throwing grenade before chasing threat!" );
9291
}
9392
}

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

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "weapon_smokegrenade.h"
88
#include "nav_mesh.h"
99
#include "nav_pathfind.h"
10+
#include "bot/neo_bot_path_compute.h"
1011

1112
ConVar 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
//---------------------------------------------------------------------------------------------
9192
ActionResult< 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
//---------------------------------------------------------------------------------------------
286335
void 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

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class CNEOBotGrenadeThrow : public Action< CNEOBot >
2121
virtual void OnEnd( CNEOBot *me, Action< CNEOBot > *nextAction ) override;
2222
virtual ActionResult< CNEOBot > OnSuspend( CNEOBot *me, Action< CNEOBot > *interruptingAction ) override;
2323
virtual ActionResult< CNEOBot > OnResume( CNEOBot *me, Action< CNEOBot > *interruptingAction ) override;
24+
virtual QueryResultType ShouldRetreat( const INextBot *me ) const override;
2425

2526
protected:
2627
Vector m_vecTarget; // caches target to aim at during throw action in implementation classes
@@ -29,6 +30,9 @@ class CNEOBotGrenadeThrow : public Action< CNEOBot >
2930
CHandle< CBaseEntity > m_hThreatGrenadeTarget;
3031
CountdownTimer m_giveUpTimer;
3132
CountdownTimer m_scanTimer;
33+
CountdownTimer m_repathTimer;
34+
PathFollower m_PathFollower;
35+
3236
bool m_bPinPulled;
3337

3438
enum ThrowTargetResult

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

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ ActionResult< CNEOBot > CNEOBotGrenadeThrowFrag::OnStart( CNEOBot *me, Action< C
3131
return result;
3232
}
3333

34-
m_PathFollower.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
35-
if ( !CNEOBotPathCompute( me, m_PathFollower, m_vecThreatLastKnownPos, FASTEST_ROUTE ) )
36-
{
37-
return Done( "Path to threat last known position unavailable" );
38-
}
39-
4034
return Continue();
4135
}
4236

@@ -52,19 +46,19 @@ CNEOBotGrenadeThrow::ThrowTargetResult CNEOBotGrenadeThrowFrag::UpdateGrenadeTar
5246
}
5347

5448
// Check if there is a more immediate threat interrupting my grenade throw
49+
bool bImmediateThreatPresent = false;
5550
const CKnownEntity* pPrimaryThreat = me->GetVisionInterface()->GetPrimaryKnownThreat();
5651
// Not using LINE_OF_FIRE_FLAGS_SHOTGUN because we want to abort grenade throw if threat could shoot us from behind glass
5752
if (pPrimaryThreat && pPrimaryThreat->GetEntity() && me->IsLineOfFireClear(pPrimaryThreat->GetEntity(), CNEOBot::LINE_OF_FIRE_FLAGS_DEFAULT))
5853
{
5954
// consider panic throwing the grenade at the immediate threat
55+
bImmediateThreatPresent = true;
6056
if ( m_scanTimer.IsElapsed() || m_vecTarget == vec3_invalid )
6157
{
6258
// Update target to immediate threat
6359
m_hThreatGrenadeTarget = pPrimaryThreat->GetEntity();
6460
m_vecTarget = pPrimaryThreat->GetLastKnownPosition();
6561
m_vecThreatLastKnownPos = m_vecTarget;
66-
67-
CNEOBotPathCompute( me, m_PathFollower, m_vecTarget, FASTEST_ROUTE );
6862
m_scanTimer.Start( 0.2f );
6963
}
7064
}
@@ -91,7 +85,6 @@ CNEOBotGrenadeThrow::ThrowTargetResult CNEOBotGrenadeThrowFrag::UpdateGrenadeTar
9185
if ( m_vecTarget == vec3_invalid )
9286
{
9387
// continue investigating last known position
94-
m_PathFollower.Update(me);
9588
return THROW_TARGET_WAIT;
9689
}
9790
}
@@ -100,7 +93,11 @@ CNEOBotGrenadeThrow::ThrowTargetResult CNEOBotGrenadeThrowFrag::UpdateGrenadeTar
10093
// Safety checks
10194
if ( !me->IsLineOfFireClear( m_vecTarget, CNEOBot::LINE_OF_FIRE_FLAGS_SHOTGUN ) )
10295
{
103-
return THROW_TARGET_CANCEL; // risk of grenade bouncing back at me
96+
if (bImmediateThreatPresent)
97+
{
98+
return THROW_TARGET_CANCEL;
99+
}
100+
return THROW_TARGET_WAIT; // attempt to reroute if blocked
104101
}
105102

106103
if ( !IsFragSafe( me, m_vecTarget ) )
@@ -148,14 +145,41 @@ bool CNEOBotGrenadeThrowFrag::IsFragSafe( const CNEOBot *me, const Vector &vecTa
148145

149146
if ( pPlayer && pPlayer->IsAlive() && pPlayer->InSameTeam( me->GetEntity() ) )
150147
{
148+
// Check if teammate is within a cone in front of the throw trajectory
149+
Vector vecToTarget = vecTarget - me->GetBodyInterface()->GetEyePosition();
150+
vecToTarget.NormalizeInPlace();
151+
152+
Vector vecToPlayer = pPlayer->WorldSpaceCenter() - me->GetBodyInterface()->GetEyePosition();
153+
vecToPlayer.NormalizeInPlace();
154+
155+
if ( DotProduct( vecToTarget, vecToPlayer ) > 0.8f )
156+
{
157+
trace_t tr;
158+
UTIL_TraceLine( me->GetBodyInterface()->GetEyePosition(), pPlayer->WorldSpaceCenter(), MASK_SHOT_HULL, me->GetEntity(), COLLISION_GROUP_NONE, &tr );
159+
160+
if ( tr.m_pEnt && tr.m_pEnt->IsPlayer() )
161+
{
162+
CNEO_Player *pHitPlayer = ToNEOPlayer( tr.m_pEnt );
163+
if ( pHitPlayer && pHitPlayer->InSameTeam( me->GetEntity() ) )
164+
{
165+
return false; // teammate in path of proposed grenade trajectory
166+
}
167+
}
168+
}
169+
170+
// Check if teammate would be near blast radius
151171
if ( pPlayer->GetAbsOrigin().DistToSqr( vecTarget ) < flSafeRadiusSqr )
152172
{
153173
trace_t tr;
154174
UTIL_TraceLine( vecTarget, pPlayer->WorldSpaceCenter(), MASK_SHOT_HULL, nullptr, COLLISION_GROUP_NONE, &tr );
155175

156-
if ( tr.fraction == 1.0f || tr.m_pEnt == pPlayer )
176+
if ( tr.m_pEnt && tr.m_pEnt->IsPlayer() )
157177
{
158-
return false; // potentially in blast radius
178+
CNEO_Player *pHitPlayer = ToNEOPlayer( tr.m_pEnt );
179+
if ( pHitPlayer && pHitPlayer->InSameTeam( me->GetEntity() ) )
180+
{
181+
return false; // teammate potentially in blast radius
182+
}
159183
}
160184
}
161185
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ class CNEOBotGrenadeThrowFrag : public CNEOBotGrenadeThrow
2121

2222
protected:
2323
virtual ThrowTargetResult UpdateGrenadeTargeting( CNEOBot *me, CNEOBaseCombatWeapon *pWeapon ) override;
24-
25-
PathFollower m_PathFollower;
2624
};
2725

2826
#endif // NEO_BOT_GRENADE_THROW_FRAG_H

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,6 @@ ActionResult< CNEOBot > CNEOBotGrenadeThrowSmoke::OnStart( CNEOBot *me, Action<
2020
return result;
2121
}
2222

23-
m_PathFollower.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
24-
if ( !CNEOBotPathCompute( me, m_PathFollower, m_vecThreatLastKnownPos, FASTEST_ROUTE ) )
25-
{
26-
return Done( "Path to threat last known position unavailable" );
27-
}
28-
2923
return Continue();
3024
}
3125

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ class CNEOBotGrenadeThrowSmoke : public CNEOBotGrenadeThrow
1818

1919
protected:
2020
virtual ThrowTargetResult UpdateGrenadeTargeting( CNEOBot *me, CNEOBaseCombatWeapon *pWeapon ) override;
21-
22-
PathFollower m_PathFollower;
2321
};
2422

2523
#endif // NEO_BOT_GRENADE_THROW_SMOKE_H

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,13 @@ ActionResult< CNEOBot > CNEOBotRetreatToCover::Update( CNEOBot *me, float interv
236236
#endif
237237

238238
// Consider throwing a grenade
239-
if ( !m_grenadeThrowCooldownTimer.HasStarted() || m_grenadeThrowCooldownTimer.IsElapsed() )
239+
if ( ( !m_grenadeThrowCooldownTimer.HasStarted() || m_grenadeThrowCooldownTimer.IsElapsed() ) &&
240+
threat && threat->GetEntity() && !me->IsLineOfFireClear( threat->GetEntity()->EyePosition(), CNEOBot::LINE_OF_FIRE_FLAGS_DEFAULT ) )
240241
{
241-
m_grenadeThrowCooldownTimer.Start( sv_neo_bot_grenade_throw_cooldown.GetFloat() );
242242
Action<CNEOBot> *pGrenadeBehavior = CNEOBotGrenadeDispatch::ChooseGrenadeThrowBehavior( me, threat );
243243
if ( pGrenadeBehavior )
244244
{
245+
m_grenadeThrowCooldownTimer.Start( sv_neo_bot_grenade_throw_cooldown.GetFloat() );
245246
return SuspendFor( pGrenadeBehavior, "Throwing grenade while taking cover!" );
246247
}
247248
}

0 commit comments

Comments
 (0)