From b3a0188d1c5e592e1c3341a0a46726a91846b234 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Sat, 7 Mar 2026 09:34:27 -0700 Subject: [PATCH 1/3] Bots avoid detpack at capture zone --- .../neo/bot/behavior/neo_bot_ctg_capture.cpp | 26 +++++++ .../behavior/neo_bot_ctg_lone_wolf_seek.cpp | 22 +++++- src/game/server/neo/bot/neo_bot.cpp | 77 +++++++++++++++++++ src/game/server/neo/bot/neo_bot.h | 1 + 4 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp b/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp index d6ee9d542a..b0647673bc 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp @@ -3,6 +3,7 @@ #include "bot/behavior/neo_bot_ctg_lone_wolf_seek.h" #include "bot/behavior/neo_bot_seek_weapon.h" #include "bot/neo_bot_path_compute.h" +#include "neo_detpack.h" #include "weapon_ghost.h" @@ -68,6 +69,31 @@ ActionResult CNEOBotCtgCapture::Update( CNEOBot *me, float interval ) m_captureAttemptTimer.Start( 3.0f ); } + // Check if there is a detpack that risks exploding if I pick up the ghost + // NEO Jank: It may be more proper to check for line of sight, + // but triggering an entity search at the last moment may involve fewer overall calculations + // with the lampshade explanation as the bot being able to notice the detpack along the path + // even if we didn't check constantly along the same path + CBaseEntity *pEnts[256]; + int numEnts = UTIL_EntitiesInSphere( pEnts, 256, me->GetAbsOrigin(), NEO_DETPACK_DAMAGE_RADIUS, 0 ); + bool bDetpackNear = false; + for ( int i = 0; i < numEnts; ++i ) + { + if ( pEnts[i] && FClassnameIs( pEnts[i], "neo_deployed_detpack" ) ) + { + bDetpackNear = true; + break; + } + } + + if ( bDetpackNear ) + { + // NEO JANK: Putting the bot into seek mode will have it search the map for enemies for the rest of the round + // but for now this could be fine as it may indicate an entrenched enemy + // or a friendly that is setting up an ambush, where either scenario indicates ghost capture is too dangerous + return ChangeTo( new CNEOBotCtgLoneWolfSeek(), "Found detpack: skipping ghost capture to search for enemies" ); + } + CBaseCombatWeapon *pPrimary = me->Weapon_GetSlot( 0 ); if ( pPrimary ) { diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf_seek.cpp b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf_seek.cpp index b5f40b25e9..1e32136672 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf_seek.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf_seek.cpp @@ -158,7 +158,27 @@ ActionResult< CNEOBot > CNEOBotCtgLoneWolfSeek::Update( CNEOBot *me, float inter } } - const Vector& currentGhostPos = NEORules()->GetGhostPos(); + Vector vecSoundPos = me->GetAudibleEnemySoundPos(); + if ( vecSoundPos != CNEO_Player::VECTOR_INVALID_WAYPOINT ) + { + // Don't veer path for sound if waypoint is not that far off + if ( m_vecSearchWaypoint.DistToSqr( vecSoundPos ) > Square( 200.0f ) ) + { + m_vecSearchWaypoint = vecSoundPos; + m_path.Invalidate(); + m_repathTimer.Invalidate(); // path to sound next tick + + CNavArea *soundArea = TheNavMesh->GetNearestNavArea( vecSoundPos ); + if ( soundArea ) + { + m_iExplorationTargetId = (int)soundArea->GetID(); + // Mark sound area as not explored + m_exploredAreaIds.Remove( m_iExplorationTargetId ); + } + } + } + + const Vector currentGhostPos = NEORules()->GetGhostPos(); if ( !m_pCachedGhostArea || currentGhostPos.DistToSqr( m_vecLastGhostPos ) > Square( 64.0f ) ) { CNavArea *pLastGhostArea = m_pCachedGhostArea; diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index 1931b34153..dbd883e128 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -21,6 +21,8 @@ #include "neo_weapon_loadout.h" #include "behavior/neo_bot_behavior.h" #include "neo_crosshair.h" +#include "recipientfilter.h" +#include "soundent.h" ConVar neo_bot_notice_gunfire_range("neo_bot_notice_gunfire_range", "3000", FCVAR_GAMEDLL); ConVar neo_bot_notice_quiet_gunfire_range("neo_bot_notice_quiet_gunfire_range", "500", FCVAR_GAMEDLL); @@ -2939,5 +2941,80 @@ QueryResultType CNEOBotBehavior::ShouldAim(const CNEOBot *me, const bool bWepHas return result; } +//--------------------------------------------------------------------------------------------- +Vector CNEOBot::GetAudibleEnemySoundPos(const Vector& vecReferencePos, float flMaxRangeSq) const +{ + CSound *pSound = nullptr; + for ( int iSound = CSoundEnt::ActiveList(); iSound != SOUNDLIST_EMPTY; iSound = pSound->NextSound() ) + { + pSound = CSoundEnt::SoundPointerForIndex( iSound ); + if ( !pSound ) + { + break; + } + + // If a reference position and range are provided, check distance first + if ( vecReferencePos != CNEO_Player::VECTOR_INVALID_WAYPOINT && flMaxRangeSq > 0.0f ) + { + if ( pSound->GetSoundOrigin().DistToSqr( vecReferencePos ) > flMaxRangeSq ) + { + continue; + } + } + + if ( ( pSound->SoundType() & ( SOUND_COMBAT | SOUND_PLAYER ) ) == 0 ) + { + continue; + } + + CBaseEntity *pOwner = pSound->m_hOwner.Get(); + + // Ignore non-player sounds and sounds I was responsible for + if ( !pOwner || !pOwner->IsPlayer() || pOwner == GetEntity() ) + { + continue; + } + + // Only care about sounds from the enemy + if ( InSameTeam( pOwner ) ) + { + continue; + } + + // Check if I can hear the sound + bool bCanHearEnemy = false; + CPASFilter soundFilter( pSound->GetSoundOrigin() ); + for ( int i = 0; i < soundFilter.GetRecipientCount(); ++i ) + { + if ( soundFilter.GetRecipientIndex( i ) == entindex() ) + { + bCanHearEnemy = true; + break; + } + } + + if ( !bCanHearEnemy ) + { + // Check if I can hear the shooter + CPASFilter shooterFilter( pOwner->GetAbsOrigin() ); + for ( int i = 0; i < shooterFilter.GetRecipientCount(); ++i ) + { + if ( shooterFilter.GetRecipientIndex( i ) == entindex() ) + { + bCanHearEnemy = true; + break; + } + } + } + + if ( bCanHearEnemy ) + { + return pSound->GetSoundOrigin(); + } + } + + return CNEO_Player::VECTOR_INVALID_WAYPOINT; +} + diff --git a/src/game/server/neo/bot/neo_bot.h b/src/game/server/neo/bot/neo_bot.h index eef55c4c83..b88feac775 100644 --- a/src/game/server/neo/bot/neo_bot.h +++ b/src/game/server/neo/bot/neo_bot.h @@ -172,6 +172,7 @@ class CNEOBot : public NextBotPlayer< CNEO_Player >, public CGameEventListener bool IsQuietWeapon(CNEOBaseCombatWeapon* weapon) const; // return true if given weapon doesn't make much sound when used (ie: spy knife, etc) bool IsEnvironmentNoisy(void) const; // return true if there are/have been loud noises (ie: non-quiet weapons) nearby very recently + Vector GetAudibleEnemySoundPos(const Vector& vecReferencePos = CNEO_Player::VECTOR_INVALID_WAYPOINT, float flMaxRangeSq = -1.0f) const; bool IsEnemy(const CBaseEntity* them) const OVERRIDE; From a65992eee70faa4ab6c1b1adc16b1ce752be8545 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Wed, 15 Jul 2026 23:39:14 -0600 Subject: [PATCH 2/3] Revert "Bots avoid detpack at capture zone" This reverts commit b3a0188d1c5e592e1c3341a0a46726a91846b234. --- .../neo/bot/behavior/neo_bot_ctg_capture.cpp | 26 ------- .../behavior/neo_bot_ctg_lone_wolf_seek.cpp | 22 +----- src/game/server/neo/bot/neo_bot.cpp | 77 ------------------- src/game/server/neo/bot/neo_bot.h | 1 - 4 files changed, 1 insertion(+), 125 deletions(-) diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp b/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp index b0647673bc..d6ee9d542a 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp @@ -3,7 +3,6 @@ #include "bot/behavior/neo_bot_ctg_lone_wolf_seek.h" #include "bot/behavior/neo_bot_seek_weapon.h" #include "bot/neo_bot_path_compute.h" -#include "neo_detpack.h" #include "weapon_ghost.h" @@ -69,31 +68,6 @@ ActionResult CNEOBotCtgCapture::Update( CNEOBot *me, float interval ) m_captureAttemptTimer.Start( 3.0f ); } - // Check if there is a detpack that risks exploding if I pick up the ghost - // NEO Jank: It may be more proper to check for line of sight, - // but triggering an entity search at the last moment may involve fewer overall calculations - // with the lampshade explanation as the bot being able to notice the detpack along the path - // even if we didn't check constantly along the same path - CBaseEntity *pEnts[256]; - int numEnts = UTIL_EntitiesInSphere( pEnts, 256, me->GetAbsOrigin(), NEO_DETPACK_DAMAGE_RADIUS, 0 ); - bool bDetpackNear = false; - for ( int i = 0; i < numEnts; ++i ) - { - if ( pEnts[i] && FClassnameIs( pEnts[i], "neo_deployed_detpack" ) ) - { - bDetpackNear = true; - break; - } - } - - if ( bDetpackNear ) - { - // NEO JANK: Putting the bot into seek mode will have it search the map for enemies for the rest of the round - // but for now this could be fine as it may indicate an entrenched enemy - // or a friendly that is setting up an ambush, where either scenario indicates ghost capture is too dangerous - return ChangeTo( new CNEOBotCtgLoneWolfSeek(), "Found detpack: skipping ghost capture to search for enemies" ); - } - CBaseCombatWeapon *pPrimary = me->Weapon_GetSlot( 0 ); if ( pPrimary ) { diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf_seek.cpp b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf_seek.cpp index 1e32136672..b5f40b25e9 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf_seek.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf_seek.cpp @@ -158,27 +158,7 @@ ActionResult< CNEOBot > CNEOBotCtgLoneWolfSeek::Update( CNEOBot *me, float inter } } - Vector vecSoundPos = me->GetAudibleEnemySoundPos(); - if ( vecSoundPos != CNEO_Player::VECTOR_INVALID_WAYPOINT ) - { - // Don't veer path for sound if waypoint is not that far off - if ( m_vecSearchWaypoint.DistToSqr( vecSoundPos ) > Square( 200.0f ) ) - { - m_vecSearchWaypoint = vecSoundPos; - m_path.Invalidate(); - m_repathTimer.Invalidate(); // path to sound next tick - - CNavArea *soundArea = TheNavMesh->GetNearestNavArea( vecSoundPos ); - if ( soundArea ) - { - m_iExplorationTargetId = (int)soundArea->GetID(); - // Mark sound area as not explored - m_exploredAreaIds.Remove( m_iExplorationTargetId ); - } - } - } - - const Vector currentGhostPos = NEORules()->GetGhostPos(); + const Vector& currentGhostPos = NEORules()->GetGhostPos(); if ( !m_pCachedGhostArea || currentGhostPos.DistToSqr( m_vecLastGhostPos ) > Square( 64.0f ) ) { CNavArea *pLastGhostArea = m_pCachedGhostArea; diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index dbd883e128..1931b34153 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -21,8 +21,6 @@ #include "neo_weapon_loadout.h" #include "behavior/neo_bot_behavior.h" #include "neo_crosshair.h" -#include "recipientfilter.h" -#include "soundent.h" ConVar neo_bot_notice_gunfire_range("neo_bot_notice_gunfire_range", "3000", FCVAR_GAMEDLL); ConVar neo_bot_notice_quiet_gunfire_range("neo_bot_notice_quiet_gunfire_range", "500", FCVAR_GAMEDLL); @@ -2941,80 +2939,5 @@ QueryResultType CNEOBotBehavior::ShouldAim(const CNEOBot *me, const bool bWepHas return result; } -//--------------------------------------------------------------------------------------------- -Vector CNEOBot::GetAudibleEnemySoundPos(const Vector& vecReferencePos, float flMaxRangeSq) const -{ - CSound *pSound = nullptr; - for ( int iSound = CSoundEnt::ActiveList(); iSound != SOUNDLIST_EMPTY; iSound = pSound->NextSound() ) - { - pSound = CSoundEnt::SoundPointerForIndex( iSound ); - if ( !pSound ) - { - break; - } - - // If a reference position and range are provided, check distance first - if ( vecReferencePos != CNEO_Player::VECTOR_INVALID_WAYPOINT && flMaxRangeSq > 0.0f ) - { - if ( pSound->GetSoundOrigin().DistToSqr( vecReferencePos ) > flMaxRangeSq ) - { - continue; - } - } - - if ( ( pSound->SoundType() & ( SOUND_COMBAT | SOUND_PLAYER ) ) == 0 ) - { - continue; - } - - CBaseEntity *pOwner = pSound->m_hOwner.Get(); - - // Ignore non-player sounds and sounds I was responsible for - if ( !pOwner || !pOwner->IsPlayer() || pOwner == GetEntity() ) - { - continue; - } - - // Only care about sounds from the enemy - if ( InSameTeam( pOwner ) ) - { - continue; - } - - // Check if I can hear the sound - bool bCanHearEnemy = false; - CPASFilter soundFilter( pSound->GetSoundOrigin() ); - for ( int i = 0; i < soundFilter.GetRecipientCount(); ++i ) - { - if ( soundFilter.GetRecipientIndex( i ) == entindex() ) - { - bCanHearEnemy = true; - break; - } - } - - if ( !bCanHearEnemy ) - { - // Check if I can hear the shooter - CPASFilter shooterFilter( pOwner->GetAbsOrigin() ); - for ( int i = 0; i < shooterFilter.GetRecipientCount(); ++i ) - { - if ( shooterFilter.GetRecipientIndex( i ) == entindex() ) - { - bCanHearEnemy = true; - break; - } - } - } - - if ( bCanHearEnemy ) - { - return pSound->GetSoundOrigin(); - } - } - - return CNEO_Player::VECTOR_INVALID_WAYPOINT; -} - diff --git a/src/game/server/neo/bot/neo_bot.h b/src/game/server/neo/bot/neo_bot.h index b88feac775..eef55c4c83 100644 --- a/src/game/server/neo/bot/neo_bot.h +++ b/src/game/server/neo/bot/neo_bot.h @@ -172,7 +172,6 @@ class CNEOBot : public NextBotPlayer< CNEO_Player >, public CGameEventListener bool IsQuietWeapon(CNEOBaseCombatWeapon* weapon) const; // return true if given weapon doesn't make much sound when used (ie: spy knife, etc) bool IsEnvironmentNoisy(void) const; // return true if there are/have been loud noises (ie: non-quiet weapons) nearby very recently - Vector GetAudibleEnemySoundPos(const Vector& vecReferencePos = CNEO_Player::VECTOR_INVALID_WAYPOINT, float flMaxRangeSq = -1.0f) const; bool IsEnemy(const CBaseEntity* them) const OVERRIDE; From 7210d206e8b9569642fc710567c462901cb0b855 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Wed, 15 Jul 2026 23:41:06 -0600 Subject: [PATCH 3/3] Bots avoid picking up ghost if they notice a detpack --- .../neo/bot/behavior/neo_bot_ctg_capture.cpp | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp b/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp index d6ee9d542a..b0647673bc 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_capture.cpp @@ -3,6 +3,7 @@ #include "bot/behavior/neo_bot_ctg_lone_wolf_seek.h" #include "bot/behavior/neo_bot_seek_weapon.h" #include "bot/neo_bot_path_compute.h" +#include "neo_detpack.h" #include "weapon_ghost.h" @@ -68,6 +69,31 @@ ActionResult CNEOBotCtgCapture::Update( CNEOBot *me, float interval ) m_captureAttemptTimer.Start( 3.0f ); } + // Check if there is a detpack that risks exploding if I pick up the ghost + // NEO Jank: It may be more proper to check for line of sight, + // but triggering an entity search at the last moment may involve fewer overall calculations + // with the lampshade explanation as the bot being able to notice the detpack along the path + // even if we didn't check constantly along the same path + CBaseEntity *pEnts[256]; + int numEnts = UTIL_EntitiesInSphere( pEnts, 256, me->GetAbsOrigin(), NEO_DETPACK_DAMAGE_RADIUS, 0 ); + bool bDetpackNear = false; + for ( int i = 0; i < numEnts; ++i ) + { + if ( pEnts[i] && FClassnameIs( pEnts[i], "neo_deployed_detpack" ) ) + { + bDetpackNear = true; + break; + } + } + + if ( bDetpackNear ) + { + // NEO JANK: Putting the bot into seek mode will have it search the map for enemies for the rest of the round + // but for now this could be fine as it may indicate an entrenched enemy + // or a friendly that is setting up an ambush, where either scenario indicates ghost capture is too dangerous + return ChangeTo( new CNEOBotCtgLoneWolfSeek(), "Found detpack: skipping ghost capture to search for enemies" ); + } + CBaseCombatWeapon *pPrimary = me->Weapon_GetSlot( 0 ); if ( pPrimary ) {