From 9421515ca9d29cec1427ceef66deb45c76d379cf Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Tue, 27 Jan 2026 22:20:50 -0700 Subject: [PATCH 1/2] Move bot command following to scenario monitor --- .../bot/behavior/neo_bot_scenario_monitor.cpp | 18 ++++++++++++++++++ .../bot/behavior/neo_bot_tactical_monitor.cpp | 18 +----------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/game/server/neo/bot/behavior/neo_bot_scenario_monitor.cpp b/src/game/server/neo/bot/behavior/neo_bot_scenario_monitor.cpp index 2ef7bf96e7..cb0b348e02 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_scenario_monitor.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_scenario_monitor.cpp @@ -13,6 +13,8 @@ #include "bot/behavior/neo_bot_retreat_to_cover.h" #include "bot/behavior/neo_bot_get_health.h" #include "bot/behavior/neo_bot_get_ammo.h" +#include "bot/behavior/neo_bot_command_follow.h" +#include "bot/behavior/neo_bot_pause.h" #include "bot/behavior/neo_bot_attack.h" #include "bot/behavior/neo_bot_seek_and_destroy.h" @@ -64,10 +66,26 @@ ActionResult< CNEOBot > CNEOBotScenarioMonitor::OnStart( CNEOBot *me, Action< CN ConVar neo_bot_fetch_lost_flag_time( "neo_bot_fetch_lost_flag_time", "10", FCVAR_CHEAT, "How long busy NEOBots will ignore the dropped flag before they give up what they are doing and go after it" ); ConVar neo_bot_flag_kill_on_touch( "neo_bot_flag_kill_on_touch", "0", FCVAR_CHEAT, "If nonzero, any bot that picks up the flag dies. For testing." ); +extern ConVar sv_neo_bot_cmdr_enable; +extern ConVar sv_neo_bot_cmdr_debug_pause_uncommanded; + //----------------------------------------------------------------------------------------- ActionResult< CNEOBot > CNEOBotScenarioMonitor::Update( CNEOBot *me, float interval ) { + if (sv_neo_bot_cmdr_enable.GetBool()) + { + if (me->m_hLeadingPlayer.Get() || me->m_hCommandingPlayer.Get()) + { + return SuspendFor(new CNEOBotCommandFollow, "Following commander"); + } + + if (sv_neo_bot_cmdr_debug_pause_uncommanded.GetBool()) + { + return SuspendFor(new CNEOBotPause, "Paused by debug convar sv_neo_bot_cmdr_debug_pause_uncommanded"); + } + } + return Continue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp index 70dca838ee..4e1049c9f7 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp @@ -10,7 +10,6 @@ #include "bot/behavior/neo_bot_tactical_monitor.h" #include "bot/behavior/neo_bot_scenario_monitor.h" -#include "bot/behavior/neo_bot_command_follow.h" #include "bot/behavior/neo_bot_seek_and_destroy.h" #include "bot/behavior/neo_bot_seek_weapon.h" #include "bot/behavior/neo_bot_retreat_to_cover.h" @@ -26,8 +25,6 @@ ConVar neo_bot_force_jump( "neo_bot_force_jump", "0", FCVAR_CHEAT, "Force bots to continuously jump" ); ConVar neo_bot_grenade_check_radius( "neo_bot_grenade_check_radius", "500", FCVAR_CHEAT ); -extern ConVar sv_neo_bot_cmdr_enable; -extern ConVar sv_neo_bot_cmdr_debug_pause_uncommanded; //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -279,19 +276,6 @@ ActionResult< CNEOBot > CNEOBotTacticalMonitor::Update( CNEOBot *me, float inter return result; } - if (sv_neo_bot_cmdr_enable.GetBool()) - { - if (me->m_hLeadingPlayer.Get() || me->m_hCommandingPlayer.Get()) - { - return SuspendFor(new CNEOBotCommandFollow, "Following commander"); - } - - if (sv_neo_bot_cmdr_debug_pause_uncommanded.GetBool()) - { - return SuspendFor( new CNEOBotPause, "Paused by debug convar sv_neo_bot_cmdr_debug_pause_uncommanded" ); - } - } - const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); // check if we need to get to cover @@ -370,7 +354,7 @@ ActionResult< CNEOBot > CNEOBotTacticalMonitor::ScavengeForPrimaryWeapon( CNEOBo { return Continue(); } - m_maintainTimer.Start( RandomFloat( 1.0f, 3.0f ) ); + m_maintainTimer.Start( 1.0f ); // Look for any one valid primary weapon, then dispatch into behavior for more optimal search // true parameter: short-circuit the search if any valid primary weapon is found From cebc10a1b7fad77c25fc38d014477ed5bfc6b2cb Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Tue, 27 Jan 2026 22:51:36 -0700 Subject: [PATCH 2/2] Bots can traverse ladders --- README.md | 1 + src/game/server/CMakeLists.txt | 4 + .../bot/behavior/neo_bot_command_follow.cpp | 4 +- .../bot/behavior/neo_bot_ladder_approach.cpp | 151 ++++++++++++++++++ .../bot/behavior/neo_bot_ladder_approach.h | 34 ++++ .../neo/bot/behavior/neo_bot_ladder_climb.cpp | 126 +++++++++++++++ .../neo/bot/behavior/neo_bot_ladder_climb.h | 34 ++++ .../bot/behavior/neo_bot_tactical_monitor.cpp | 50 ++++++ .../bot/behavior/neo_bot_tactical_monitor.h | 3 +- src/game/server/neo/bot/neo_bot_path_cost.cpp | 42 +++-- 10 files changed, 430 insertions(+), 19 deletions(-) create mode 100644 src/game/server/neo/bot/behavior/neo_bot_ladder_approach.cpp create mode 100644 src/game/server/neo/bot/behavior/neo_bot_ladder_approach.h create mode 100644 src/game/server/neo/bot/behavior/neo_bot_ladder_climb.cpp create mode 100644 src/game/server/neo/bot/behavior/neo_bot_ladder_climb.h diff --git a/README.md b/README.md index 9db5597955..8115d6b346 100644 --- a/README.md +++ b/README.md @@ -339,4 +339,5 @@ shaders\fxc\sdk_screenspaceeffect_vs20.vcs * Updated as of the TF2 SDK update: [Commit 0759e2e8e179d5352d81d0d4aaded72c1704b7a9](https://github.com/ValveSoftware/source-sdk-2013/commit/0759e2e8e179d5352d81d0d4aaded72c1704b7a9) * [Nbc66/source-sdk-2013-ce](https://github.com/Nbc66/source-sdk-2013-ce) - Community Edition for additional fixes prior to the TF2 SDK update * [tonysergi/source-sdk-2013](https://github.com/tonysergi/source-sdk-2013) - tonysergi's commits that were missing from the original SDK +* [Dragoteryx/drgbase](https://github.com/Dragoteryx/drgbase) - Reference for NextBot ladder climbing behavior diff --git a/src/game/server/CMakeLists.txt b/src/game/server/CMakeLists.txt index 386a7225e7..ee715fb865 100644 --- a/src/game/server/CMakeLists.txt +++ b/src/game/server/CMakeLists.txt @@ -1450,6 +1450,10 @@ target_sources_grouped( neo/bot/behavior/neo_bot_jgr_juggernaut.h neo/bot/behavior/neo_bot_jgr_seek.cpp neo/bot/behavior/neo_bot_jgr_seek.h + neo/bot/behavior/neo_bot_ladder_approach.cpp + neo/bot/behavior/neo_bot_ladder_approach.h + neo/bot/behavior/neo_bot_ladder_climb.cpp + neo/bot/behavior/neo_bot_ladder_climb.h neo/bot/behavior/neo_bot_melee_attack.cpp neo/bot/behavior/neo_bot_melee_attack.h neo/bot/behavior/neo_bot_move_to_vantage_point.cpp diff --git a/src/game/server/neo/bot/behavior/neo_bot_command_follow.cpp b/src/game/server/neo/bot/behavior/neo_bot_command_follow.cpp index adaa7545a1..bcc5b0149b 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_command_follow.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_command_follow.cpp @@ -199,7 +199,7 @@ bool CNEOBotCommandFollow::FollowCommandChain(CNEOBot* me) // Anti-collision: follow neighbor in snake chain for (int idx = 1; idx <= gpGlobals->maxClients; ++idx) { - CNEO_Player* pOther = static_cast(UTIL_PlayerByIndex(idx)); + CNEO_Player* pOther = ToNEOPlayer(UTIL_PlayerByIndex(idx)); if (!pOther || !pOther->IsBot() || pOther == me || (pOther->m_hLeadingPlayer.Get() != me->m_hLeadingPlayer.Get())) { @@ -272,7 +272,7 @@ bool CNEOBotCommandFollow::FanOutAndCover(CNEOBot* me, Vector& movementTarget, b if (pPlayer->GetTeamNumber() != me->GetTeamNumber()) continue; - CNEO_Player* pOther = static_cast(pPlayer); + CNEO_Player* pOther = ToNEOPlayer(pPlayer); if (!pOther) continue; diff --git a/src/game/server/neo/bot/behavior/neo_bot_ladder_approach.cpp b/src/game/server/neo/bot/behavior/neo_bot_ladder_approach.cpp new file mode 100644 index 0000000000..3a7cc4de2c --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_ladder_approach.cpp @@ -0,0 +1,151 @@ +#include "cbase.h" +#include "bot/behavior/neo_bot_ladder_approach.h" +#include "bot/behavior/neo_bot_ladder_climb.h" +#include "bot/behavior/neo_bot_attack.h" +#include "nav_ladder.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//--------------------------------------------------------------------------------------------- +CNEOBotLadderApproach::CNEOBotLadderApproach( const CNavLadder *ladder, bool goingUp ) + : m_ladder( ladder ), m_bGoingUp( goingUp ) +{ +} + +//--------------------------------------------------------------------------------------------- +ActionResult CNEOBotLadderApproach::OnStart( CNEOBot *me, Action *priorAction ) +{ + if ( !m_ladder ) + { + return Done( "No ladder specified" ); + } + + // Timeout for approach phase + m_timeoutTimer.Start( 3.0f ); + + if ( me->IsDebugging( NEXTBOT_PATH ) ) + { + DevMsg( "%s: Starting ladder approach (%s), length %.1f\n", + me->GetDebugIdentifier(), + m_bGoingUp ? "up" : "down", + m_ladder->m_length ); + } + + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +// Implementation based on ladder climbing implementation in https://github.com/Dragoteryx/drgbase/ +ActionResult CNEOBotLadderApproach::Update( CNEOBot *me, float interval ) +{ + if ( m_timeoutTimer.IsElapsed() ) + { + return Done( "Ladder approach timeout" ); + } + + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( threat && threat->IsVisibleRecently() ) + { + if ( me->IsDebugging( NEXTBOT_PATH ) ) + { + DevMsg( "%s: Threat detected during ladder approach - engaging\n", me->GetDebugIdentifier() ); + } + // ChangeTo: We may move away from ladder when fighting, so don't want to get stuck in ladder approach behavior + return ChangeTo( new CNEOBotAttack(), "Engaging enemy before climbing" ); + } + + ILocomotion *mover = me->GetLocomotionInterface(); + IBody *body = me->GetBodyInterface(); + + Vector myPos = mover->GetFeet(); + + // Are we climbing up or down the ladder? + Vector targetPos = m_bGoingUp ? m_ladder->m_bottom : m_ladder->m_top; + + // Calculate 2D vector from bot to ladder mount point + Vector2D to = ( targetPos - myPos ).AsVector2D(); + float range = to.NormalizeInPlace(); + + // Get ladder's outward normal in 2D + Vector2D ladderNormal2D = m_ladder->GetNormal().AsVector2D(); + float dot = DotProduct2D( ladderNormal2D, to ); + + if ( me->IsDebugging( NEXTBOT_PATH ) ) + { + NDebugOverlay::Line( myPos, targetPos, 255, 255, 0, true, 0.1f ); + } + + // Are we aligned and close enough to mount the ladder? + if ( range >= ALIGN_RANGE ) + { + // Far from ladder - just approach the target position + body->AimHeadTowards( targetPos, IBody::CRITICAL, 0.5f, nullptr, "Moving toward ladder" ); + mover->Approach( targetPos ); + } + else if ( range >= MOUNT_RANGE ) + { + // Within alignment range but not mount range - need to align with ladder + if ( dot >= ALIGN_DOT_THRESHOLD ) + { + // Not aligned - rotate around the ladder to get aligned + Vector2D myPerp( -to.y, to.x ); + Vector2D ladderPerp2D( -ladderNormal2D.y, ladderNormal2D.x ); + + Vector goal = targetPos; + + // Calculate offset to circle around + float alignRange = MOUNT_RANGE + (1.0f + dot) * (ALIGN_RANGE - MOUNT_RANGE); + goal.x -= alignRange * to.x; + goal.y -= alignRange * to.y; + + // Choose direction to circle based on perpendicular alignment + if ( DotProduct2D( to, ladderPerp2D ) < 0.0f ) + { + goal.x += 10.0f * myPerp.x; + goal.y += 10.0f * myPerp.y; + } + else + { + goal.x -= 10.0f * myPerp.x; + goal.y -= 10.0f * myPerp.y; + } + + body->AimHeadTowards( goal, IBody::CRITICAL, 0.3f, nullptr, "Aligning with ladder" ); + mover->Approach( goal ); + + if ( me->IsDebugging( NEXTBOT_PATH ) ) + { + NDebugOverlay::Cross3D( goal, 5.0f, 255, 0, 255, true, 0.1f ); + } + } + else + { + // Aligned - approach the ladder base directly + body->AimHeadTowards( targetPos, IBody::CRITICAL, 0.3f, nullptr, "Approaching ladder" ); + mover->Approach( targetPos ); + } + } + else + { + // Within mount range - check if aligned to start climbing + if ( dot < ALIGN_DOT_THRESHOLD ) + { + if ( me->IsDebugging( NEXTBOT_PATH ) ) + { + DevMsg( "%s: Starting ladder climb\n", me->GetDebugIdentifier() ); + } + + // ChangeTo: if something goes wrong during climb, reevaluate situation + return ChangeTo( new CNEOBotLadderClimb( m_ladder, m_bGoingUp ), "Mounting ladder" ); + } + else + { + // Close but not aligned - continue approaching to align + body->AimHeadTowards( targetPos, IBody::CRITICAL, 0.3f, nullptr, "Approaching ladder" ); + mover->Approach( targetPos ); + } + } + + return Continue(); +} diff --git a/src/game/server/neo/bot/behavior/neo_bot_ladder_approach.h b/src/game/server/neo/bot/behavior/neo_bot_ladder_approach.h new file mode 100644 index 0000000000..293e61e8ab --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_ladder_approach.h @@ -0,0 +1,34 @@ +#pragma once + +#include "NextBotBehavior.h" +#include "bot/neo_bot.h" + +class CNavLadder; + +//---------------------------------------------------------------------------------------------------------------- +/** + * Implementation based on ladder climbing logic in https://github.com/Dragoteryx/drgbase/ + * Behavior that handles approaching a ladder and aligning with it. + * Uses ChangeTo for climb transition so knocked-off bots reevaluate situation. + * Recommended to use SuspendFor to transition to the behavior or else scenario behavior may be stopped. + */ +class CNEOBotLadderApproach : public Action +{ +public: + CNEOBotLadderApproach( const CNavLadder *ladder, bool goingUp ); + virtual ~CNEOBotLadderApproach() = default; + + virtual const char *GetName() const override { return "LadderApproach"; } + + virtual ActionResult OnStart( CNEOBot *me, Action *priorAction ) override; + virtual ActionResult Update( CNEOBot *me, float interval ) override; + +private: + const CNavLadder *m_ladder; + bool m_bGoingUp; + CountdownTimer m_timeoutTimer; + + static constexpr float MOUNT_RANGE = 25.0f; // Distance to start climbing + static constexpr float ALIGN_RANGE = 50.0f; // Distance to start alignment behavior + static constexpr float ALIGN_DOT_THRESHOLD = -0.9f; // cos(~25 degrees) alignment tolerance +}; diff --git a/src/game/server/neo/bot/behavior/neo_bot_ladder_climb.cpp b/src/game/server/neo/bot/behavior/neo_bot_ladder_climb.cpp new file mode 100644 index 0000000000..72666f23f7 --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_ladder_climb.cpp @@ -0,0 +1,126 @@ +#include "cbase.h" +#include "bot/behavior/neo_bot_ladder_climb.h" +#include "nav_ladder.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//--------------------------------------------------------------------------------------------- +CNEOBotLadderClimb::CNEOBotLadderClimb( const CNavLadder *ladder, bool goingUp ) + : m_ladder( ladder ), m_bGoingUp( goingUp ), m_bHasBeenOnLadder( false ) +{ +} + +//--------------------------------------------------------------------------------------------- +ActionResult CNEOBotLadderClimb::OnStart( CNEOBot *me, Action *priorAction ) +{ + if ( !m_ladder ) + { + return Done( "No ladder specified" ); + } + + // Ignore enemies while climbing + me->StopLookingAroundForEnemies(); + + // Timeout based on ladder length + float estimatedClimbTime = m_ladder->m_length / 100.0f + 2.0f; + m_timeoutTimer.Start( estimatedClimbTime ); + + m_bHasBeenOnLadder = false; + + if ( me->IsDebugging( NEXTBOT_PATH ) ) + { + DevMsg( "%s: Starting ladder climb (%s), length %.1f\n", + me->GetDebugIdentifier(), + m_bGoingUp ? "up" : "down", + m_ladder->m_length ); + } + + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +// Implementation based on ladder climbing logic in https://github.com/Dragoteryx/drgbase/ +ActionResult CNEOBotLadderClimb::Update( CNEOBot *me, float interval ) +{ + if ( m_timeoutTimer.IsElapsed() ) + { + return Done( "Ladder climb timeout" ); + } + + ILocomotion *mover = me->GetLocomotionInterface(); + IBody *body = me->GetBodyInterface(); + + // Check if we're on the ladder (MOVETYPE_LADDER or locomotion says so) + bool onLadder = ( me->GetMoveType() == MOVETYPE_LADDER ) || + mover->IsUsingLadder() || + mover->IsAscendingOrDescendingLadder(); + + if ( onLadder ) + { + m_bHasBeenOnLadder = true; + } + else if ( m_bHasBeenOnLadder ) + { + // We were on the ladder but got knocked off - return to reevaluate situation + // Since ladder approach should use ChangeTo climbing behavior, this Done will return to the state BEFORE ladder approach + return Done( "Knocked off ladder - reevaluating situation" ); + } + + // Get current position and target + Vector myPos = mover->GetFeet(); + float currentZ = me->GetAbsOrigin().z; + float targetZ = m_bGoingUp ? m_ladder->m_top.z : m_ladder->m_bottom.z; + + if ( m_bGoingUp ) + { + if ( currentZ >= targetZ - mover->GetStepHeight() ) + { + return Done( "Reached top of ladder" ); + } + + // Climb up: look up and push into ladder + Vector goal = myPos + 100.0f * ( -m_ladder->GetNormal() + Vector( 0, 0, 2 ) ); + body->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, nullptr, "Climbing ladder" ); + mover->Approach( goal, 9999999.9f ); + } + else + { + if ( currentZ <= targetZ + mover->GetStepHeight() ) + { + return Done( "Reached bottom of ladder" ); + } + + // Climb down: Stare at bottom of ladder while moving forward to it + Vector goal = m_ladder->m_bottom; + body->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, nullptr, "Descending ladder fast" ); + mover->Approach( goal, 9999999.9f ); + } + + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +void CNEOBotLadderClimb::OnEnd( CNEOBot *me, Action *nextAction ) +{ + me->StartLookingAroundForEnemies(); + + if ( me->IsDebugging( NEXTBOT_PATH ) ) + { + DevMsg( "%s: Finished ladder climb\n", me->GetDebugIdentifier() ); + } +} + +//--------------------------------------------------------------------------------------------- +ActionResult CNEOBotLadderClimb::OnSuspend( CNEOBot *me, Action *interruptingAction ) +{ + me->StartLookingAroundForEnemies(); + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +ActionResult CNEOBotLadderClimb::OnResume( CNEOBot *me, Action *interruptingAction ) +{ + me->StopLookingAroundForEnemies(); + return Continue(); +} diff --git a/src/game/server/neo/bot/behavior/neo_bot_ladder_climb.h b/src/game/server/neo/bot/behavior/neo_bot_ladder_climb.h new file mode 100644 index 0000000000..592bf13b13 --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_ladder_climb.h @@ -0,0 +1,34 @@ +#pragma once + +#include "NextBotBehavior.h" +#include "bot/neo_bot.h" + +class CNavLadder; + +//---------------------------------------------------------------------------------------------------------------- +/** + * Implementation based on ladder climbing logic in https://github.com/Dragoteryx/drgbase/ + * Behavior that handles the actual ladder climbing. + * DOES disable enemy awareness while climbing. + * Checks if still on ladder - returns Done if knocked off to reevaluate situation. + */ +class CNEOBotLadderClimb : public Action +{ +public: + CNEOBotLadderClimb( const CNavLadder *ladder, bool goingUp ); + virtual ~CNEOBotLadderClimb() = default; + + virtual const char *GetName() const override { return "LadderClimb"; } + + virtual ActionResult OnStart( CNEOBot *me, Action *priorAction ) override; + virtual ActionResult Update( CNEOBot *me, float interval ) override; + virtual void OnEnd( CNEOBot *me, Action *nextAction ) override; + virtual ActionResult OnSuspend( CNEOBot *me, Action *interruptingAction ) override; + virtual ActionResult OnResume( CNEOBot *me, Action *interruptingAction ) override; + +private: + const CNavLadder *m_ladder; + bool m_bGoingUp; + CountdownTimer m_timeoutTimer; + bool m_bHasBeenOnLadder; // Track if we ever got on the ladder +}; diff --git a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp index 4e1049c9f7..902ad1071a 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp @@ -2,6 +2,7 @@ #include "fmtstr.h" #include "neo_gamerules.h" +#include "nav_ladder.h" #include "NextBot/NavMeshEntities/func_nav_prerequisite.h" #include "bot/neo_bot.h" @@ -14,6 +15,7 @@ #include "bot/behavior/neo_bot_seek_weapon.h" #include "bot/behavior/neo_bot_retreat_to_cover.h" #include "bot/behavior/neo_bot_retreat_from_grenade.h" +#include "bot/behavior/neo_bot_ladder_approach.h" #include "bot/behavior/neo_bot_pause.h" #if 0 // NEO TODO (Adam) Fix picking up weapons, search for dropped weapons to pick up ammo #include "bot/behavior/neo_bot_get_ammo.h" @@ -257,6 +259,48 @@ ActionResult< CNEOBot > CNEOBotTacticalMonitor::WatchForGrenades( CNEOBot *me ) } +//----------------------------------------------------------------------------------------- +ActionResult< CNEOBot > CNEOBotTacticalMonitor::WatchForLadders( CNEOBot *me ) +{ + // Check if our current path has an approaching ladder segment + const PathFollower *path = me->GetCurrentPath(); + if ( !path || !path->IsValid() ) + { + return Continue(); + } + + const Path::Segment *goal = path->GetCurrentGoal(); + if ( !goal || !goal->ladder ) + { + return Continue(); + } + + // Already using a ladder via locomotion interface + ILocomotion *mover = me->GetLocomotionInterface(); + if ( mover->IsUsingLadder() || mover->IsAscendingOrDescendingLadder() ) + { + return Continue(); + } + + // We're approaching a ladder - check distance + const float ladderApproachRange = 60.0f; + Vector ladderPos = (goal->how == GO_LADDER_UP) + ? goal->ladder->m_bottom + : goal->ladder->m_top; + + if ( me->GetAbsOrigin().DistToSqr( ladderPos ) < Square(ladderApproachRange) ) + { + bool goingUp = (goal->how == GO_LADDER_UP); + return SuspendFor( + new CNEOBotLadderApproach( goal->ladder, goingUp ), + goingUp ? "Approaching ladder up" : "Approaching ladder down" + ); + } + + return Continue(); +} + + //----------------------------------------------------------------------------------------- ActionResult< CNEOBot > CNEOBotTacticalMonitor::Update( CNEOBot *me, float interval ) { @@ -276,6 +320,12 @@ ActionResult< CNEOBot > CNEOBotTacticalMonitor::Update( CNEOBot *me, float inter return result; } + result = WatchForLadders( me ); + if ( result.IsRequestingChange() ) + { + return result; + } + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); // check if we need to get to cover diff --git a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h index 292e640df1..eeab6f9c03 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h +++ b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h @@ -36,4 +36,5 @@ class CNEOBotTacticalMonitor : public Action< CNEOBot > void AvoidBumpingFriends(CNEOBot* me); void ReconConsiderSuperJump(CNEOBot *me); ActionResult< CNEOBot > WatchForGrenades(CNEOBot* me); -}; \ No newline at end of file + ActionResult< CNEOBot > WatchForLadders(CNEOBot* me); +}; diff --git a/src/game/server/neo/bot/neo_bot_path_cost.cpp b/src/game/server/neo/bot/neo_bot_path_cost.cpp index 89f8e173c9..f4a5d667b0 100644 --- a/src/game/server/neo/bot/neo_bot_path_cost.cpp +++ b/src/game/server/neo/bot/neo_bot_path_cost.cpp @@ -15,6 +15,9 @@ ConVar neo_bot_path_around_friendly_cooldown("neo_bot_path_around_friendly_coold ConVar neo_bot_path_penalty_jump_multiplier("neo_bot_path_penalty_jump_multiplier", "100.0", FCVAR_CHEAT, "Maximum penalty multiplier for jump height changes in pathfinding", false, 0.01f, false, 1000.0f); +ConVar neo_bot_path_penalty_ladder_multiplier("neo_bot_path_penalty_ladder_multiplier", "3.0", FCVAR_CHEAT, + "Penalty multiplier for ladder traversal in pathfinding", true, 0.1f, true, 100.0f); + //------------------------------------------------------------------------------------------------- CNEOBotPathCost::CNEOBotPathCost(CNEOBot* me, RouteType routeType) { @@ -51,6 +54,10 @@ float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const if (ladder) { dist = ladder->m_length; + + // ladders leave bots exposed, but can be a shortcut + const float ladderPenalty = neo_bot_path_penalty_ladder_multiplier.GetFloat(); + dist *= ladderPenalty; } else if (length > 0.0) { @@ -61,26 +68,29 @@ float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const dist = (area->GetCenter() - fromArea->GetCenter()).Length(); } - - // check height change - float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange(area); - - if (deltaZ >= m_stepHeight) + // Only apply height restrictions for non-ladder jump paths + if (!ladder) { - if (deltaZ >= m_maxJumpHeight) + // check height change + float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange(area); + + if (deltaZ >= m_stepHeight) { - // too high to reach + if (deltaZ >= m_maxJumpHeight) + { + // too high to reach + return -1.0f; + } + + // jumping is slower than flat ground + const float jumpPenalty = neo_bot_path_penalty_jump_multiplier.GetFloat() * Square( deltaZ / m_maxJumpHeight ); + dist *= jumpPenalty; + } + else if (deltaZ < -m_maxDropHeight) + { + // too far to drop return -1.0f; } - - // jumping is slower than flat ground - const float jumpPenalty = neo_bot_path_penalty_jump_multiplier.GetFloat() * Square( deltaZ / m_maxJumpHeight ); - dist *= jumpPenalty; - } - else if (deltaZ < -m_maxDropHeight) - { - // too far to drop - return -1.0f; } // add a random penalty unique to this character so they choose different routes to the same place