From 675dea144915fc8e1c5427e56f1a4e195704b8ca Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Tue, 9 Dec 2025 22:32:00 -0700 Subject: [PATCH 1/2] Bots slow down if far ahead of teammates --- .../bot/behavior/neo_bot_seek_and_destroy.cpp | 62 +++++++++++++++++++ .../bot/behavior/neo_bot_seek_and_destroy.h | 6 +- src/game/server/neo/bot/neo_bot.cpp | 42 ++++++++----- 3 files changed, 92 insertions(+), 18 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 a30d954344..33c0776ca2 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 @@ -227,6 +227,55 @@ QueryResultType CNEOBotSeekAndDestroy::ShouldHurry( const INextBot *me ) const return ANSWER_UNDEFINED; } + +//--------------------------------------------------------------------------------------------- +// Should I slow down to let my teammates catch up? +QueryResultType CNEOBotSeekAndDestroy::ShouldWalk( const CNEOBot *me, const QueryResultType qShouldAimQuery ) const +{ + + if (NEORules()->GetGameType() == NEO_GAME_TYPE_CTG) + { + if (NEORules()->GhostExists()) + { + // For comparing my distance to the ghost compared to my teammates + const Vector &vGhostPos = NEORules()->GetGhostPos(); + float flMySqDistToGhost = (me->GetAbsOrigin() - vGhostPos).LengthSqr(); + int iAliveTeammates = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if (!pPlayer || !pPlayer->IsAlive() || pPlayer->GetTeamNumber() != me->GetTeamNumber() || pPlayer == me->GetEntity()) + { + continue; + } + + iAliveTeammates++; + + // Check if this teammate is closer to the ghost + float flTeammateSqDistToGhost = (pPlayer->GetAbsOrigin() - vGhostPos).LengthSqr(); + if (flTeammateSqDistToGhost < flMySqDistToGhost) + { + // Maybe I can catch up to my teammate up ahead + return ANSWER_UNDEFINED; + } + } + + if (iAliveTeammates == 0) + { + // I'm the sole survivor, don't need to wait for anyone + return ANSWER_UNDEFINED; + } + + // I should slow down to let my teammates catch up + return ANSWER_YES; + } + } + + return ANSWER_UNDEFINED; +} + class CNextSpawnFilter : public IEntityFindFilter { public: @@ -561,3 +610,16 @@ EventDesiredResult< CNEOBot > CNEOBotSeekAndDestroy::OnCommandApproach( CNEOBot* return TryContinue(); } + + +//--------------------------------------------------------------------------------------------- +QueryResultType CNEOBotSeekAndDestroy::ShouldAim(const CNEOBot* me, const bool bWepHasClip) const +{ + if (ShouldWalk(me, ANSWER_UNDEFINED) == ANSWER_YES) + { + // aiming is a safe way to slow down for teammates + return ANSWER_YES; + } + + return ANSWER_UNDEFINED; +} 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 39950bfa70..9731d0148c 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 @@ -1,12 +1,12 @@ #pragma once #include "Path/NextBotChasePath.h" - +#include "bot/neo_bot_contextual_query_interface.h" // // Roam around the map attacking enemies // -class CNEOBotSeekAndDestroy : public Action< CNEOBot > +class CNEOBotSeekAndDestroy : public Action< CNEOBot >, public CNEOBotContextualQueryInterface { public: CNEOBotSeekAndDestroy( float duration = -1.0f ); @@ -22,6 +22,8 @@ class CNEOBotSeekAndDestroy : public Action< CNEOBot > virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat? virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry? + virtual QueryResultType ShouldWalk( const CNEOBot *me, const QueryResultType qShouldAimQuery ) const override; // are we to walk? + virtual QueryResultType ShouldAim(const CNEOBot *me, const bool bWepHasClip) const override; virtual EventDesiredResult< CNEOBot > OnTerritoryCaptured( CNEOBot *me, int territoryID ); virtual EventDesiredResult< CNEOBot > OnTerritoryLost( CNEOBot *me, int territoryID ); diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index 8390df1cf8..068272bd99 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -2657,24 +2657,29 @@ QueryResultType CNEOBotBehavior::ShouldWalk(const CNEOBot *me, const QueryResult { QueryResultType result = ANSWER_UNDEFINED; - auto *neoAction = static_cast(m_action); - if ( neoAction ) + Action *action = m_action; + if ( action ) { // find innermost child action - CNEOBotMainAction *action; - for( action = neoAction; action->m_child; action = static_cast(action->m_child) ) - ; + while( action->GetActiveChildAction() ) + { + action = action->GetActiveChildAction(); + } // work our way through our containers while( action && result == ANSWER_UNDEFINED ) { - CNEOBotMainAction *containingAction = static_cast(action->m_parent); + Action *containingAction = action->GetParentAction(); // work our way up the stack while( action && result == ANSWER_UNDEFINED ) { - result = action->ShouldWalk(me, qShouldAimQuery); - action = static_cast(action->GetActionBuriedUnderMe()); + auto *query = dynamic_cast(action); + if (query) + { + result = query->ShouldWalk(me, qShouldAimQuery); + } + action = action->GetActionBuriedUnderMe(); } action = containingAction; @@ -2693,24 +2698,29 @@ QueryResultType CNEOBotBehavior::ShouldAim(const CNEOBot *me, const bool bWepHas { QueryResultType result = ANSWER_UNDEFINED; - auto *neoAction = static_cast(m_action); - if ( neoAction ) + Action *action = m_action; + if ( action ) { // find innermost child action - CNEOBotMainAction *action; - for( action = neoAction; action->m_child; action = static_cast(action->m_child) ) - ; + while( action->GetActiveChildAction() ) + { + action = action->GetActiveChildAction(); + } // work our way through our containers while( action && result == ANSWER_UNDEFINED ) { - CNEOBotMainAction *containingAction = static_cast(action->m_parent); + Action *containingAction = action->GetParentAction(); // work our way up the stack while( action && result == ANSWER_UNDEFINED ) { - result = action->ShouldAim(me, bWepHasClip); - action = static_cast(action->GetActionBuriedUnderMe()); + auto *query = dynamic_cast(action); + if (query) + { + result = query->ShouldAim(me, bWepHasClip); + } + action = action->GetActionBuriedUnderMe(); } action = containingAction; From 10b2a3f38d03d322f753251d9326640cecdfb7a1 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Wed, 10 Dec 2025 22:01:12 -0700 Subject: [PATCH 2/2] Consider running if threat or friendly ghoster present --- .../bot/behavior/neo_bot_seek_and_destroy.cpp | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 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 33c0776ca2..7e85892070 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 @@ -232,6 +232,18 @@ QueryResultType CNEOBotSeekAndDestroy::ShouldHurry( const INextBot *me ) const // Should I slow down to let my teammates catch up? QueryResultType CNEOBotSeekAndDestroy::ShouldWalk( const CNEOBot *me, const QueryResultType qShouldAimQuery ) const { + // ShouldAim query shorts cuts ShouldWalk to ANSWER_YES as aiming and running blocks each other + if (qShouldAimQuery == ANSWER_YES) + { + return ANSWER_YES; + } + + const CKnownEntity* threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if (threat != NULL) + { + // I need to decide how to deal with a threat + return ANSWER_UNDEFINED; + } if (NEORules()->GetGameType() == NEO_GAME_TYPE_CTG) { @@ -253,6 +265,13 @@ QueryResultType CNEOBotSeekAndDestroy::ShouldWalk( const CNEOBot *me, const Quer iAliveTeammates++; + const CNEO_Player *pNeoPlayer = ToNEOPlayer(pPlayer); + if (pNeoPlayer && pNeoPlayer->IsCarryingGhost()) + { + // Maybe I can catch up to my team's ghost carrier + return ANSWER_UNDEFINED; + } + // Check if this teammate is closer to the ghost float flTeammateSqDistToGhost = (pPlayer->GetAbsOrigin() - vGhostPos).LengthSqr(); if (flTeammateSqDistToGhost < flMySqDistToGhost) @@ -276,6 +295,14 @@ QueryResultType CNEOBotSeekAndDestroy::ShouldWalk( const CNEOBot *me, const Quer return ANSWER_UNDEFINED; } + +//--------------------------------------------------------------------------------------------- +QueryResultType CNEOBotSeekAndDestroy::ShouldAim(const CNEOBot* me, const bool bWepHasClip) const +{ + return ANSWER_UNDEFINED; +} + + class CNextSpawnFilter : public IEntityFindFilter { public: @@ -610,16 +637,3 @@ EventDesiredResult< CNEOBot > CNEOBotSeekAndDestroy::OnCommandApproach( CNEOBot* return TryContinue(); } - - -//--------------------------------------------------------------------------------------------- -QueryResultType CNEOBotSeekAndDestroy::ShouldAim(const CNEOBot* me, const bool bWepHasClip) const -{ - if (ShouldWalk(me, ANSWER_UNDEFINED) == ANSWER_YES) - { - // aiming is a safe way to slow down for teammates - return ANSWER_YES; - } - - return ANSWER_UNDEFINED; -}