99#include " bot/behavior/neo_bot_jgr_seek.h"
1010#include " bot/neo_bot_path_compute.h"
1111#include " nav_mesh.h"
12+ #include " soundent.h"
1213
1314extern ConVar neo_bot_path_lookahead_range;
1415extern ConVar neo_bot_offense_must_push_time;
@@ -18,6 +19,87 @@ ConVar neo_bot_debug_seek_and_destroy( "neo_bot_debug_seek_and_destroy", "0", FC
1819ConVar neo_bot_disable_seek_and_destroy ( " neo_bot_disable_seek_and_destroy" , " 0" , FCVAR_CHEAT );
1920
2021
22+ // ---------------------------------------------------------------------------------------------
23+ CSound* CNEOBotSeekAndDestroy::SearchGunfireSounds (CNEOBot* me, const Vector* currentGoalPos)
24+ {
25+ CSound* pClosestSound = nullptr ;
26+ float flClosestDistSqr = FLT_MAX;
27+ const Vector& vecMyOrigin = me->GetAbsOrigin ();
28+
29+ float flGoalDistSqr = FLT_MAX;
30+ if (currentGoalPos && *currentGoalPos != vec3_invalid)
31+ {
32+ flGoalDistSqr = vecMyOrigin.DistToSqr (*currentGoalPos);
33+ }
34+
35+ CSound* pSound = nullptr ;
36+ for (int iSound = CSoundEnt::ActiveList (); iSound != SOUNDLIST_EMPTY; iSound = pSound->NextSound ())
37+ {
38+ pSound = CSoundEnt::SoundPointerForIndex (iSound);
39+ if (!pSound)
40+ {
41+ break ;
42+ }
43+
44+ if (!(pSound->SoundType () & SOUND_COMBAT))
45+ {
46+ continue ;
47+ }
48+
49+ CBaseEntity *pOwner = pSound->m_hOwner .Get ();
50+
51+ // Ignore non-player sounds and sounds we were responsible for
52+ if (!pOwner || !pOwner->IsPlayer () || pOwner == me)
53+ {
54+ continue ;
55+ }
56+
57+ // NEO Jank: prevent bots from crowding teammates in teamplay
58+ if (NEORules ()->IsTeamplay () && me->InSameTeam (pOwner))
59+ {
60+ continue ;
61+ }
62+
63+ // Search for the closest gunfire sounds
64+ float distSqr = vecMyOrigin.DistToSqr (pSound->GetSoundOrigin ());
65+
66+ // Only consider sounds that are closer than the current goal
67+ if (distSqr >= flGoalDistSqr)
68+ {
69+ continue ;
70+ }
71+
72+ if (distSqr < flClosestDistSqr)
73+ {
74+ flClosestDistSqr = distSqr;
75+ pClosestSound = pSound;
76+ }
77+ }
78+
79+ return pClosestSound;
80+ }
81+
82+ // ---------------------------------------------------------------------------------------------
83+ const Vector& CNEOBotSeekAndDestroy::SearchGunfireLocation (CNEOBot* me, const Vector* currentGoalPos)
84+ {
85+ CSound* pBestSound = SearchGunfireSounds (me, currentGoalPos);
86+ if (pBestSound)
87+ {
88+ if (currentGoalPos && *currentGoalPos != vec3_invalid)
89+ {
90+ // Only change goal if recent gunfire is radically different than where I was going
91+ constexpr float flThresholdSqr = 200 .0f * 200 .0f ;
92+ if (currentGoalPos->DistToSqr (pBestSound->GetSoundOrigin ()) <= flThresholdSqr)
93+ {
94+ return vec3_invalid;
95+ }
96+ }
97+ return pBestSound->GetSoundOrigin ();
98+ }
99+
100+ return vec3_invalid;
101+ }
102+
21103// ---------------------------------------------------------------------------------------------
22104CNEOBotSeekAndDestroy::CNEOBotSeekAndDestroy ( float duration )
23105{
@@ -161,6 +243,27 @@ ActionResult< CNEOBot > CNEOBotSeekAndDestroy::UpdateCommon( CNEOBot *me, float
161243
162244 RecomputeSeekPath ( me );
163245 }
246+ else if ( m_bInvestigateGunfire && (!m_soundSearchTimer.HasStarted () || m_soundSearchTimer.IsElapsed ()) )
247+ {
248+ m_soundSearchTimer.Start ( 0 .25f );
249+
250+ const Vector& vGunfireLocation = SearchGunfireLocation (me, &m_vGoalPos);
251+ if (vGunfireLocation != vec3_invalid)
252+ {
253+ m_vGoalPos = vGunfireLocation;
254+ m_bGoingToTargetEntity = false ;
255+
256+ if (CNEOBotPathCompute (me, m_path, m_vGoalPos, DEFAULT_ROUTE))
257+ {
258+ m_repathTimer.Start ( 45 .0f );
259+ }
260+ else
261+ {
262+ // NEO Jank: Sound is unreachable so wait for it clear from the sound list
263+ m_soundSearchTimer.Start ( 3 .0f );
264+ }
265+ }
266+ }
164267
165268 return Continue ();
166269}
@@ -363,6 +466,28 @@ void CNEOBotSeekAndDestroy::RecomputeSeekPath( CNEOBot *me )
363466 }
364467 }
365468#endif
469+
470+ // Listen for gunfights
471+ if (m_bInvestigateGunfire)
472+ {
473+ const Vector& vGunfireLocation = SearchGunfireLocation (me);
474+ if (vGunfireLocation != vec3_invalid)
475+ {
476+ m_vGoalPos = vGunfireLocation;
477+ m_bGoingToTargetEntity = false ;
478+
479+ if (CNEOBotPathCompute (me, m_path, m_vGoalPos, DEFAULT_ROUTE) && m_path.IsValid () && m_path.GetResult () == Path::COMPLETE_PATH)
480+ {
481+ return ;
482+ }
483+ else
484+ {
485+ // NEO Jank: Sound is unreachable so wait for it clear from the sound list
486+ m_soundSearchTimer.Start ( 3 .0f );
487+ }
488+ }
489+ }
490+
366491 // Fallback and roam random spawn points if we have all weapons.
367492 {
368493 CNextSpawnFilter spawnFilter ( me, 128 .0f );
0 commit comments