diff --git a/src/Ai/Base/Actions/CheckValuesAction.cpp b/src/Ai/Base/Actions/CheckValuesAction.cpp index dce66bd47..19f8f21f5 100644 --- a/src/Ai/Base/Actions/CheckValuesAction.cpp +++ b/src/Ai/Base/Actions/CheckValuesAction.cpp @@ -6,10 +6,10 @@ #include "CheckValuesAction.h" #include "Event.h" +#include "ObjectGuid.h" #include "ServerFacade.h" #include "PlayerbotAI.h" -#include "TravelNode.h" #include "AiObjectContext.h" CheckValuesAction::CheckValuesAction(PlayerbotAI* botAI) : Action(botAI, "check values") {} @@ -21,11 +21,6 @@ bool CheckValuesAction::Execute(Event /*event*/) botAI->Ping(bot->GetPositionX(), bot->GetPositionY()); } - if (botAI->HasStrategy("map", BOT_STATE_NON_COMBAT) || botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT)) - { - TravelNodeMap::instance().manageNodes(bot, botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT)); - } - GuidVector possible_targets = *context->GetValue("possible targets"); GuidVector all_targets = *context->GetValue("all targets"); GuidVector npcs = *context->GetValue("nearest npcs"); diff --git a/src/Ai/Base/Actions/DebugAction.cpp b/src/Ai/Base/Actions/DebugAction.cpp index 006bf0b80..a4d0d2494 100644 --- a/src/Ai/Base/Actions/DebugAction.cpp +++ b/src/Ai/Base/Actions/DebugAction.cpp @@ -76,7 +76,7 @@ bool DebugAction::Execute(Event event) return false; std::vector beginPath, endPath; - TravelNodeRoute route = TravelNodeMap::instance().GetNearestNodes(botPos, *points.front(), beginPath, bot); + TravelNodeRoute route = TravelNodeMap::instance().FindRouteNearestNodes(botPos, *points.front(), beginPath, bot); std::ostringstream out; out << "Traveling to " << dest->getTitle() << ": "; diff --git a/src/Ai/Base/Actions/MovementActions.cpp b/src/Ai/Base/Actions/MovementActions.cpp index 81d3c6da2..5d0bd71d0 100644 --- a/src/Ai/Base/Actions/MovementActions.cpp +++ b/src/Ai/Base/Actions/MovementActions.cpp @@ -2997,6 +2997,14 @@ bool MovementAction::CheckSplineProgress(TravelPlan& state) if (!state.splineActive) return false; + // walkPoints may have been cleared by a map transfer or external reset + // while the spline was still flagged active; bail out safely. + if (state.walkPoints.empty()) + { + state.splineActive = false; + return false; + } + if (bot->movespline->Finalized()) { G3D::Vector3 const& endPt = state.walkPoints.back(); @@ -3009,7 +3017,7 @@ bool MovementAction::CheckSplineProgress(TravelPlan& state) return true; // Arrived } - // If we havent arrived to destination, but are done moving then something interrupted it. + // If we haven't arrived to destination, but are done moving then something interrupted it. // Need to restart. Reset state state.splineActive = false; return false; @@ -3226,7 +3234,7 @@ bool MovementAction::ExecuteTravelPlan(TravelPlan& state) if (dist > INTERACTION_DISTANCE) return MoveTo(src.point.GetMapId(), src.point.GetPositionX(), src.point.GetPositionY(), src.point.GetPositionZ()); - // At portal, but havent teleported though + // At portal, but haven't teleported through TeleportFallback(state, dst.point, "portal walk-through"); state.stepIdx += 2; return true; @@ -3369,6 +3377,14 @@ bool MovementAction::ExecuteTravelPlan(TravelPlan& state) state.stepIdx += 2; return true; } + default: + { + LOG_ERROR("playerbots", + "[TravelPlan] Bot {} encountered unknown PathNodeType ({}); resetting plan", + bot->GetName(), static_cast(pt.type)); + state.Reset(); + return false; + } } return false; } diff --git a/src/Mgr/Travel/TravelNode.cpp b/src/Mgr/Travel/TravelNode.cpp index d4bdd21ee..a825bbc5f 100644 --- a/src/Mgr/Travel/TravelNode.cpp +++ b/src/Mgr/Travel/TravelNode.cpp @@ -115,25 +115,25 @@ float TravelNodePath::getCost(Player* bot, uint32 cGold) if (getPathType() == TravelNodePathType::flightPath && pathObject) { if (!bot->IsAlive()) - return -1; + return -1.0f; TaxiPathEntry const* taxiPath = sTaxiPathStore.LookupEntry(pathObject); if (!taxiPath) - return -1; + return -1.0f; if (!bot->isTaxiCheater() && taxiPath->price > cGold) - return -1; + return -1.0f; if (!bot->isTaxiCheater() && !bot->m_taxi.IsTaximaskNodeKnown(taxiPath->to)) - return -1; + return -1.0f; TaxiNodesEntry const* startTaxiNode = sTaxiNodesStore.LookupEntry(taxiPath->from); TaxiNodesEntry const* endTaxiNode = sTaxiNodesStore.LookupEntry(taxiPath->to); if (!startTaxiNode || !endTaxiNode || !startTaxiNode->MountCreatureID[bot->GetTeamId() == TEAM_ALLIANCE ? 1 : 0] || !endTaxiNode->MountCreatureID[bot->GetTeamId() == TEAM_ALLIANCE ? 1 : 0]) - return -1; + return -1.0f; } speed = bot->GetSpeed(MOVE_RUN); @@ -164,7 +164,7 @@ float TravelNodePath::getCost(Player* bot, uint32 cGold) if (getPathType() == TravelNodePathType::flyingMount) { if (!bot->IsAlive() || bot->GetLevel() < 70 || !bot->CanFly()) - return -1; + return -1.0f; float flySpeed = bot->GetSpeed(MOVE_FLIGHT); if (flySpeed < 1.0f) @@ -173,7 +173,7 @@ float TravelNodePath::getCost(Player* bot, uint32 cGold) } } else if (getPathType() == TravelNodePathType::flightPath || getPathType() == TravelNodePathType::flyingMount) - return -1; + return -1.0f; if (getPathType() != TravelNodePathType::walk) timeCost = extraCost * modifier; @@ -1097,7 +1097,12 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal, while (!open.empty()) { if (++nodesExplored > MAX_A_STAR_EXPLORED) + { + LOG_DEBUG("playerbots", + "[TravelNode A*] Exceeded MAX_A_STAR_EXPLORED ({}); truncating route from '{}' to '{}'", + MAX_A_STAR_EXPLORED, start->getName(), goal->getName()); return TravelNodeRoute(); + } std::pop_heap(open.begin(), open.end(), heapComp); currentNode = open.back(); @@ -1164,7 +1169,7 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal, return TravelNodeRoute(); } -TravelNodeRoute TravelNodeMap::GetNearestNodes(WorldPosition startPos, WorldPosition endPos, +TravelNodeRoute TravelNodeMap::FindRouteNearestNodes(WorldPosition startPos, WorldPosition endPos, std::vector& startPath, Player* bot) { if (nodes.empty() || !bot) @@ -1375,14 +1380,6 @@ TravelNode* TravelNodeMap::addRandomExtNode(TravelNode* startNode) return nullptr; } -void TravelNodeMap::manageNodes(Unit* bot, bool mapFull) -{ - // Runtime node mutation disabled — the graph is fully built at startup via Init() - // and .generate. Taking a unique_lock here caused 100-250ms contention spikes for - // all bot threads holding shared_locks in TravelPlan. - // Node pruning/creation is still available via the .generate console command. -} - void TravelNodeMap::generateNpcNodes() { std::unordered_map> bossMap; @@ -1943,6 +1940,14 @@ void TravelNodeMap::generateAll() void TravelNodeMap::Init() { + InitTaxiGraph(); + + if (!sPlayerbotAIConfig.enableTravelNodes) + { + LOG_INFO("playerbots", "TravelNodeMap: travel node system disabled via AiPlayerbot.EnableTravelNodes=0 — skipping node load/generate/index."); + return; + } + LoadNodeStore(); calcMapOffset(); @@ -1961,7 +1966,6 @@ void TravelNodeMap::Init() BuildZoneIndex(); PrecomputeReachability(); - InitTaxiGraph(); LOG_INFO("playerbots", "TravelNodeMap initialized: {} nodes, zone index and reachability built.", nodes.size()); } diff --git a/src/Mgr/Travel/TravelNode.h b/src/Mgr/Travel/TravelNode.h index 7f368424a..3277e21de 100644 --- a/src/Mgr/Travel/TravelNode.h +++ b/src/Mgr/Travel/TravelNode.h @@ -647,14 +647,12 @@ public: TravelNodeRoute GetNodeRoute(TravelNode* start, TravelNode* goal, Player* bot); - // Find the nearest start/end nodes for two world positions - TravelNodeRoute GetNearestNodes(WorldPosition startPos, - WorldPosition endPos, - std::vector& startPath, - Player* bot = nullptr); - - // Manage/update nodes - void manageNodes(Unit* bot, bool mapFull = false); + // Picks the nearest start/end nodes for two world positions and runs A* + // over the node graph to return a full route between them. + TravelNodeRoute FindRouteNearestNodes(WorldPosition startPos, + WorldPosition endPos, + std::vector& startPath, + Player* bot = nullptr); void setHasToGen() { hasToGen = true; }