From 248e4a289748606f8dab22479d1ab77bb9a83f96 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Sat, 12 Sep 2026 21:03:47 -0600 Subject: [PATCH] Bot path reservation dispersal improvements - fixed bots using the same serial prefix as a reservation index (the reservation system was treating all teammates as the same individual) - clear reservations on death - penalize excessively long detours --- .../neo/bot/behavior/neo_bot_attack.cpp | 13 +- .../server/neo/bot/behavior/neo_bot_dead.cpp | 2 + src/game/server/neo/bot/neo_bot.cpp | 1 + .../server/neo/bot/neo_bot_path_compute.cpp | 5 - src/game/server/neo/bot/neo_bot_path_cost.cpp | 37 ++- src/game/server/neo/bot/neo_bot_path_cost.h | 1 + .../neo/bot/neo_bot_path_reservation.cpp | 266 ++++++------------ .../server/neo/bot/neo_bot_path_reservation.h | 52 ++-- 8 files changed, 148 insertions(+), 229 deletions(-) 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 4ea2cfc265..0353094a36 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_attack.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_attack.cpp @@ -84,13 +84,14 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor // only consider this new candidate area if it's an improvement // as we assume earlier breadth first search nodes are closer to bot // and thus faster to reach for safety. - if ( neo_bot_path_reservation_enable.GetBool() ) + const int nFriendlyHere = CNEOBotPathReservations()->GetPredictedFriendlyPathCount( area->GetID(), m_me->GetTeamNumber(), m_me ); + const int nFriendlyBest = CNEOBotPathReservations()->GetPredictedFriendlyPathCount( m_attackCoverArea->GetID(), m_me->GetTeamNumber(), m_me ); + if ( nFriendlyHere != nFriendlyBest ) { // prefer areas that friendly bots have reserved relatively less - return CNEOBotPathReservations()->GetPredictedFriendlyPathCount( area->GetID(), m_me->GetTeamNumber() ) - < CNEOBotPathReservations()->GetPredictedFriendlyPathCount( m_attackCoverArea->GetID(), m_me->GetTeamNumber() ); + return nFriendlyHere < nFriendlyBest; } - // Fallback when path reservation is disabled: potentially visible area + // Tie-break when friendly reservations are equal (or disabled): potentially visible area // count is a rough proxy for how exposed an area is. It ignores whether // the area is actually reachable and does nothing to keep friendlies // from bunching up in the same area. @@ -135,9 +136,7 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor } } - float avoidPenalty = neo_bot_path_reservation_enable.GetBool() - ? CNEOBotPathReservations()->GetAreaAvoidPenalty( area->GetID() ) - : 0.0f; + float avoidPenalty = CNEOBotPathReservations()->GetAreaAvoidPenalty( area->GetID() ); if ( !IsBetterCandidate( area, avoidPenalty ) ) { return true; // the cover candidate we already have is at least as good diff --git a/src/game/server/neo/bot/behavior/neo_bot_dead.cpp b/src/game/server/neo/bot/behavior/neo_bot_dead.cpp index da8cee7138..52d629e9fa 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_dead.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_dead.cpp @@ -4,6 +4,7 @@ #include "bot/neo_bot.h" #include "bot/behavior/neo_bot_dead.h" #include "bot/behavior/neo_bot_behavior.h" +#include "bot/neo_bot_path_reservation.h" #include "nav_mesh.h" @@ -13,6 +14,7 @@ extern void respawn( CBaseEntity* pEdict, bool fCopyCorpse ); ActionResult< CNEOBot > CNEOBotDead::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) { m_deadTimer.Start(); + CNEOBotPathReservations()->ReleaseAllAreas( me ); return Continue(); } diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index 0d0ec60a5f..d5c9822527 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -817,6 +817,7 @@ void CNEOBot::AvoidPlayers(CUserCmd* pCmd) void CNEOBot::UpdateOnRemove(void) { StopIdleSound(); + CNEOBotPathReservations()->ReleaseAllAreas(this); BaseClass::UpdateOnRemove(); } diff --git a/src/game/server/neo/bot/neo_bot_path_compute.cpp b/src/game/server/neo/bot/neo_bot_path_compute.cpp index 0bc1778398..82b2f5ce39 100644 --- a/src/game/server/neo/bot/neo_bot_path_compute.cpp +++ b/src/game/server/neo/bot/neo_bot_path_compute.cpp @@ -8,11 +8,6 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" -extern ConVar neo_bot_path_reservation_enable; -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_enable.GetBool() || !path.IsValid()) 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 81199a0dee..6efb59c651 100644 --- a/src/game/server/neo/bot/neo_bot_path_cost.cpp +++ b/src/game/server/neo/bot/neo_bot_path_cost.cpp @@ -6,8 +6,6 @@ #include "nav_mesh.h" #include "neo_bot_path_reservation.h" -extern ConVar neo_bot_path_reservation_enable; - 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", true, 0, true, 60); @@ -32,6 +30,9 @@ ConVar neo_bot_path_penalty_exposure_inverse_base_battle_rifle("neo_bot_path_pen ConVar neo_bot_path_penalty_exposure_inverse_base_scoped("neo_bot_path_penalty_exposure_inverse_base_scoped", "1000.0", FCVAR_CHEAT, "Base penalty for calculating inverse traversal penalty for scoped weapons", true, 1.0f, false, 0.0f); +ConVar neo_bot_path_visibility_exposure_enable("neo_bot_path_visibility_exposure_enable", "1", FCVAR_NONE, + "Enable visibility-exposure pathing penalties", true, 0, true, 1); + //------------------------------------------------------------------------------------------------- CNEOBotPathCost::CNEOBotPathCost(CNEOBot* me, RouteType routeType) { @@ -41,6 +42,7 @@ CNEOBotPathCost::CNEOBotPathCost(CNEOBot* me, RouteType routeType) m_maxJumpHeight = me->GetLocomotionInterface()->GetMaxJumpHeight(); m_maxDropHeight = me->GetLocomotionInterface()->GetDeathDropHeight(); m_bIgnoreReservations = !neo_bot_path_reservation_enable.GetBool(); + m_bIgnoreVisibilityExposure = !neo_bot_path_visibility_exposure_enable.GetBool(); } //------------------------------------------------------------------------------------------------- @@ -146,11 +148,29 @@ float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const // ------------------------------------------------------------------------------------------------ // New path reservation related cost adjustments - if ( !m_bIgnoreReservations && (m_routeType != FASTEST_ROUTE) ) + if ( NEORules()->IsTeamplay() && !m_bIgnoreReservations && (m_routeType != FASTEST_ROUTE) ) { - cost += CNEOBotPathReservations()->GetPredictedFriendlyPathCount(area->GetID(), m_me->GetTeamNumber()) * neo_bot_path_reservation_penalty.GetFloat(); + const int nFriendly = CNEOBotPathReservations()->GetPredictedFriendlyPathCount(area->GetID(), m_me->GetTeamNumber(), m_me); + if (nFriendly > 0) + { + // Discourage team clustering: (n^2 * penalty) + cost += nFriendly * nFriendly * neo_bot_path_reservation_penalty.GetFloat(); + } cost += CNEOBotPathReservations()->GetAreaAvoidPenalty(area->GetID()); + if (m_routeType == SAFEST_ROUTE) + { + // NEO Jank Cheat: Incorporate enemy bot paths so that we don't run directly into their line of fire + // Intended for use by ghost carrier team, to emulate a team that knows where enemies are likely to ambush + // Compensates for bots' lack of meta knowledge by making them prefer routes not reserved by enemies + // Adheres to cheat against bots but not against humans philosophy by not considering human players' positions + const int nEnemy = CNEOBotPathReservations()->GetPredictedFriendlyPathCount(area->GetID(), GetEnemyTeam(m_me->GetTeamNumber())); + cost += nEnemy * neo_bot_path_reservation_penalty.GetFloat() * 2.0f; + } + } + + if ( !m_bIgnoreVisibilityExposure && (m_routeType != FASTEST_ROUTE) ) + { // Weapon range penalties auto* myWeapon = assert_cast(m_me->GetActiveWeapon()); if (myWeapon) @@ -199,15 +219,6 @@ float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const } } } - - if (m_routeType == SAFEST_ROUTE) - { - // NEO Jank Cheat: Incorporate enemy bot paths so that we don't run directly into their line of fire - // Intended for use by ghost carrier team, to emulate a team that knows where enemies are likely to ambush - // Compensates for bots' lack of meta knowledge by making them prefer routes not reserved by enemies - // Adheres to cheat against bots but not against humans philosophy by not considering human players' positions - cost += CNEOBotPathReservations()->GetPredictedFriendlyPathCount(area->GetID(), GetEnemyTeam(m_me->GetTeamNumber())) * neo_bot_path_reservation_penalty.GetFloat() * 2; - } } // ------------------------------------------------------------------------------------------------ diff --git a/src/game/server/neo/bot/neo_bot_path_cost.h b/src/game/server/neo/bot/neo_bot_path_cost.h index 55ce34b43e..ad312ed011 100644 --- a/src/game/server/neo/bot/neo_bot_path_cost.h +++ b/src/game/server/neo/bot/neo_bot_path_cost.h @@ -24,6 +24,7 @@ class CNEOBotPathCost : public IPathCost virtual float operator()(CNavArea* baseArea, CNavArea* fromArea, const CNavLadder* ladder, const CFuncElevator* elevator, float length) const override; bool m_bIgnoreReservations; + bool m_bIgnoreVisibilityExposure; private: CNEOBot* m_me; diff --git a/src/game/server/neo/bot/neo_bot_path_reservation.cpp b/src/game/server/neo/bot/neo_bot_path_reservation.cpp index 65d08ecb34..12e827f3ea 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.cpp +++ b/src/game/server/neo/bot/neo_bot_path_reservation.cpp @@ -17,7 +17,7 @@ ConVar neo_bot_path_reservation_duration("neo_bot_path_reservation_duration", "3 ConVar neo_bot_path_reservation_distance("neo_bot_path_reservation_distance", "100000", FCVAR_NONE, "How far along the path to reserve, in Hammer units.", true, 0, false, 0); -ConVar neo_bot_path_reservation_penalty("neo_bot_path_reservation_penalty", "10000", FCVAR_NONE, +ConVar neo_bot_path_reservation_penalty("neo_bot_path_reservation_penalty", "3000", FCVAR_NONE, "Pathing cost penalty for a reserved area.", true, 0, false, 0); ConVar neo_bot_path_reservation_friendly_penalty_enable("neo_bot_path_reservation_friendly_penalty_enable", "1", FCVAR_NONE, @@ -40,8 +40,37 @@ CNEOBotPathReservationSystem* CNEOBotPathReservations() return &g_BotPathReservations; } +//------------------------------------------------------------------------------------------------- +// Check if reservation claim is valid and not expired. +static bool ClaimIsLive(const AreaClaim_t &claim) +{ + return claim.hOwner.Get() != NULL && claim.flExpirationTime >= gpGlobals->curtime; +} + +//------------------------------------------------------------------------------------------------- +// Count live reservations on a nav area. +int CNEOBotPathReservationSystem::CountLiveClaims(const AreaReservation_t &res, const CNEOBot *excluding) const +{ + int count = 0; + for (int i = 0; i < res.claims.Count(); ++i) + { + const AreaClaim_t &claim = res.claims[i]; + if (!ClaimIsLive(claim)) + { + continue; + } + if (excluding != NULL && claim.hOwner.Get() == excluding) + { + continue; + } + ++count; + } + return count; +} + +//------------------------------------------------------------------------------------------------- /** - * Reserve a navigation area for a specific bot for a given duration. + * Record (or refresh) this bot's claim on a nav area for the given duration. */ void CNEOBotPathReservationSystem::ReserveArea(CNavArea *area, CNEOBot *bot, float duration) { @@ -61,90 +90,48 @@ void CNEOBotPathReservationSystem::ReserveArea(CNavArea *area, CNEOBot *bot, flo return; } - int areaID = area->GetID(); - int reservationIndex = m_Reservations[team].Find(areaID); + const int areaID = area->GetID(); + const float flExpiration = gpGlobals->curtime + duration; + int reservationIndex = m_Reservations[team].Find(areaID); if (reservationIndex == m_Reservations[team].InvalidIndex()) { reservationIndex = m_Reservations[team].Insert(areaID); } - else + + // Refresh this bot's existing claim, or add a new one. + // For tracking number of teammates routing through here. + AreaReservation_t &res = m_Reservations[team][reservationIndex]; + bool bHadClaim = false; + for (int i = 0; i < res.claims.Count(); ++i) { - // 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) + if (res.claims[i].hOwner.Get() == bot) { - DecrementPredictedFriendlyPathCount(area->GetID(), team); + res.claims[i].flExpirationTime = flExpiration; + bHadClaim = true; + break; } } - - 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) + if (!bHadClaim) { - m_BotReservedAreas.Element(botReservedIndex).areas.AddToTail(area); + AreaClaim_t claim; + claim.hOwner = bot; + claim.flExpirationTime = flExpiration; + res.claims.AddToTail(claim); } -} -/** - * Release a navigation area reservation. - */ -void CNEOBotPathReservationSystem::ReleaseArea(CNavArea *area, CNEOBot *bot) -{ - if (!area || !bot) + // Reverse index for fast release. + int botIndex = m_BotReservedAreas.Find(bot->entindex()); + if (botIndex == m_BotReservedAreas.InvalidIndex()) { - return; + botIndex = m_BotReservedAreas.Insert(bot->entindex()); } - - int team = bot->GetTeamNumber(); - if (team < 0 || team >= TEAM__TOTAL) + if (m_BotReservedAreas[botIndex].areas.Find(area) == -1) { - 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); + m_BotReservedAreas[botIndex].areas.AddToTail(area); } } - //------------------------------------------------------------------------------------------------- /** * Release all navigation area reservations for a specific bot. @@ -156,87 +143,46 @@ void CNEOBotPathReservationSystem::ReleaseAllAreas(CNEOBot *bot) return; } - int team = bot->GetTeamNumber(); - if (team < 0 || team >= TEAM__TOTAL) - { - return; - } - - EHANDLE hBot = bot; - int botReservedIndex = m_BotReservedAreas.Find(hBot); - - if (botReservedIndex == m_BotReservedAreas.InvalidIndex()) + int botIndex = m_BotReservedAreas.Find(bot->entindex()); + if (botIndex == 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) + const int team = bot->GetTeamNumber(); + if (team >= 0 && team < TEAM__TOTAL) { - CNavArea *area = botReservedAreas.areas[i]; - if (area) + const CUtlVector &areas = m_BotReservedAreas[botIndex].areas; + for (int a = 0; a < areas.Count(); ++a) { - int areaID = area->GetID(); - int reservationIndex = m_Reservations[team].Find(areaID); - if (reservationIndex != m_Reservations[team].InvalidIndex()) + CNavArea *area = areas[a]; + if (!area) + { + continue; + } + + int reservationIndex = m_Reservations[team].Find(area->GetID()); + if (reservationIndex == m_Reservations[team].InvalidIndex()) + { + continue; + } + + AreaReservation_t &res = m_Reservations[team][reservationIndex]; + for (int i = res.claims.Count() - 1; i >= 0; --i) { - const ReservationInfo &info = m_Reservations[team].Element(reservationIndex); - if (ToNEOBot(info.hOwner.Get()) == bot) + if (res.claims[i].hOwner.Get() == bot) { - DecrementPredictedFriendlyPathCount(area->GetID(), team); - m_Reservations[team].RemoveAt(reservationIndex); + res.claims.Remove(i); } } + if (res.claims.Count() == 0) + { + 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 >= TEAM__TOTAL) - { - 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; + m_BotReservedAreas.RemoveAt(botIndex); } //-------------------------------------------------------------------------------------------------------------- @@ -258,66 +204,32 @@ void CNEOBotPathReservationSystem::ClearRound() for (int team = 0; team < TEAM__TOTAL; ++team) { m_Reservations[team].RemoveAll(); - m_AreaPathCounts[team].RemoveAll(); m_HazardAreas[team].RemoveAll(); } m_BotReservedAreas.RemoveAll(); } //------------------------------------------------------------------------------------------------- -void CNEOBotPathReservationSystem::IncrementPredictedFriendlyPathCount( int areaID, int teamID ) +int CNEOBotPathReservationSystem::GetPredictedFriendlyPathCount( int areaID, int teamID, const CNEOBot *excluding ) const { - if (teamID < 0 || teamID >= TEAM__TOTAL) - { - 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 >= TEAM__TOTAL) - { - return; - } - - int i = m_AreaPathCounts[teamID].Find( areaID ); - if ( m_AreaPathCounts[teamID].IsValidIndex( i ) ) + if (!NEORules()->GetTeamPlayEnabled()) { - m_AreaPathCounts[teamID][i]--; - if ( m_AreaPathCounts[teamID][i] <= 0 ) - { - m_AreaPathCounts[teamID].RemoveAt( i ); - } + return 0; } -} -//------------------------------------------------------------------------------------------------- -int CNEOBotPathReservationSystem::GetPredictedFriendlyPathCount( int areaID, int teamID ) const -{ if (!neo_bot_path_reservation_friendly_penalty_enable.GetBool() || teamID < 0 || teamID >= TEAM__TOTAL) { return 0; } - int i = m_AreaPathCounts[teamID].Find( areaID ); - if ( m_AreaPathCounts[teamID].IsValidIndex( i ) ) + int reservationIndex = m_Reservations[teamID].Find(areaID); + if (reservationIndex == m_Reservations[teamID].InvalidIndex()) { - return m_AreaPathCounts[teamID][i]; + return 0; } - return 0; + return CountLiveClaims(m_Reservations[teamID][reservationIndex], excluding); } //------------------------------------------------------------------------------------------------- diff --git a/src/game/server/neo/bot/neo_bot_path_reservation.h b/src/game/server/neo/bot/neo_bot_path_reservation.h index 932642d1aa..db76bd11aa 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.h +++ b/src/game/server/neo/bot/neo_bot_path_reservation.h @@ -2,15 +2,25 @@ #include "cbase.h" #include "utlmap.h" +#include "utlvector.h" #include "nav_area.h" #include "neo_player_shared.h" class CNEOBot; -struct ReservationInfo +struct AreaClaim_t { - EHANDLE hOwner; // The bot that reserved this area - float flExpirationTime; // When the reservation expires (gpGlobals->curtime) + EHANDLE hOwner; + float flExpirationTime; // gpGlobals->curtime past which this claim no longer counts +}; + +struct AreaReservation_t +{ + CUtlVector claims; + + AreaReservation_t() {} + AreaReservation_t(const AreaReservation_t& src) { claims = src.claims; } + AreaReservation_t& operator=(const AreaReservation_t& src) { claims = src.claims; return *this; } }; struct HazardInfo @@ -36,38 +46,28 @@ struct BotReservedAreas_t class CNEOBotPathReservationSystem { public: - // Need to define a less function for the CUtlMap - static bool ReservationLessFunc(const int &lhs, const int &rhs) + static bool AreaIDLessFunc(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), m_AreaAvoidPenalties(DefLessFunc(unsigned int)) + CNEOBotPathReservationSystem() + : m_BotReservedAreas(DefLessFunc(int)) + , m_AreaAvoidPenalties(DefLessFunc(unsigned int)) { for (int i = 0; i < TEAM__TOTAL; ++i) { - m_Reservations[i].SetLessFunc(ReservationLessFunc); - m_AreaPathCounts[i].SetLessFunc(ReservationLessFunc); - m_HazardAreas[i].SetLessFunc(ReservationLessFunc); + m_Reservations[i].SetLessFunc(AreaIDLessFunc); + m_HazardAreas[i].SetLessFunc(AreaIDLessFunc); } } void ReserveArea(CNavArea *area, CNEOBot *bot, float duration); - void ReleaseArea(CNavArea *area, CNEOBot *bot); - bool IsAreaReservedByTeammate(CNavArea *area, CNEOBot *avoider) const; + void ReleaseAllAreas(CNEOBot *bot); void Clear(); void ClearRound(); - void ReleaseAllAreas(CNEOBot *bot); - void IncrementPredictedFriendlyPathCount( int areaID, int teamID ); - void DecrementPredictedFriendlyPathCount( int areaID, int teamID ); - int GetPredictedFriendlyPathCount( int areaID, int teamID ) const; + int GetPredictedFriendlyPathCount( int areaID, int teamID, const CNEOBot *excluding = NULL ) const; void IncrementAreaAvoidPenalty(unsigned int navAreaID, float penaltyAmount); float GetAreaAvoidPenalty(unsigned int navAreaID) const; @@ -78,13 +78,11 @@ class CNEOBotPathReservationSystem float GetAreaHazardousTime(int navAreaID, const CNEOBot *me) const; bool IsAreaHazardous(int navAreaID, const CNEOBot *me) const; - // Allow the global accessor to access private members if needed, though constructor handles init now. - friend CNEOBotPathReservationSystem* CNEOBotPathReservations(); - private: - CUtlMap m_Reservations[TEAM__TOTAL]; - CUtlMap m_BotReservedAreas; - CUtlMap m_AreaPathCounts[TEAM__TOTAL]; + int CountLiveClaims(const AreaReservation_t &res, const CNEOBot *excluding) const; + + CUtlMap m_Reservations[TEAM__TOTAL]; // keyed by nav area ID + CUtlMap m_BotReservedAreas; // keyed by bot entindex CUtlMap m_AreaAvoidPenalties; CUtlMap m_HazardAreas[TEAM__TOTAL]; };