From 2bcffa51316d44bab3cb413e6c4ce89fddc5fe93 Mon Sep 17 00:00:00 2001 From: nico Date: Sat, 17 Oct 2026 21:10:53 +0000 Subject: [PATCH] fiexed virtuel entry nodes so they will be considered equal to all other Nodes --- .../World/ClientlessActionExecutorMgr.cpp | 52 +++++++++---------- .../game/World/ClientlessTravelGraphMgr.cpp | 23 +++++++- src/server/game/World/ClientlessTravelMgr.cpp | 33 ++++++++++-- src/server/game/World/ClientlessTravelMgr.h | 4 +- 4 files changed, 80 insertions(+), 32 deletions(-) diff --git a/src/server/game/World/ClientlessActionExecutorMgr.cpp b/src/server/game/World/ClientlessActionExecutorMgr.cpp index b8d090d1a8..8a36e6d226 100644 --- a/src/server/game/World/ClientlessActionExecutorMgr.cpp +++ b/src/server/game/World/ClientlessActionExecutorMgr.cpp @@ -551,14 +551,9 @@ bool ClientlessActionExecutorMgr::StartTravelToNode( record.TaxiSourceNodeId = 0; record.TaxiDestinationNodeId = 0; std::string error; - if (sClientlessTravelGraphMgr->PlanRoute( - player, destinationNode, startRadius, record.Route, error, - &record.FailedEdgeDatabaseIds)) - { - record.RouteComplete = true; - record.AdaptiveReachedNode = destinationNode; - } - else if (!sClientlessTravelGraphMgr->PlanAdaptiveRoute( + // VCORE_VIRTUAL_EDGE_V11_PORTAL_APPROACH: adaptive planning is primary. + // This considers MMAP-reachable ordinary and virtual ground entry points together. + if (!sClientlessTravelGraphMgr->PlanAdaptiveRoute( player, destinationNode, startRadius, record.LocalBridgeRadius, record.Route, record.RouteComplete, record.AdaptiveReachedNode, error, &record.FailedEdgeDatabaseIds, &record.FailedBridgeKeys)) @@ -1279,7 +1274,11 @@ bool ClientlessActionExecutorMgr::StartRouteStep( Position destination; destination.Relocate(step.X, step.Y, step.Z, step.O); - if (!sClientlessTravelMgr->StartTravel(player, destination, true)) + bool const portalSourceApproach = + step.NodeKey.rfind("portal_at_", 0) == 0 && + step.NodeKey.find("_source") != std::string::npos; + if (!sClientlessTravelMgr->StartTravel(player, destination, !portalSourceApproach, + portalSourceApproach)) { record.Status.State = ClientlessActionExecutionState::Failed; record.Status.Detail = "travel start failed for route node " + step.NodeKey; @@ -1466,6 +1465,18 @@ bool ClientlessActionExecutorMgr::RecoverTravel( if (failedEdgeDatabaseId) record.FailedEdgeDatabaseIds.insert(failedEdgeDatabaseId); + // A failed initial local approach must not be tried five times at the same spot. + if (!failedEdgeDatabaseId && record.RouteStep == 0 && !record.Route.empty() && + reason.rfind("route-local-", 0) == 0) + { + std::string const& badStart = record.Route.front().NodeKey; + if (badStart != "virtual_ground_entry") + { + record.FailedBridgeKeys.insert("start:" + badStart); + TC_LOG_WARN("server.adventurebot.navigation", + "START_NODE_BLOCK node='%s' reason='%s'", badStart.c_str(), reason.c_str()); + } + } bool const bridgeFailure = reason.rfind("local-bridge-", 0) == 0; @@ -1561,24 +1572,11 @@ bool ClientlessActionExecutorMgr::RecoverTravel( } else if (record.Status.ActionName == "travel_to_node") { - planned = sClientlessTravelGraphMgr->PlanRoute( - player, - record.PlannedDestinationNode, - radius, - record.Route, - error, - &record.FailedEdgeDatabaseIds); - - if (planned) - { - record.RouteComplete = true; - record.AdaptiveReachedNode = record.PlannedDestinationNode; - } - else - planned = sClientlessTravelGraphMgr->PlanAdaptiveRoute( - player, record.PlannedDestinationNode, radius, record.LocalBridgeRadius, - record.Route, record.RouteComplete, record.AdaptiveReachedNode, error, - &record.FailedEdgeDatabaseIds, &record.FailedBridgeKeys); + // VCORE_VIRTUAL_EDGE_V11_PORTAL_APPROACH: adaptive recovery only. + planned = sClientlessTravelGraphMgr->PlanAdaptiveRoute( + player, record.PlannedDestinationNode, radius, record.LocalBridgeRadius, + record.Route, record.RouteComplete, record.AdaptiveReachedNode, error, + &record.FailedEdgeDatabaseIds, &record.FailedBridgeKeys); if (planned) TC_LOG_WARN("server.adventurebot.navigation", diff --git a/src/server/game/World/ClientlessTravelGraphMgr.cpp b/src/server/game/World/ClientlessTravelGraphMgr.cpp index 8ca1ac886a..3a8b05f382 100644 --- a/src/server/game/World/ClientlessTravelGraphMgr.cpp +++ b/src/server/game/World/ClientlessTravelGraphMgr.cpp @@ -470,6 +470,25 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute( if (localDistance > startRadius) continue; + // VCORE_VIRTUAL_EDGE_V11_PORTAL_APPROACH: never seed an unreachable node. + if (blockedBridgeKeys && blockedBridgeKeys->count("start:" + node.Key)) + continue; + if (localDistance > 1.5f) + { + Position entryTarget; + entryTarget.Relocate(node.X, node.Y, node.Z, node.O); + ClientlessPathDiagnostics entryNav; + sClientlessTravelMgr->AnalyzePath(const_cast(player), entryTarget, entryNav); + if (!entryNav.Calculated || !entryNav.Complete || entryNav.FinalDistance > 1.5f || + entryNav.MaxSegmentLength > 8.0f || entryNav.MaxZDelta > 5.0f || + entryNav.PathLength > 500.0f) + { + TC_LOG_INFO("server.adventurebot.navigation", + "START_NODE_REJECT node='%s' distance=%.2f reason='%s'", + node.Key.c_str(), localDistance, entryNav.Reason.c_str()); + continue; + } + } State state{ node.Key, false }; std::string const key = stateKey(state); auto found = distance.find(key); @@ -575,6 +594,8 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute( State state{ candidate.Edge.ToNode, false }; std::string key = stateKey(state); + if (blockedBridgeKeys && blockedBridgeKeys->count("start:" + candidate.Edge.ToNode)) + continue; float remainingFraction = candidate.Edge.Reverse ? float(candidate.Index) / float(candidate.Count - 1) : float(candidate.Count - candidate.Index - 1) / float(candidate.Count - 1); @@ -591,7 +612,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute( open.push({ newCost, state }); ++accepted; } - if (accepted) + if (validated) TC_LOG_INFO("server.adventurebot.navigation", "VIRTUAL_EDGE_CANDIDATES map=%u tested=%u accepted=%u", player->GetMapId(), validated, accepted); diff --git a/src/server/game/World/ClientlessTravelMgr.cpp b/src/server/game/World/ClientlessTravelMgr.cpp index f5be4e2a9f..10a1fcf4f2 100644 --- a/src/server/game/World/ClientlessTravelMgr.cpp +++ b/src/server/game/World/ClientlessTravelMgr.cpp @@ -242,7 +242,16 @@ bool ClientlessTravelMgr::LaunchPath(Player* player, TravelRecord& record) nav.MaxZDelta, nav.FinalDistance, record.Status.RepathCount, nav.Reason.c_str()); - if (!nav.Calculated || !nav.Complete || nav.PointCount < 2 || nav.FinalDistance > MAX_PATH_FINAL_ERROR) + // VCORE_VIRTUAL_EDGE_V11_PORTAL_APPROACH: execute only a validated partial + // MMAP corridor. Never turn this exception on for normal ground navigation. + bool const portalPartial = record.PortalApproach && nav.Calculated && + (nav.PathType & uint32(PATHFIND_INCOMPLETE)) != 0 && + (nav.PathType & uint32(PATHFIND_NOPATH)) == 0 && + nav.PointCount >= 2 && nav.FinalDistance <= 6.0f && + nav.PathLength <= 100.0f && nav.MaxSegmentLength <= 6.0f && + nav.MaxZDelta <= 4.0f; + if (!nav.Calculated || (!nav.Complete && !portalPartial) || nav.PointCount < 2 || + (nav.FinalDistance > MAX_PATH_FINAL_ERROR && !portalPartial)) { record.Status.State = ClientlessTravelState::PathRejected; record.Status.NoProgressMs = 0; @@ -299,9 +308,15 @@ bool ClientlessTravelMgr::LaunchPath(Player* player, TravelRecord& record) "NAV_SHORT_INCOMPLETE_ACCEPT guid=%u name='%s' path=%.2f final=%.2f", player->GetGUID().GetCounter(), player->GetName().c_str(), executionLength, executionFinalDistance); + bool const portalExecutionPartial = record.PortalApproach && portalPartial && + (executionType & uint32(PATHFIND_INCOMPLETE)) != 0 && + (executionType & uint32(PATHFIND_NOPATH)) == 0 && + points.size() >= 2 && executionFinalDistance <= 6.0f && + executionLength <= 100.0f && executionMaxSegment <= 6.0f && executionMaxZ <= 4.0f; if (points.size() < 2 || (executionType & uint32(PATHFIND_NOPATH)) != 0 || - ((executionType & uint32(PATHFIND_INCOMPLETE)) != 0 && !executionShortEndReached)) + ((executionType & uint32(PATHFIND_INCOMPLETE)) != 0 && + !executionShortEndReached && !portalExecutionPartial)) { record.Status.State = ClientlessTravelState::PathRejected; TC_LOG_WARN("server.adventurebot.navigation", @@ -312,6 +327,17 @@ bool ClientlessTravelMgr::LaunchPath(Player* player, TravelRecord& record) return false; } + if (portalExecutionPartial) + { + // Mark local traversal arrived at the actual MMAP endpoint only; + // the portal executor still checks IsInAreaTriggerRadius and actual map transition. + Position endpoint; + endpoint.Relocate(points.back().x, points.back().y, points.back().z, player->GetOrientation()); + record.Status.Target = endpoint; + TC_LOG_INFO("server.adventurebot.navigation", + "NAV_PORTAL_PARTIAL_APPROACH guid=%u remainingToTrigger=%.2f path=%.2f", + player->GetGUID().GetCounter(), executionFinalDistance, executionLength); + } std::vector spline; spline.reserve(points.size() + 1); @@ -406,7 +432,7 @@ bool ClientlessTravelMgr::LaunchPath(Player* player, TravelRecord& record) return true; } -bool ClientlessTravelMgr::StartTravel(Player* player, Position const& target, bool allowGroundMount) +bool ClientlessTravelMgr::StartTravel(Player* player, Position const& target, bool allowGroundMount, bool portalApproach) { if (!player || !player->IsInWorld()) return false; @@ -419,6 +445,7 @@ bool ClientlessTravelMgr::StartTravel(Player* player, Position const& target, bo record.Status.MapId = player->GetMapId(); record.Status.Target = target; record.AllowGroundMount = allowGroundMount; + record.PortalApproach = portalApproach; LaunchPath(player, record); diff --git a/src/server/game/World/ClientlessTravelMgr.h b/src/server/game/World/ClientlessTravelMgr.h index 2b2f4ee70e..18ebee8982 100644 --- a/src/server/game/World/ClientlessTravelMgr.h +++ b/src/server/game/World/ClientlessTravelMgr.h @@ -57,7 +57,7 @@ class TC_GAME_API ClientlessTravelMgr public: static ClientlessTravelMgr* instance(); - bool StartTravel(Player* player, Position const& target, bool allowGroundMount = false); + bool StartTravel(Player* player, Position const& target, bool allowGroundMount = false, bool portalApproach = false); bool AnalyzePath(Player* player, Position const& target, ClientlessPathDiagnostics& diagnostics) const; void CancelTravel(uint32 characterGuid); ClientlessTravelStatus const* GetStatus(uint32 characterGuid) const; @@ -76,6 +76,8 @@ private: uint32 CheckTimerMs = 0; std::vector ActiveSpline; bool AllowGroundMount = false; + // VCORE_VIRTUAL_EDGE_V11_PORTAL_APPROACH: only actual portal source steps. + bool PortalApproach = false; }; bool LaunchPath(Player* player, TravelRecord& record);