From c7d420be2e71621220690097f49f7da40075d5ea Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Wed, 7 Oct 2026 19:06:00 -0600 Subject: [PATCH 1/3] Let a ghost carrier clear a breakable in its path A bot clears a breakable in its path only while it sees no threat, so it fights first. A ghost carrier does not fight a threat it sees, so it waited at the breakable for the rest of the round. On ntre_marketa_ctg carriers stood at the window onto the roof: 40 of 42 of their stuck events there had a threat in sight, through or past the glass. Skip that wait for a ghost carrier, both when the action starts and while it runs. It switches to its knife or secondary, breaks the glass and goes back to the ghost. Measured on a research build: 3 of 3 carriers that reached the window broke both panes, 0 stuck (11 stuck events in 2 of 8 runs without the change). A 96-match screen on marketa, transit, rise and dusk showed no new stuck spot involving a breakable or a carrier. --- .../behavior/neo_bot_path_clear_breakable.cpp | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp b/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp index 59c45f1271..fbb1994392 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp @@ -9,6 +9,20 @@ extern ConVar neo_bot_fire_weapon_allowed; ConVar neo_bot_fire_at_breakable_weapon_min_time( "neo_bot_fire_at_breakable_weapon_min_time", "0.2", FCVAR_CHEAT, "Minimum time to fire at breakables", true, 0.0f, true, 60.0f ); +//-------------------------------------------------------------------------------------------------------- +// A bot fights a threat in sight before it clears a breakable, +// but a ghost carrier does not fight, so waiting for the threat to leave keeps it stuck at the breakable +static bool IsHeldBackByThreat( CNEOBot *me ) +{ + if ( me->IsCarryingGhost() ) + { + return false; + } + + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + return threat && threat->GetEntity() && threat->IsVisibleRecently(); +} + //-------------------------------------------------------------------------------------------------------- CBaseEntity *CNEOBotPathClearBreakable::GetBreakableInPath( CNEOBot *me ) { @@ -28,8 +42,7 @@ CBaseEntity *CNEOBotPathClearBreakable::GetBreakableInPath( CNEOBot *me ) } // Only enter this behavior if there is no visible threat - const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); - if ( threat && threat->GetEntity() && threat->IsVisibleRecently() ) + if ( IsHeldBackByThreat( me ) ) { return nullptr; } @@ -127,8 +140,7 @@ ActionResult< CNEOBot > CNEOBotPathClearBreakable::Update( CNEOBot *me, float in } // If a visible threat appeared, bail out and let the normal combat system handle it - const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); - if ( threat && threat->GetEntity() && threat->IsVisibleRecently() ) + if ( IsHeldBackByThreat( me ) ) { return Done( "Threat appeared" ); } From 9e82d300943451e4c50ba8106263593b640f7283 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Wed, 7 Oct 2026 19:07:00 -0600 Subject: [PATCH 2/3] Check the ghost only when a threat is in sight IsCarryingGhost walks the bot's weapons, and the tactical monitor asks every tick, so test the cheap threat visibility first. --- .../neo/bot/behavior/neo_bot_path_clear_breakable.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp b/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp index fbb1994392..c6930fbe1b 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp @@ -14,13 +14,13 @@ ConVar neo_bot_fire_at_breakable_weapon_min_time( "neo_bot_fire_at_breakable_wea // but a ghost carrier does not fight, so waiting for the threat to leave keeps it stuck at the breakable static bool IsHeldBackByThreat( CNEOBot *me ) { - if ( me->IsCarryingGhost() ) + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( !threat || !threat->GetEntity() || !threat->IsVisibleRecently() ) { return false; } - const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); - return threat && threat->GetEntity() && threat->IsVisibleRecently(); + return !me->IsCarryingGhost(); } //-------------------------------------------------------------------------------------------------------- From f7302dcc775f88032cfaf452ffc3fcf7f5536d4f Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Wed, 14 Oct 2026 19:08:28 -0600 Subject: [PATCH 3/3] Say which bots a threat in sight holds back The two comments at the threat check still described the rule before the carrier exception, and the helper only reads the bot, so it takes a const pointer. --- .../neo/bot/behavior/neo_bot_path_clear_breakable.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp b/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp index c6930fbe1b..0c9d9e1824 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp @@ -11,8 +11,8 @@ ConVar neo_bot_fire_at_breakable_weapon_min_time( "neo_bot_fire_at_breakable_wea //-------------------------------------------------------------------------------------------------------- // A bot fights a threat in sight before it clears a breakable, -// but a ghost carrier does not fight, so waiting for the threat to leave keeps it stuck at the breakable -static bool IsHeldBackByThreat( CNEOBot *me ) +// but a ghost carrier does not attack, so waiting for the threat to leave keeps it stuck at the breakable +static bool IsHeldBackByThreat( const CNEOBot *me ) { const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); if ( !threat || !threat->GetEntity() || !threat->IsVisibleRecently() ) @@ -41,7 +41,7 @@ CBaseEntity *CNEOBotPathClearBreakable::GetBreakableInPath( CNEOBot *me ) return nullptr; } - // Only enter this behavior if there is no visible threat + // A threat in sight holds back any bot but a ghost carrier if ( IsHeldBackByThreat( me ) ) { return nullptr; @@ -139,7 +139,7 @@ ActionResult< CNEOBot > CNEOBotPathClearBreakable::Update( CNEOBot *me, float in return Done( "Freeze time" ); } - // If a visible threat appeared, bail out and let the normal combat system handle it + // A threat in sight holds back any bot but a ghost carrier if ( IsHeldBackByThreat( me ) ) { return Done( "Threat appeared" );