From 71ab4130f3e78b9e370a5173b425bf712a33e76e Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Sun, 16 Aug 2026 19:07:03 -0600 Subject: [PATCH 1/4] When there are no cap zones for their team, bots search for enemy --- .../bot/behavior/neo_bot_seek_and_destroy.cpp | 40 ++++++++++++++++++- .../bot/behavior/neo_bot_seek_and_destroy.h | 1 + 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp index 00419171c3..45e96977e1 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp @@ -6,11 +6,13 @@ #include "bot/behavior/neo_bot_attack.h" #include "bot/behavior/neo_bot_seek_and_destroy.h" #include "bot/behavior/neo_bot_ctg_seek.h" +#include "bot/behavior/neo_bot_ctg_enemy.h" #include "bot/behavior/neo_bot_jgr_seek.h" #include "bot/neo_bot_path_compute.h" #include "nav_mesh.h" #include "soundent.h" +extern ConVar mp_chattime; extern ConVar neo_bot_path_lookahead_range; extern ConVar neo_bot_offense_must_push_time; extern ConVar neo_bot_defense_must_defend_time; @@ -160,7 +162,43 @@ ActionResult< CNEOBot > CNEOBotSeekAndDestroy::Update( CNEOBot *me, float interv // Check for Game Type Specific behaviors and suspend for them if (NEORules()->GetGameType() == NEO_GAME_TYPE_CTG) { - return SuspendFor( new CNEOBotCtgSeek, "Switching to Ghost-related Seek and Destroy" ); + // Check if enemy has the ghost + if (NEORules()->GhostExists()) + { + int iGhosterPlayer = NEORules()->GetGhosterPlayer(); + if (iGhosterPlayer > 0 && iGhosterPlayer <= gpGlobals->maxClients) + { + CNEO_Player* pGhostCarrier = ToNEOPlayer(UTIL_PlayerByIndex(iGhosterPlayer)); + if (pGhostCarrier && pGhostCarrier != me && pGhostCarrier->GetTeamNumber() != me->GetTeamNumber()) + { + return SuspendFor(new CNEOBotCtgEnemy, "Stopping the ghost carrier!"); + } + } + } + + if (!m_ctgCheckTimer.HasStarted() || m_ctgCheckTimer.IsElapsed()) + { + m_ctgCheckTimer.Start(mp_chattime.GetFloat()); + + // Only switch to CTG behavior if there are available capture zones this round + bool bHasAvailableCapZone = false; + const int iMyTeam = me->GetTeamNumber(); + + for( int i=0; im_pGhostCaps.Count(); ++i ) + { + CNEOGhostCapturePoint *pCapPoint = dynamic_cast( UTIL_EntityByIndex( NEORules()->m_pGhostCaps[i] ) ); + if ( pCapPoint && pCapPoint->GetActive() && pCapPoint->owningTeamAlternate() == iMyTeam ) + { + bHasAvailableCapZone = true; + break; + } + } + + if ( bHasAvailableCapZone ) + { + return SuspendFor( new CNEOBotCtgSeek, "Switching to Ghost-related Seek and Destroy" ); + } + } } else if (NEORules()->GetGameType() == NEO_GAME_TYPE_JGR) { diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h index d9b1370b58..c5fcb22fe5 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h @@ -52,5 +52,6 @@ class CNEOBotSeekAndDestroy : public Action< CNEOBot > bool m_bOverrideApproach = false; Vector m_vOverrideApproach = vec3_origin; + CountdownTimer m_ctgCheckTimer; CountdownTimer m_giveUpTimer; }; From bb2a6e2211f71d922dd5f680757de58d79cc2b73 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Tue, 18 Aug 2026 00:06:54 -0600 Subject: [PATCH 2/4] Only check if cap zones exist during freezetime --- src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp | 4 +--- src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp index 45e96977e1..6e18c46f40 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp @@ -12,7 +12,6 @@ #include "nav_mesh.h" #include "soundent.h" -extern ConVar mp_chattime; extern ConVar neo_bot_path_lookahead_range; extern ConVar neo_bot_offense_must_push_time; extern ConVar neo_bot_defense_must_defend_time; @@ -176,9 +175,8 @@ ActionResult< CNEOBot > CNEOBotSeekAndDestroy::Update( CNEOBot *me, float interv } } - if (!m_ctgCheckTimer.HasStarted() || m_ctgCheckTimer.IsElapsed()) + if ( NEORules()->GetRemainingPreRoundFreezeTime( true ) > 0.0f ) { - m_ctgCheckTimer.Start(mp_chattime.GetFloat()); // Only switch to CTG behavior if there are available capture zones this round bool bHasAvailableCapZone = false; diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h index c5fcb22fe5..d9b1370b58 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h @@ -52,6 +52,5 @@ class CNEOBotSeekAndDestroy : public Action< CNEOBot > bool m_bOverrideApproach = false; Vector m_vOverrideApproach = vec3_origin; - CountdownTimer m_ctgCheckTimer; CountdownTimer m_giveUpTimer; }; From fde3ef96f09ddfeae2e3e04cc5f9fb8f2d1faff5 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Tue, 18 Aug 2026 00:34:10 -0600 Subject: [PATCH 3/4] Reorganize execution order --- .../bot/behavior/neo_bot_seek_and_destroy.cpp | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp index 6e18c46f40..0f52286514 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp @@ -154,30 +154,11 @@ ActionResult< CNEOBot > CNEOBotSeekAndDestroy::OnStart( CNEOBot *me, Action< CNE //--------------------------------------------------------------------------------------------- ActionResult< CNEOBot > CNEOBotSeekAndDestroy::Update( CNEOBot *me, float interval ) { - ActionResult< CNEOBot > result = UpdateCommon( me, interval ); - if ( result.IsRequestingChange() || result.IsDone() ) - return result; - // Check for Game Type Specific behaviors and suspend for them - if (NEORules()->GetGameType() == NEO_GAME_TYPE_CTG) + if ( NEORules()->GetRemainingPreRoundFreezeTime( true ) > 0.0f ) { - // Check if enemy has the ghost - if (NEORules()->GhostExists()) + if (NEORules()->GetGameType() == NEO_GAME_TYPE_CTG) { - int iGhosterPlayer = NEORules()->GetGhosterPlayer(); - if (iGhosterPlayer > 0 && iGhosterPlayer <= gpGlobals->maxClients) - { - CNEO_Player* pGhostCarrier = ToNEOPlayer(UTIL_PlayerByIndex(iGhosterPlayer)); - if (pGhostCarrier && pGhostCarrier != me && pGhostCarrier->GetTeamNumber() != me->GetTeamNumber()) - { - return SuspendFor(new CNEOBotCtgEnemy, "Stopping the ghost carrier!"); - } - } - } - - if ( NEORules()->GetRemainingPreRoundFreezeTime( true ) > 0.0f ) - { - // Only switch to CTG behavior if there are available capture zones this round bool bHasAvailableCapZone = false; const int iMyTeam = me->GetTeamNumber(); @@ -196,11 +177,37 @@ ActionResult< CNEOBot > CNEOBotSeekAndDestroy::Update( CNEOBot *me, float interv { return SuspendFor( new CNEOBotCtgSeek, "Switching to Ghost-related Seek and Destroy" ); } + + } + else if (NEORules()->GetGameType() == NEO_GAME_TYPE_JGR) + { + return SuspendFor( new CNEOBotJgrSeek, "Switching to Juggernaut-related Seek and Destroy" ); + } + + return Continue(); + } + + if (NEORules()->GetGameType() == NEO_GAME_TYPE_CTG) + { + // Check if enemy has the ghost + if (NEORules()->GhostExists()) + { + int iGhosterPlayer = NEORules()->GetGhosterPlayer(); + if (iGhosterPlayer > 0 && iGhosterPlayer <= gpGlobals->maxClients) + { + CNEO_Player* pGhostCarrier = ToNEOPlayer(UTIL_PlayerByIndex(iGhosterPlayer)); + if (pGhostCarrier && pGhostCarrier != me && pGhostCarrier->GetTeamNumber() != me->GetTeamNumber()) + { + return SuspendFor(new CNEOBotCtgEnemy, "Stopping the ghost carrier!"); + } + } } } - else if (NEORules()->GetGameType() == NEO_GAME_TYPE_JGR) + + ActionResult< CNEOBot > result = UpdateCommon( me, interval ); + if ( result.IsRequestingChange() || result.IsDone() ) { - return SuspendFor( new CNEOBotJgrSeek, "Switching to Juggernaut-related Seek and Destroy" ); + return result; } return Continue(); From 28cd86c2a2538b7d9ad843d79d9e74cdb249c04d Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Tue, 18 Aug 2026 00:34:20 -0600 Subject: [PATCH 4/4] Check cap zone team alignment --- .../server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp index 0f52286514..296631fa91 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp @@ -166,7 +166,13 @@ ActionResult< CNEOBot > CNEOBotSeekAndDestroy::Update( CNEOBot *me, float interv for( int i=0; im_pGhostCaps.Count(); ++i ) { CNEOGhostCapturePoint *pCapPoint = dynamic_cast( UTIL_EntityByIndex( NEORules()->m_pGhostCaps[i] ) ); - if ( pCapPoint && pCapPoint->GetActive() && pCapPoint->owningTeamAlternate() == iMyTeam ) + if ( !pCapPoint || !pCapPoint->GetActive() ) + { + continue; + } + + const int iCapTeam = pCapPoint->owningTeamAlternate(); + if ( iCapTeam == iMyTeam || iCapTeam == TEAM_ANY ) { bHasAvailableCapZone = true; break;