diff --git a/src/game/server/CMakeLists.txt b/src/game/server/CMakeLists.txt index b80b4eca32..3e40c5d5fc 100644 --- a/src/game/server/CMakeLists.txt +++ b/src/game/server/CMakeLists.txt @@ -1408,6 +1408,12 @@ target_sources_grouped( FILES neo/bot/neo_bot.cpp neo/bot/neo_bot.h + neo/bot/neo_bot_path_compute.cpp + neo/bot/neo_bot_path_compute.h + neo/bot/neo_bot_path_cost.cpp + neo/bot/neo_bot_path_cost.h + neo/bot/neo_bot_path_reservation.cpp + neo/bot/neo_bot_path_reservation.h neo/bot/neo_bot_body.cpp neo/bot/neo_bot_body.h neo/bot/neo_bot_contextual_query_interface.h diff --git a/src/game/server/neo/bot/behavior/nav_entities/neo_bot_nav_ent_destroy_entity.cpp b/src/game/server/neo/bot/behavior/nav_entities/neo_bot_nav_ent_destroy_entity.cpp index 4748e27e47..ec074243da 100644 --- a/src/game/server/neo/bot/behavior/nav_entities/neo_bot_nav_ent_destroy_entity.cpp +++ b/src/game/server/neo/bot/behavior/nav_entities/neo_bot_nav_ent_destroy_entity.cpp @@ -3,6 +3,7 @@ #include "neo_player.h" #include "bot/neo_bot.h" #include "bot/behavior/nav_entities/neo_bot_nav_ent_destroy_entity.h" +#include "bot/neo_bot_path_compute.h" extern ConVar neo_bot_path_lookahead_range; @@ -133,8 +134,7 @@ ActionResult< CNEOBot > CNEOBotNavEntDestroyEntity::Update( CNEOBot *me, float i { m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); - CNEOBotPathCost cost( me, FASTEST_ROUTE ); - m_path.Compute( me, target->GetAbsOrigin(), cost ); + CNEOBotPathCompute( me, m_path, target->GetAbsOrigin(), FASTEST_ROUTE ); } m_path.Update( me ); diff --git a/src/game/server/neo/bot/behavior/nav_entities/neo_bot_nav_ent_move_to.cpp b/src/game/server/neo/bot/behavior/nav_entities/neo_bot_nav_ent_move_to.cpp index 709925b870..861b5404c2 100644 --- a/src/game/server/neo/bot/behavior/nav_entities/neo_bot_nav_ent_move_to.cpp +++ b/src/game/server/neo/bot/behavior/nav_entities/neo_bot_nav_ent_move_to.cpp @@ -3,6 +3,7 @@ #include "neo_player.h" #include "bot/neo_bot.h" #include "bot/behavior/nav_entities/neo_bot_nav_ent_move_to.h" +#include "bot/neo_bot_path_compute.h" extern ConVar neo_bot_path_lookahead_range; @@ -90,8 +91,7 @@ ActionResult< CNEOBot > CNEOBotNavEntMoveTo::Update( CNEOBot *me, float interval { m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); - CNEOBotPathCost cost( me, FASTEST_ROUTE ); - m_path.Compute( me, m_goalPosition, cost ); + CNEOBotPathCompute( me, m_path, m_goalPosition, FASTEST_ROUTE ); } // move into position diff --git a/src/game/server/neo/bot/behavior/neo_bot_approach_object.cpp b/src/game/server/neo/bot/behavior/neo_bot_approach_object.cpp index 3831c0d6c6..a1282f4440 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_approach_object.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_approach_object.cpp @@ -5,6 +5,7 @@ #include "neo_player.h" #include "bot/neo_bot.h" #include "bot/behavior/neo_bot_approach_object.h" +#include "bot/neo_bot_path_compute.h" extern ConVar neo_bot_path_lookahead_range; @@ -53,8 +54,7 @@ ActionResult< CNEOBot > CNEOBotApproachObject::Update( CNEOBot *me, float interv { m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); - CNEOBotPathCost cost( me, FASTEST_ROUTE ); - m_path.Compute( me, m_loot->GetAbsOrigin(), cost ); + CNEOBotPathCompute( me, m_path, m_loot->GetAbsOrigin(), FASTEST_ROUTE ); } // move to the loot diff --git a/src/game/server/neo/bot/behavior/neo_bot_attack.cpp b/src/game/server/neo/bot/behavior/neo_bot_attack.cpp index be6c831fac..e2b47ad691 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_attack.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_attack.cpp @@ -4,6 +4,7 @@ #include "team_control_point_master.h" #include "bot/neo_bot.h" #include "bot/behavior/neo_bot_attack.h" +#include "bot/neo_bot_path_compute.h" #include "nav_mesh.h" @@ -80,13 +81,11 @@ ActionResult< CNEOBot > CNEOBotAttack::Update( CNEOBot *me, float interval ) if ( isUsingCloseRangeWeapon ) { - CNEOBotPathCost cost( me, FASTEST_ROUTE ); - m_chasePath.Update( me, threat->GetEntity(), cost ); + CNEOBotPathUpdateChase( me, m_chasePath, threat->GetEntity(), FASTEST_ROUTE ); } else { - CNEOBotPathCost cost( me, DEFAULT_ROUTE ); - m_chasePath.Update( me, threat->GetEntity(), cost ); + CNEOBotPathUpdateChase( me, m_chasePath, threat->GetEntity(), DEFAULT_ROUTE ); } } else @@ -115,13 +114,11 @@ ActionResult< CNEOBot > CNEOBotAttack::Update( CNEOBot *me, float interval ) if ( isUsingCloseRangeWeapon ) { - CNEOBotPathCost cost( me, FASTEST_ROUTE ); - m_path.Compute( me, threat->GetLastKnownPosition(), cost ); + CNEOBotPathCompute( me, m_path, threat->GetLastKnownPosition(), FASTEST_ROUTE ); } else { - CNEOBotPathCost cost( me, DEFAULT_ROUTE ); - m_path.Compute( me, threat->GetLastKnownPosition(), cost ); + CNEOBotPathCompute( me, m_path, threat->GetLastKnownPosition(), DEFAULT_ROUTE ); } } } diff --git a/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp b/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp index 22c5712ff2..4b44cf02d2 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp @@ -154,6 +154,8 @@ ActionResult< CNEOBot > CNEOBotMainAction::Update( CNEOBot *me, float interval ) me->m_flLastShouldAimTime = gpGlobals->curtime; } me->m_qPrevShouldAim = qDirectShouldAimQuery; + + me->RepathIfFriendlyBlockingLineOfFire(); return Continue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_get_ammo.cpp b/src/game/server/neo/bot/behavior/neo_bot_get_ammo.cpp index 310d5d0b18..2a07096112 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_get_ammo.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_get_ammo.cpp @@ -4,6 +4,7 @@ #include "neo_gamerules.h" #include "bot/neo_bot.h" #include "bot/behavior/neo_bot_get_ammo.h" +#include "bot/neo_bot_path_compute.h" #include "items.h" extern ConVar neo_bot_path_lookahead_range; @@ -116,9 +117,8 @@ bool CNEOBotGetAmmo::IsPossible( CNEOBot *me ) NDebugOverlay::Cross3D( closestAmmo->WorldSpaceCenter(), 5.0f, 255, 255, 0, true, 999.9 ); } - CNEOBotPathCost cost( me, FASTEST_ROUTE ); PathFollower path; - if ( !path.Compute( me, closestAmmo->WorldSpaceCenter(), cost ) || !path.IsValid() || path.GetResult() != Path::COMPLETE_PATH ) + if ( !CNEOBotComputePath( me, path, closestAmmo->WorldSpaceCenter(), FASTEST_ROUTE ) || !path.IsValid() || path.GetResult() != Path::COMPLETE_PATH ) { if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) ) { @@ -153,8 +153,7 @@ ActionResult< CNEOBot > CNEOBotGetAmmo::OnStart( CNEOBot *me, Action< CNEOBot > m_ammo = s_possibleAmmo; - CNEOBotPathCost cost( me, FASTEST_ROUTE ); - if ( !m_path.Compute( me, m_ammo->WorldSpaceCenter(), cost ) || !m_path.IsValid() || m_path.GetResult() != Path::COMPLETE_PATH ) + if ( !CNEOBotComputePath( me, m_path, m_ammo->WorldSpaceCenter(), FASTEST_ROUTE ) || !m_path.IsValid() || m_path.GetResult() != Path::COMPLETE_PATH ) { return Done( "No path to ammo!" ); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_get_health.cpp b/src/game/server/neo/bot/behavior/neo_bot_get_health.cpp index 52e9406c82..0b37e748bb 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_get_health.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_get_health.cpp @@ -3,6 +3,7 @@ #include "bot/neo_bot.h" //#include "item_healthkit.h" #include "bot/behavior/neo_bot_get_health.h" +#include "bot/neo_bot_path_compute.h" extern ConVar neo_bot_path_lookahead_range; extern ConVar neo_bot_health_critical_ratio; @@ -138,9 +139,8 @@ bool CNEOBotGetHealth::IsPossible( CNEOBot *me ) return false; } - CNEOBotPathCost cost( me, FASTEST_ROUTE ); PathFollower path; - if ( !path.Compute( me, closestHealth->WorldSpaceCenter(), cost ) || !path.IsValid() || path.GetResult() != Path::COMPLETE_PATH ) + if ( !CNEOBotPathCompute( me, path, closestHealth->WorldSpaceCenter(), FASTEST_ROUTE ) || !path.IsValid() || path.GetResult() != Path::COMPLETE_PATH ) { if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) ) { @@ -175,8 +175,7 @@ ActionResult< CNEOBot > CNEOBotGetHealth::OnStart( CNEOBot *me, Action< CNEOBot m_healthKit = s_possibleHealth; m_isGoalCharger = m_healthKit->ClassMatches( "*charger*" ); - CNEOBotPathCost cost( me, SAFEST_ROUTE ); - if ( !m_path.Compute( me, m_healthKit->WorldSpaceCenter(), cost ) ) + if (!CNEOBotPathCompute(me, m_path, m_healthKit->WorldSpaceCenter(), SAFEST_ROUTE)) { return Done( "No path to health!" ); } @@ -212,8 +211,7 @@ ActionResult< CNEOBot > CNEOBotGetHealth::Update( CNEOBot *me, float interval ) { // this can occur if we overshoot the health kit's location // because it is momentarily gone - CNEOBotPathCost cost( me, SAFEST_ROUTE ); - if ( !m_path.Compute( me, m_healthKit->WorldSpaceCenter(), cost ) ) + if ( !CNEOBotPathCompute( me, m_path, m_healthKit->WorldSpaceCenter(), SAFEST_ROUTE ) ) { return Done( "No path to health!" ); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_melee_attack.cpp b/src/game/server/neo/bot/behavior/neo_bot_melee_attack.cpp index e64da76982..7bc3eba9c5 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_melee_attack.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_melee_attack.cpp @@ -1,6 +1,7 @@ #include "cbase.h" #include "bot/neo_bot.h" #include "bot/behavior/neo_bot_melee_attack.h" +#include "bot/neo_bot_path_compute.h" #include "nav_mesh.h" @@ -59,8 +60,7 @@ ActionResult< CNEOBot > CNEOBotMeleeAttack::Update( CNEOBot *me, float interval me->PressFireButton(); // chase them down - CNEOBotPathCost cost( me, FASTEST_ROUTE ); - m_path.Update( me, threat->GetEntity(), cost ); + CNEOBotPathUpdateChase( me, m_path, threat->GetEntity(), FASTEST_ROUTE ); return Continue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_move_to_vantage_point.cpp b/src/game/server/neo/bot/behavior/neo_bot_move_to_vantage_point.cpp index 27d247c554..800dc7931a 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_move_to_vantage_point.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_move_to_vantage_point.cpp @@ -2,6 +2,7 @@ #include "neo_player.h" #include "bot/neo_bot.h" #include "bot/behavior/neo_bot_move_to_vantage_point.h" +#include "bot/neo_bot_path_compute.h" #include "nav_mesh.h" @@ -46,8 +47,7 @@ ActionResult< CNEOBot > CNEOBotMoveToVantagePoint::Update( CNEOBot *me, float in { m_repathTimer.Start( 1.0f ); - CNEOBotPathCost cost( me, FASTEST_ROUTE ); - if ( !m_path.Compute( me, m_vantageArea->GetCenter(), cost ) ) + if ( !CNEOBotPathCompute( me, m_path, m_vantageArea->GetCenter(), FASTEST_ROUTE ) ) { return Done( "No path to vantage point exists" ); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp b/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp index 48d6104a6c..e3e55fcf59 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp @@ -4,6 +4,7 @@ #include "neo_player.h" #include "bot/neo_bot.h" #include "bot/behavior/neo_bot_retreat_to_cover.h" +#include "bot/neo_bot_path_compute.h" extern ConVar neo_bot_path_lookahead_range; ConVar neo_bot_retreat_to_cover_range( "neo_bot_retreat_to_cover_range", "1000", FCVAR_CHEAT ); @@ -235,8 +236,7 @@ ActionResult< CNEOBot > CNEOBotRetreatToCover::Update( CNEOBot *me, float interv { m_repathTimer.Start( RandomFloat( 0.3f, 0.5f ) ); - CNEOBotPathCost cost( me, RETREAT_ROUTE ); - m_path.Compute( me, m_coverArea->GetCenter(), cost ); + CNEOBotPathCompute( me, m_path, m_coverArea->GetCenter(), RETREAT_ROUTE ); } m_path.Update( me ); 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 7eacf7c24b..a30d954344 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 @@ -5,6 +5,7 @@ #include "bot/neo_bot.h" #include "bot/behavior/neo_bot_attack.h" #include "bot/behavior/neo_bot_seek_and_destroy.h" +#include "bot/neo_bot_path_compute.h" #include "nav_mesh.h" #include "neo_ghost_cap_point.h" @@ -365,11 +366,10 @@ void CNEOBotSeekAndDestroy::RecomputeSeekPath( CNEOBot *me ) CBaseEntity* pClosestWeapon = pWeapons[i]; if ( pClosestWeapon ) { - CNEOBotPathCost cost( me, SAFEST_ROUTE ); m_hTargetEntity = pClosestWeapon; m_bGoingToTargetEntity = true; m_vGoalPos = pClosestWeapon->WorldSpaceCenter(); - if ( m_path.Compute( me, m_vGoalPos, cost, 0.0f, true, true ) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH ) + if ( CNEOBotPathCompute( me, m_path, m_vGoalPos, SAFEST_ROUTE ) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH ) return; } } @@ -482,8 +482,7 @@ void CNEOBotSeekAndDestroy::RecomputeSeekPath( CNEOBot *me ) } } m_bGoingToTargetEntity = true; - CNEOBotPathCost cost(me, bQuickToGoalPos ? FASTEST_ROUTE : SAFEST_ROUTE); - if (m_path.Compute(me, m_vGoalPos, cost, 0.0f, true, true) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH) + if ( CNEOBotPathCompute( me, m_path, m_vGoalPos, bQuickToGoalPos ? FASTEST_ROUTE : SAFEST_ROUTE ) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH ) { return; } @@ -508,11 +507,10 @@ void CNEOBotSeekAndDestroy::RecomputeSeekPath( CNEOBot *me ) { for ( int i = 0; i < 10; i++ ) { - CNEOBotPathCost cost( me, SAFEST_ROUTE ); m_hTargetEntity = pSpawns[RandomInt( 0, pSpawns.Size() - 1 )]; m_bGoingToTargetEntity = true; m_vGoalPos = m_hTargetEntity->WorldSpaceCenter(); - if ( m_path.Compute( me, m_vGoalPos, cost, 0.0f, true, true ) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH ) + if ( CNEOBotPathCompute( me, m_path, m_vGoalPos, SAFEST_ROUTE ) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH ) return; } } @@ -522,10 +520,9 @@ void CNEOBotSeekAndDestroy::RecomputeSeekPath( CNEOBot *me ) { // No spawns we can get to? Just wander... somewhere! - CNEOBotPathCost cost( me, SAFEST_ROUTE ); Vector vWanderPoint = TheNavAreas[RandomInt( 0, TheNavAreas.Size() - 1 )]->GetCenter(); m_vGoalPos = vWanderPoint; - if ( m_path.Compute( me, vWanderPoint, cost ) ) + if ( CNEOBotPathCompute( me, m_path, vWanderPoint, SAFEST_ROUTE ) ) return; } @@ -560,8 +557,7 @@ EventDesiredResult< CNEOBot > CNEOBotSeekAndDestroy::OnCommandApproach( CNEOBot* m_bOverrideApproach = true; m_vOverrideApproach = pos; - CNEOBotPathCost cost( me, SAFEST_ROUTE ); - m_path.Compute( me, m_vOverrideApproach, cost ); + CNEOBotPathCompute( me, m_path, m_vOverrideApproach, SAFEST_ROUTE ); return TryContinue(); } diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index bc99244514..8390df1cf8 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -38,6 +38,7 @@ extern ConVar neo_bot_fire_weapon_min_time; extern ConVar neo_bot_difficulty; extern ConVar neo_bot_farthest_visible_theater_sample_count; extern ConVar neo_bot_path_lookahead_range; +extern ConVar neo_bot_path_around_friendly_cooldown; @@ -527,6 +528,7 @@ CNEOBot::CNEOBot() m_behaviorFlags = 0; m_attentionFocusEntity = NULL; m_noisyTimer.Invalidate(); + m_repathAroundFriendlyTimer.Invalidate(); m_difficulty = clamp((CNEOBot::DifficultyType)neo_bot_difficulty.GetInt(), CNEOBot::EASY, CNEOBot::EXPERT); @@ -1828,6 +1830,32 @@ bool CNEOBot::IsLineOfFireClearOfFriendlies(const Vector& from, CBaseEntity* who return true; } + +//----------------------------------------------------------------------------------------------------- +// Return whether there is a friendly player blocking the line of fire +bool CNEOBot::IsLineOfFireClearOfFriendlies(const Vector& from, const Vector& to) const +{ + if (NEORules()->IsTeamplay()) + { + trace_t playerTrace; + CTraceFilterSimple playerFilter(this, COLLISION_GROUP_NONE); + UTIL_TraceLine(from, to, MASK_SHOT_HULL, &playerFilter, &playerTrace); + + if (playerTrace.DidHit()) + { + CBaseEntity *pEnt = playerTrace.m_pEnt; + if (pEnt && pEnt->IsPlayer()) + { + if (IsFriend(pEnt) && pEnt != this) + { + return false; + } + } + } + } + return true; +} + //----------------------------------------------------------------------------------------------------- // Return true if a weapon has no obstructions along the line between the given point and entity bool CNEOBot::IsLineOfFireClear(const Vector& from, CBaseEntity* who, const LineOfFireFlags flags) const @@ -1878,6 +1906,47 @@ bool CNEOBot::IsLineOfFireClear(CBaseEntity* who, const LineOfFireFlags flags) c } +//----------------------------------------------------------------------------------------------------- +// If I am aiming at a friendly, recalculate my path to get around them +void CNEOBot::RepathIfFriendlyBlockingLineOfFire() +{ + if (!IsAlive()) + { + return; + } + + if (!m_repathAroundFriendlyTimer.IsElapsed()) + { + return; + } + + float jitter = RandomFloat(0.0f, 0.5f); + m_repathAroundFriendlyTimer.Start(neo_bot_path_around_friendly_cooldown.GetFloat() + jitter); + + Vector eyePos = EyePosition(); + Vector forward; + EyeVectors(&forward); + + const float checkDistance = 10000.0f; + Vector targetPos = eyePos + forward * checkDistance; + + if (!IsLineOfFireClearOfFriendlies(eyePos, targetPos)) + { + const PathFollower* pPath = GetCurrentPath(); + Vector goal = pPath->GetEndPosition(); + + CNEOBotPathCost cost(this, SAFEST_ROUTE); + if (m_repathAroundFriendlyFollower.Compute(this, goal, cost)) + { + if (m_repathAroundFriendlyFollower.IsValid()) + { + SetCurrentPath(&m_repathAroundFriendlyFollower); + } + } + } +} + + //----------------------------------------------------------------------------------------------------- bool CNEOBot::IsEntityBetweenTargetAndSelf(CBaseEntity* other, CBaseEntity* target) { diff --git a/src/game/server/neo/bot/neo_bot.h b/src/game/server/neo/bot/neo_bot.h index da6a45066f..668a73ab50 100644 --- a/src/game/server/neo/bot/neo_bot.h +++ b/src/game/server/neo/bot/neo_bot.h @@ -5,6 +5,7 @@ #include "neo_bot_vision.h" #include "neo_bot_body.h" #include "neo_bot_locomotion.h" +#include "neo_bot_path_cost.h" #include "neo_player.h" #include "neo_bot_squad.h" #include "map_entities/neo_bot_generator.h" @@ -213,6 +214,8 @@ class CNEOBot : public NextBotPlayer< CNEO_Player >, public CGameEventListener bool IsLineOfFireClear(const Vector& from, const Vector& to, const LineOfFireFlags flags) const; // return true if a weapon has no obstructions along the line between the given points bool IsLineOfFireClear(const Vector& from, CBaseEntity* who, const LineOfFireFlags flags) const; // return true if a weapon has no obstructions along the line between the given point and entity bool IsLineOfFireClearOfFriendlies(const Vector& from, CBaseEntity* who) const; + bool IsLineOfFireClearOfFriendlies(const Vector& from, const Vector& to) const; + void RepathIfFriendlyBlockingLineOfFire(); bool IsEntityBetweenTargetAndSelf(CBaseEntity* other, CBaseEntity* target); // return true if "other" is positioned inbetween us and "target" @@ -501,6 +504,9 @@ class CNEOBot : public NextBotPlayer< CNEO_Player >, public CGameEventListener float m_flAutoJumpMax; CountdownTimer m_autoJumpTimer; + CountdownTimer m_repathAroundFriendlyTimer; + PathFollower m_repathAroundFriendlyFollower; + float m_flPhyscannonPickupTime = 0.0f; CUtlVector< const EventChangeAttributes_t* > m_eventChangeAttributes; @@ -800,121 +806,6 @@ inline const CNEOBot* ToNEOBot(const CBaseEntity* pEntity) return static_cast(pEntity); } -//-------------------------------------------------------------------------------------------------------------- -/** - * Functor used with NavAreaBuildPath() - */ -class CNEOBotPathCost : public IPathCost -{ -public: - CNEOBotPathCost(CNEOBot* me, RouteType routeType) - { - m_me = me; - m_routeType = routeType; - m_stepHeight = me->GetLocomotionInterface()->GetStepHeight(); - m_maxJumpHeight = me->GetLocomotionInterface()->GetMaxJumpHeight(); - m_maxDropHeight = me->GetLocomotionInterface()->GetDeathDropHeight(); - } - - virtual float operator()(CNavArea* baseArea, CNavArea* fromArea, const CNavLadder* ladder, const CFuncElevator* elevator, float length) const - { - VPROF_BUDGET("CNEOBotPathCost::operator()", "NextBot"); - - CNavArea* area = (CNavArea*)baseArea; - - if (fromArea == NULL) - { - // first area in path, no cost - return 0.0f; - } - else - { - if (!m_me->GetLocomotionInterface()->IsAreaTraversable(area)) - { - return -1.0f; - } - - // compute distance traveled along path so far - float dist; - - if (ladder) - { - dist = ladder->m_length; - } - else if (length > 0.0) - { - dist = length; - } - else - { - dist = (area->GetCenter() - fromArea->GetCenter()).Length(); - } - - - // check height change - float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange(area); - - if (deltaZ >= m_stepHeight) - { - if (deltaZ >= m_maxJumpHeight) - { - // too high to reach - return -1.0f; - } - - // jumping is slower than flat ground - const float jumpPenalty = 2.0f; - 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 - float preference = 1.0f; - - if (m_routeType == DEFAULT_ROUTE) - { - // this term causes the same bot to choose different routes over time, - // but keep the same route for a period in case of repaths - int timeMod = (int)(gpGlobals->curtime / 10.0f) + 1; - preference = 1.0f + 50.0f * (1.0f + FastCos((float)(m_me->GetEntity()->entindex() * area->GetID() * timeMod))); - } - - if (m_routeType == SAFEST_ROUTE) - { - // misyl: combat areas. -#if 0 - // avoid combat areas - if (area->IsInCombat()) - { - const float combatDangerCost = 4.0f; - dist *= combatDangerCost * area->GetCombatIntensity(); - } -#endif - } - - float cost = (dist * preference); - - if (area->HasAttributes(NAV_MESH_FUNC_COST)) - { - cost *= area->ComputeFuncNavCost(m_me); - DebuggerBreakOnNaN_StagingOnly(cost); - } - - return cost + fromArea->GetCostSoFar(); - } - } - - CNEOBot* m_me; - RouteType m_routeType; - float m_stepHeight; - float m_maxJumpHeight; - float m_maxDropHeight; -}; - //--------------------------------------------------------------------------------------------- class CClosestNEOPlayer diff --git a/src/game/server/neo/bot/neo_bot_manager.cpp b/src/game/server/neo/bot/neo_bot_manager.cpp index ec9d8a53a1..a68421d572 100644 --- a/src/game/server/neo/bot/neo_bot_manager.cpp +++ b/src/game/server/neo/bot/neo_bot_manager.cpp @@ -5,6 +5,7 @@ #include "team.h" #include "neo_bot.h" #include "neo_gamerules.h" +#include "neo_bot_path_reservation.h" //---------------------------------------------------------------------------------------------------------------- @@ -111,6 +112,8 @@ void CNEOBotManager::OnMapLoaded( void ) NEOBotProfileResetPicks(); ClearStuckBotData(); + + CNEOBotPathReservations()->Clear(); } @@ -526,7 +529,9 @@ void CNEOBotManager::LevelShutdown() { RevertOfflinePracticeConvars(); SetIsInOfflinePractice( false ); - } + } + + CNEOBotPathReservations()->Clear(); } diff --git a/src/game/server/neo/bot/neo_bot_path_compute.cpp b/src/game/server/neo/bot/neo_bot_path_compute.cpp new file mode 100644 index 0000000000..3d0fb36069 --- /dev/null +++ b/src/game/server/neo/bot/neo_bot_path_compute.cpp @@ -0,0 +1,82 @@ +#include "cbase.h" +#include "bot/neo_bot.h" +#include "neo_bot_path_compute.h" +#include "bot/neo_bot_path_reservation.h" +#include "NextBot/Path/NextBotPathFollow.h" +#include "NextBot/Path/NextBotChasePath.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern ConVar neo_bot_path_reservation_enabled; +extern ConVar neo_bot_path_reservation_penalty; +extern ConVar neo_bot_path_reservation_duration; +extern ConVar neo_bot_path_reservation_distance; + +static void CNEOBotReservePath(CNEOBot* me, PathFollower& path) +{ + if (!neo_bot_path_reservation_enabled.GetBool() || !path.IsValid()) + { + return; + } + + CNEOBotPathReservations()->ReleaseAllAreas(me); + + const float reservation_distance = neo_bot_path_reservation_distance.GetFloat(); + const float reservation_duration = neo_bot_path_reservation_duration.GetFloat(); + + for (const Path::Segment* seg = path.FirstSegment(); seg; seg = path.NextSegment(seg)) + { + if (seg->distanceFromStart > reservation_distance) + { + break; + } + + if (seg->area) + { + CNEOBotPathReservations()->ReserveArea(seg->area, me, reservation_duration); + } + } +} + +bool CNEOBotPathCompute(CNEOBot* bot, PathFollower& path, const Vector& goal, RouteType route, float maxPathLength, bool includeGoalIfPathFails, bool requireGoalArea) +{ + CNEOBotPathCost cost_with_reservations(bot, route); + if (path.Compute(bot, goal, cost_with_reservations, maxPathLength, includeGoalIfPathFails, requireGoalArea) && path.IsValid()) + { + CNEOBotReservePath(bot, path); + return true; + } + + CNEOBotPathCost cost_without_reservations(bot, route); + cost_without_reservations.m_bIgnoreReservations = true; + if (path.Compute(bot, goal, cost_without_reservations, maxPathLength, includeGoalIfPathFails, requireGoalArea) && path.IsValid()) + { + CNEOBotReservePath(bot, path); + return true; + } + + return false; +} + +bool CNEOBotPathUpdateChase(CNEOBot* bot, ChasePath& path, CBaseEntity* subject, RouteType route, Vector* pPredictedSubjectPos) +{ + CNEOBotPathCost cost_with_reservations(bot, route); + path.Update(bot, subject, cost_with_reservations, pPredictedSubjectPos); + if (path.IsValid()) + { + CNEOBotReservePath(bot, path); + return true; + } + + CNEOBotPathCost cost_without_reservations(bot, route); + cost_without_reservations.m_bIgnoreReservations = true; + path.Update(bot, subject, cost_without_reservations, pPredictedSubjectPos); + if (path.IsValid()) + { + CNEOBotReservePath(bot, path); + return true; + } + + return false; +} diff --git a/src/game/server/neo/bot/neo_bot_path_compute.h b/src/game/server/neo/bot/neo_bot_path_compute.h new file mode 100644 index 0000000000..bf9ab2f571 --- /dev/null +++ b/src/game/server/neo/bot/neo_bot_path_compute.h @@ -0,0 +1,28 @@ +#pragma once + +#include "nav_pathfind.h" + +class CNEOBot; +class PathFollower; +class ChasePath; +class CBaseEntity; + +bool CNEOBotPathCompute +( + CNEOBot* bot, + PathFollower& path, + const Vector& goal, + RouteType route, + float maxPathLength = PATH_NO_LENGTH_LIMIT, + bool includeGoalIfPathFails = PATH_TRUNCATE_INCOMPLETE_PATH, + bool requireGoalArea = false +); + +bool CNEOBotPathUpdateChase +( + CNEOBot *bot, + ChasePath &path, + CBaseEntity *subject, + RouteType route, + Vector *pPredictedSubjectPos = NULL +); diff --git a/src/game/server/neo/bot/neo_bot_path_cost.cpp b/src/game/server/neo/bot/neo_bot_path_cost.cpp new file mode 100644 index 0000000000..d31a249112 --- /dev/null +++ b/src/game/server/neo/bot/neo_bot_path_cost.cpp @@ -0,0 +1,128 @@ +#include "cbase.h" +#include "neo_bot.h" +#include "neo_bot_path_cost.h" +#include "neo_gamerules.h" +#include "neo_bot_locomotion.h" +#include "nav_mesh.h" +#include "neo_bot_path_reservation.h" + +ConVar neo_bot_path_friendly_reservation_enable("neo_bot_path_friendly_reservation_enable", "1", FCVAR_NONE, + "Enable friendly bot path dispersal", true, 0, false, 1); + +ConVar neo_bot_path_around_friendly_cooldown("neo_bot_path_around_friendly_cooldown", "2.0", FCVAR_CHEAT, + "How often to check for friendly path dispersion", false, 0, false, 60); + +//------------------------------------------------------------------------------------------------- +CNEOBotPathCost::CNEOBotPathCost(CNEOBot* me, RouteType routeType) +{ + m_me = me; + m_routeType = routeType; + m_stepHeight = me->GetLocomotionInterface()->GetStepHeight(); + m_maxJumpHeight = me->GetLocomotionInterface()->GetMaxJumpHeight(); + m_maxDropHeight = me->GetLocomotionInterface()->GetDeathDropHeight(); + m_bIgnoreReservations = !neo_bot_path_friendly_reservation_enable.GetBool(); +} + +//------------------------------------------------------------------------------------------------- +float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const CNavLadder* ladder, const CFuncElevator* elevator, float length) const +{ + VPROF_BUDGET("CNEOBotPathCost::operator()", "NextBot"); + + CNavArea* area = (CNavArea*)baseArea; + + if (fromArea == NULL) + { + // first area in path, no cost + return 0.0f; + } + else + { + if (!m_me->GetLocomotionInterface()->IsAreaTraversable(area)) + { + return -1.0f; + } + + // compute distance traveled along path so far + float dist; + + if (ladder) + { + dist = ladder->m_length; + } + else if (length > 0.0) + { + dist = length; + } + else + { + dist = (area->GetCenter() - fromArea->GetCenter()).Length(); + } + + + // check height change + float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange(area); + + if (deltaZ >= m_stepHeight) + { + if (deltaZ >= m_maxJumpHeight) + { + // too high to reach + return -1.0f; + } + + // jumping is slower than flat ground + const float jumpPenalty = 2.0f; + 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 + float preference = 1.0f; + + if (m_routeType == DEFAULT_ROUTE) + { + // this term causes the same bot to choose different routes over time, + // but keep the same route for a period in case of repaths + int timeMod = (int)(gpGlobals->curtime / 10.0f) + 1; + preference = 1.0f + 50.0f * (1.0f + FastCos((float)(m_me->GetEntity()->entindex() * area->GetID() * timeMod))); + } + + if (m_routeType == SAFEST_ROUTE) + { + // misyl: combat areas. +#if 0 + // avoid combat areas + if (area->IsInCombat()) + { + const float combatDangerCost = 4.0f; + dist *= combatDangerCost * area->GetCombatIntensity(); + } +#endif + } + + + float cost = (dist * preference); + + // ------------------------------------------------------------------------------------------------ + // New path reservation related cost adjustments + if ( neo_bot_path_friendly_reservation_enable.GetBool() + && !m_bIgnoreReservations + && (m_routeType != FASTEST_ROUTE) ) + { + cost += CNEOBotPathReservations()->GetPredictedFriendlyPathCount(area->GetID(), m_me->GetTeamNumber()) * neo_bot_path_reservation_penalty.GetFloat(); + } + // ------------------------------------------------------------------------------------------------ + + if (area->HasAttributes(NAV_MESH_FUNC_COST)) + { + cost *= area->ComputeFuncNavCost(m_me); + DebuggerBreakOnNaN_StagingOnly(cost); + } + + return cost + fromArea->GetCostSoFar(); + } +} diff --git a/src/game/server/neo/bot/neo_bot_path_cost.h b/src/game/server/neo/bot/neo_bot_path_cost.h new file mode 100644 index 0000000000..55ce34b43e --- /dev/null +++ b/src/game/server/neo/bot/neo_bot_path_cost.h @@ -0,0 +1,34 @@ +#pragma once + +#include "NextBot/Path/NextBotPath.h" +#include "nav_pathfind.h" +#include "tier1/utlmap.h" + +class CNEOBot; +class CNavArea; +class CNavLadder; +class CFuncElevator; + +#include "neo_bot_path_reservation.h" +#include "tier1/utlstack.h" + +//---------------------------------------------------------------------------------------------------------------- +/** + * Functor used with NavAreaBuildPath() + */ +class CNEOBotPathCost : public IPathCost +{ +public: + CNEOBotPathCost(CNEOBot* me, RouteType routeType); + + virtual float operator()(CNavArea* baseArea, CNavArea* fromArea, const CNavLadder* ladder, const CFuncElevator* elevator, float length) const override; + + bool m_bIgnoreReservations; + +private: + CNEOBot* m_me; + RouteType m_routeType; + float m_stepHeight; + float m_maxJumpHeight; + float m_maxDropHeight; +}; diff --git a/src/game/server/neo/bot/neo_bot_path_reservation.cpp b/src/game/server/neo/bot/neo_bot_path_reservation.cpp new file mode 100644 index 0000000000..7ce7086c4d --- /dev/null +++ b/src/game/server/neo/bot/neo_bot_path_reservation.cpp @@ -0,0 +1,288 @@ +#include "cbase.h" +#include "neo/bot/neo_bot_path_reservation.h" +#include "neo/bot/neo_bot.h" +#include "nav_mesh.h" +#include "neo_gamerules.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar neo_bot_path_reservation_enabled("neo_bot_path_reservation_enabled", "1", FCVAR_NONE, "Enable the bot path reservation system."); +ConVar neo_bot_path_reservation_penalty("neo_bot_path_reservation_penalty", "100", FCVAR_NONE, "Pathing cost penalty for a reserved area."); +ConVar neo_bot_path_reservation_duration("neo_bot_path_reservation_duration", "30.0", FCVAR_NONE, "How long a path reservation lasts, in seconds."); +ConVar neo_bot_path_reservation_distance("neo_bot_path_reservation_distance", "10000", FCVAR_NONE, "How far along the path to reserve, in Hammer units."); + + +CNEOBotPathReservationSystem* CNEOBotPathReservations() +{ + static CNEOBotPathReservationSystem g_BotPathReservations; + return &g_BotPathReservations; +} + +/** + * Reserve a navigation area for a specific bot for a given duration. + */ +void CNEOBotPathReservationSystem::ReserveArea(CNavArea *area, CNEOBot *bot, float duration) +{ + if (!area || !bot) + { + return; + } + + if (!NEORules()->GetTeamPlayEnabled()) + { + return; + } + + int team = bot->GetTeamNumber(); + if (team < 0 || team >= MAX_TEAMS) + { + return; + } + + int areaID = area->GetID(); + int reservationIndex = m_Reservations[team].Find(areaID); + + if (reservationIndex == m_Reservations[team].InvalidIndex()) + { + reservationIndex = m_Reservations[team].Insert(areaID); + } + else + { + // If the area is already reserved by this bot, and the reservation is not expired, + // we should decrement the count before updating the reservation to avoid double counting. + // This handles cases where a bot re-reserves an area it already owns. + const ReservationInfo &existingInfo = m_Reservations[team].Element(reservationIndex); + CNEOBot *existingOwner = ToNEOBot(existingInfo.hOwner.Get()); + if (existingOwner == bot && existingInfo.flExpirationTime >= gpGlobals->curtime) + { + DecrementPredictedFriendlyPathCount(area->GetID(), team); + } + } + + m_Reservations[team][reservationIndex].hOwner = bot; + m_Reservations[team][reservationIndex].flExpirationTime = gpGlobals->curtime + duration; + IncrementPredictedFriendlyPathCount(area->GetID(), team); + + // Add to bot's reserved areas list + EHANDLE hBot = bot; + int botReservedIndex = m_BotReservedAreas.Find(hBot); + if (botReservedIndex == m_BotReservedAreas.InvalidIndex()) + { + m_BotReservedAreas.Insert(hBot, BotReservedAreas_t()); + botReservedIndex = m_BotReservedAreas.Find(hBot); + } + + if (m_BotReservedAreas.Element(botReservedIndex).areas.Find(area) == -1) + { + m_BotReservedAreas.Element(botReservedIndex).areas.AddToTail(area); + } +} + +/** + * Release a navigation area reservation. + */ +void CNEOBotPathReservationSystem::ReleaseArea(CNavArea *area, CNEOBot *bot) +{ + if (!area || !bot) + { + return; + } + + int team = bot->GetTeamNumber(); + if (team < 0 || team >= MAX_TEAMS) + { + return; + } + + int areaID = area->GetID(); + int reservationIndex = m_Reservations[team].Find(areaID); + + if (reservationIndex == m_Reservations[team].InvalidIndex()) + { + return; // No reservation for this area + } + + const ReservationInfo &info = m_Reservations[team].Element(reservationIndex); + CNEOBot *owner = ToNEOBot(info.hOwner.Get()); + + // Only remove if this bot is the current owner and the reservation hasn't expired naturally. + // If it has expired, or another bot has taken ownership, we shouldn't decrement the count or remove the entry. + if (owner == bot && info.flExpirationTime >= gpGlobals->curtime) + { + DecrementPredictedFriendlyPathCount(area->GetID(), team); + m_Reservations[team].RemoveAt(reservationIndex); + } + + // Remove from bot's reserved areas list + EHANDLE hBot = bot; + int botReservedIndex = m_BotReservedAreas.Find(hBot); + if (botReservedIndex != m_BotReservedAreas.InvalidIndex()) + { + m_BotReservedAreas.Element(botReservedIndex).areas.FindAndRemove(area); + } +} + + +//------------------------------------------------------------------------------------------------- +/** + * Release all navigation area reservations for a specific bot. + */ +void CNEOBotPathReservationSystem::ReleaseAllAreas(CNEOBot *bot) +{ + if (!bot) + { + return; + } + + int team = bot->GetTeamNumber(); + if (team < 0 || team >= MAX_TEAMS) + { + return; + } + + EHANDLE hBot = bot; + int botReservedIndex = m_BotReservedAreas.Find(hBot); + + if (botReservedIndex == m_BotReservedAreas.InvalidIndex()) + { + return; // No reservations for this bot + } + + BotReservedAreas_t &botReservedAreas = m_BotReservedAreas.Element(botReservedIndex); + for (int i = 0; i < botReservedAreas.areas.Count(); ++i) + { + CNavArea *area = botReservedAreas.areas[i]; + if (area) + { + int areaID = area->GetID(); + int reservationIndex = m_Reservations[team].Find(areaID); + if (reservationIndex != m_Reservations[team].InvalidIndex()) + { + const ReservationInfo &info = m_Reservations[team].Element(reservationIndex); + if (ToNEOBot(info.hOwner.Get()) == bot) + { + DecrementPredictedFriendlyPathCount(area->GetID(), team); + m_Reservations[team].RemoveAt(reservationIndex); + } + } + } + } + m_BotReservedAreas.RemoveAt(botReservedIndex); +} + +//------------------------------------------------------------------------------------------------- +/** + * Check if a navigation area is currently reserved by a teammate of the bot avoiding friendlies. + */ +bool CNEOBotPathReservationSystem::IsAreaReservedByTeammate(CNavArea *area, CNEOBot *avoider) const +{ + if (!area || !avoider) + { + return false; + } + + if (!NEORules()->GetTeamPlayEnabled()) + { + return false; + } + + int team = avoider->GetTeamNumber(); + if (team < 0 || team >= MAX_TEAMS) + { + return false; + } + + int areaID = area->GetID(); + int reservationIndex = m_Reservations[team].Find(areaID); + + if (reservationIndex == m_Reservations[team].InvalidIndex()) + { + return false; + } + + const ReservationInfo &info = m_Reservations[team].Element(reservationIndex); + + if (info.flExpirationTime < gpGlobals->curtime) + { + return false; + } + + CNEOBot *owner = ToNEOBot(info.hOwner.Get()); + + if (owner && owner != avoider) + { + return true; + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Clear all current path reservations. + */ +void CNEOBotPathReservationSystem::Clear() +{ + for (int team = 0; team < MAX_TEAMS; ++team) + { + m_Reservations[team].RemoveAll(); + m_AreaPathCounts[team].RemoveAll(); + } + m_BotReservedAreas.RemoveAll(); +} + +//------------------------------------------------------------------------------------------------- +void CNEOBotPathReservationSystem::IncrementPredictedFriendlyPathCount( int areaID, int teamID ) +{ + if (teamID < 0 || teamID >= MAX_TEAMS) + { + return; + } + + int i = m_AreaPathCounts[teamID].Find( areaID ); + if ( m_AreaPathCounts[teamID].IsValidIndex( i ) ) + { + m_AreaPathCounts[teamID][i]++; + } + else + { + m_AreaPathCounts[teamID].Insert( areaID, 1 ); + } +} + +//------------------------------------------------------------------------------------------------- +void CNEOBotPathReservationSystem::DecrementPredictedFriendlyPathCount( int areaID, int teamID ) +{ + if (teamID < 0 || teamID >= MAX_TEAMS) + { + return; + } + + int i = m_AreaPathCounts[teamID].Find( areaID ); + if ( m_AreaPathCounts[teamID].IsValidIndex( i ) ) + { + m_AreaPathCounts[teamID][i]--; + if ( m_AreaPathCounts[teamID][i] <= 0 ) + { + m_AreaPathCounts[teamID].RemoveAt( i ); + } + } +} + +//------------------------------------------------------------------------------------------------- +int CNEOBotPathReservationSystem::GetPredictedFriendlyPathCount( int areaID, int teamID ) const +{ + if (teamID < 0 || teamID >= MAX_TEAMS) + { + return 0; + } + + int i = m_AreaPathCounts[teamID].Find( areaID ); + if ( m_AreaPathCounts[teamID].IsValidIndex( i ) ) + { + return m_AreaPathCounts[teamID][i]; + } + + return 0; +} diff --git a/src/game/server/neo/bot/neo_bot_path_reservation.h b/src/game/server/neo/bot/neo_bot_path_reservation.h new file mode 100644 index 0000000000..709763a888 --- /dev/null +++ b/src/game/server/neo/bot/neo_bot_path_reservation.h @@ -0,0 +1,79 @@ +#pragma once + +#include "cbase.h" +#include "utlmap.h" +#include "nav_area.h" +#include "neo_player_shared.h" + +class CNEOBot; + +struct ReservationInfo +{ + EHANDLE hOwner; // The bot that reserved this area + float flExpirationTime; // When the reservation expires (gpGlobals->curtime) +}; + +struct BotReservedAreas_t +{ + CUtlVector areas; + + BotReservedAreas_t() {} + BotReservedAreas_t(const BotReservedAreas_t& src) { areas = src.areas; } + BotReservedAreas_t& operator=(const BotReservedAreas_t& src) { areas = src.areas; return *this; } +}; + +//---------------------------------------------------------------------------------------------------- +/** + * This singleton allows bots to temporarily "claim" navigation areas along their intended path, + * discouraging other bots from using the same route simultaneously. + */ +class CNEOBotPathReservationSystem +{ +public: + // Need to define a less function for the CUtlMap + static bool ReservationLessFunc(const int &lhs, const int &rhs) + { + return lhs < rhs; + } + + // Less function for EHANDLE in m_BotReservedAreas + inline static bool EHandleLessFunc(const EHANDLE &lhs, const EHANDLE &rhs) + { + return lhs.GetSerialNumber() < rhs.GetSerialNumber(); + } + + CNEOBotPathReservationSystem() : m_BotReservedAreas(EHandleLessFunc) + { + for (int i = 0; i < MAX_TEAMS; ++i) + { + m_Reservations[i].SetLessFunc(ReservationLessFunc); + m_AreaPathCounts[i].SetLessFunc(ReservationLessFunc); + } + } + + void ReserveArea(CNavArea *area, CNEOBot *bot, float duration); + void ReleaseArea(CNavArea *area, CNEOBot *bot); + bool IsAreaReservedByTeammate(CNavArea *area, CNEOBot *avoider) const; + void Clear(); + void ReleaseAllAreas(CNEOBot *bot); + + void IncrementPredictedFriendlyPathCount( int areaID, int teamID ); + void DecrementPredictedFriendlyPathCount( int areaID, int teamID ); + int GetPredictedFriendlyPathCount( int areaID, int teamID ) const; + + // Allow the global accessor to access private members if needed, though constructor handles init now. + friend CNEOBotPathReservationSystem* CNEOBotPathReservations(); + +private: + CUtlMap m_Reservations[MAX_TEAMS]; + CUtlMap m_BotReservedAreas; + CUtlMap m_AreaPathCounts[MAX_TEAMS]; +}; + + +CNEOBotPathReservationSystem* CNEOBotPathReservations(); + +extern ConVar neo_bot_path_reservation_enabled; +extern ConVar neo_bot_path_reservation_penalty; +extern ConVar neo_bot_path_reservation_duration; +extern ConVar neo_bot_path_reservation_distance;