Patch for the portalnode approach and selection
This commit is contained in:
1 parent
b37a0ddcc4
commit
56da0d60cf
4 files changed
+222
-8
No files matched your search
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"original": {
|
||||
"graph": "5d632279d7e25748f3bd8e7ffe954482821334bef12088aad885e6a3ef79c6ac",
|
||||
"executor": "016cdfb51387ab58d3e15a36b91e8fd69381b66962edd62214e4c54868340468",
|
||||
"header": "61ccbca3b666016a465d8a9a9049dfa5420177e66aa073fdfe927d749ad2b53b"
|
||||
},
|
||||
"patched": {
|
||||
"graph": "a9da61755df58666cfa2c6deb7f5d886760036936bc51b51b91ad4ef0ff76ecd",
|
||||
"executor": "cbb79474640192c5c853174e219deade3694231060d90163362385db2fd6fb6c",
|
||||
"header": "64bd460621aa63c3cf7e341173249a5b945091e955e2673523478b0762e89dde"
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Opcodes.h"
|
||||
#include "Player.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "PathGenerator.h" // VCORE_C819_DEFERRED_START_NODES_V2
|
||||
|
||||
#include <algorithm>
|
||||
#include "Position.h"
|
||||
@@ -190,6 +191,8 @@ bool ClientlessActionExecutorMgr::StartAction(Player* player, ClientlessAction c
|
||||
record.PlannedAnchorRadius = 1000.0f;
|
||||
record.PlannedDestinationNode.clear();
|
||||
record.FailedBridgeKeys.clear();
|
||||
record.DeferredStartActive = false; // VCORE_C819_DEFERRED_START_NODES_V2
|
||||
record.DeferredStartAttempts = 0;
|
||||
record.RouteComplete = true;
|
||||
record.HardDestination = false;
|
||||
record.LocalBridgeRadius = 300.0f;
|
||||
@@ -1279,6 +1282,95 @@ bool ClientlessActionExecutorMgr::StartRouteStep(
|
||||
bool const portalSourceApproach =
|
||||
step.NodeKey.rfind("portal_at_", 0) == 0 &&
|
||||
step.NodeKey.find("_source") != std::string::npos;
|
||||
// VCORE_C819_DEFERRED_START_NODES_V2: a deferred START root is only a
|
||||
// graph candidate. Move along its safe MMAP portion before entering the graph.
|
||||
// A partial-path endpoint is never considered successful arrival at the node.
|
||||
if (step.EdgeType == "start" && record.RouteStep == 0)
|
||||
{
|
||||
// If Arthoran is already physically inside the source trigger, the
|
||||
// graph start is reached even if the navmesh ends before its center.
|
||||
if (portalSourceApproach)
|
||||
{
|
||||
uint32 triggerId = 0;
|
||||
if (ParsePortalSourceNodeKey(step.NodeKey, triggerId))
|
||||
{
|
||||
AreaTriggerEntry const* trigger = sAreaTriggerStore.LookupEntry(triggerId);
|
||||
if (trigger && player->GetMapId() == uint32(trigger->ContinentID) &&
|
||||
player->IsInAreaTriggerRadius(trigger))
|
||||
{
|
||||
TC_LOG_INFO("server.adventurebot.navigation",
|
||||
"C819_START_PORTAL_READY node='%s' trigger=%u",
|
||||
step.NodeKey.c_str(), triggerId);
|
||||
++record.RouteStep;
|
||||
return StartRouteStep(player, characterGuid, record);
|
||||
}
|
||||
}
|
||||
}
|
||||
ClientlessPathDiagnostics entryNav;
|
||||
sClientlessTravelMgr->AnalyzePath(player, destination, entryNav);
|
||||
bool const partial = entryNav.Calculated && !entryNav.Complete &&
|
||||
(entryNav.PathType & uint32(PATHFIND_INCOMPLETE)) != 0 &&
|
||||
(entryNav.PathType & uint32(PATHFIND_NOPATH)) == 0 &&
|
||||
entryNav.PointCount >= 2 && entryNav.PathLength >= 2.0f &&
|
||||
entryNav.PathLength <= 500.0f && entryNav.MaxSegmentLength <= 8.0f &&
|
||||
entryNav.MaxZDelta <= 5.0f &&
|
||||
entryNav.FinalDistance + 1.0f < entryNav.DirectDistance;
|
||||
|
||||
// All safe deferred roots execute only the MMAP portion. For portals,
|
||||
// the real trigger-radius check below decides whether this was enough.
|
||||
// No off-mesh straight-line movement or artificial teleport is added.
|
||||
if (partial)
|
||||
{
|
||||
constexpr uint32 MaxDeferredApproaches = 4;
|
||||
if (record.DeferredStartAttempts >= MaxDeferredApproaches)
|
||||
{
|
||||
record.FailedBridgeKeys.insert("start:" + step.NodeKey);
|
||||
return RecoverTravel(player, characterGuid, record, 0,
|
||||
"deferred-start-attempts-exhausted");
|
||||
}
|
||||
|
||||
PathGenerator entryPath(player);
|
||||
if (entryPath.CalculatePath(step.X, step.Y, step.Z) &&
|
||||
(uint32(entryPath.GetPathType()) & uint32(PATHFIND_INCOMPLETE)) != 0 &&
|
||||
(uint32(entryPath.GetPathType()) & uint32(PATHFIND_NOPATH)) == 0 &&
|
||||
entryPath.GetPath().size() >= 2)
|
||||
{
|
||||
auto const& endpoint = entryPath.GetPath().back();
|
||||
Position safeEnd;
|
||||
safeEnd.Relocate(endpoint.x, endpoint.y, endpoint.z, player->GetOrientation());
|
||||
float const dx = endpoint.x - player->GetPositionX();
|
||||
float const dy = endpoint.y - player->GetPositionY();
|
||||
float const dz = endpoint.z - player->GetPositionZ();
|
||||
float const progress = std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||
float const tx = endpoint.x - step.X;
|
||||
float const ty = endpoint.y - step.Y;
|
||||
float const tz = endpoint.z - step.Z;
|
||||
float const remaining = std::sqrt(tx * tx + ty * ty + tz * tz);
|
||||
// Prevent a second path query from silently giving us a different,
|
||||
// non-progressing or unsafe endpoint. StartTravel validates MMAP again.
|
||||
if (progress >= 2.0f && remaining + 1.0f < entryNav.DirectDistance &&
|
||||
std::fabs(remaining - entryNav.FinalDistance) <= 2.0f &&
|
||||
sClientlessTravelMgr->StartTravel(player, safeEnd, true, false))
|
||||
{
|
||||
ClientlessTravelStatus const* started =
|
||||
sClientlessTravelMgr->GetStatus(characterGuid);
|
||||
if (started && started->State != ClientlessTravelState::PathRejected)
|
||||
{
|
||||
record.DeferredStartActive = true;
|
||||
++record.DeferredStartAttempts;
|
||||
TC_LOG_INFO("server.adventurebot.navigation",
|
||||
"C819_START_APPROACH node='%s' segment=%.2f remaining=%.2f attempt=%u",
|
||||
step.NodeKey.c_str(), progress, remaining,
|
||||
record.DeferredStartAttempts);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
record.FailedBridgeKeys.insert("start:" + step.NodeKey);
|
||||
return RecoverTravel(player, characterGuid, record, 0,
|
||||
"deferred-start-no-progress");
|
||||
}
|
||||
}
|
||||
if (!sClientlessTravelMgr->StartTravel(player, destination, !portalSourceApproach,
|
||||
portalSourceApproach))
|
||||
{
|
||||
@@ -1499,9 +1591,10 @@ bool ClientlessActionExecutorMgr::RecoverTravel(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bridgeFailure)
|
||||
if (!bridgeFailure && reason != "deferred-start-endpoint-replan")
|
||||
++record.RecoveryCount;
|
||||
sClientlessTravelMgr->CancelTravel(characterGuid);
|
||||
record.DeferredStartActive = false; // VCORE_C819_DEFERRED_START_NODES_V2
|
||||
record.EdgePath.clear();
|
||||
record.TaxiActive = false;
|
||||
record.TaxiPathId = 0;
|
||||
@@ -1850,6 +1943,50 @@ void ClientlessActionExecutorMgr::UpdateTravelToNode(
|
||||
if (travel->State == ClientlessTravelState::Moving)
|
||||
return;
|
||||
|
||||
// VCORE_C819_DEFERRED_START_NODES_V2: local endpoint != graph arrival.
|
||||
if (record.DeferredStartActive)
|
||||
{
|
||||
record.DeferredStartActive = false;
|
||||
if (travel->State != ClientlessTravelState::Arrived)
|
||||
{
|
||||
RecoverTravel(player, characterGuid, record, 0,
|
||||
"deferred-start-movement-failed");
|
||||
return;
|
||||
}
|
||||
if (record.RouteStep >= record.Route.size())
|
||||
{
|
||||
record.Status.State = ClientlessActionExecutionState::Failed;
|
||||
record.Status.Detail = "deferred start route step disappeared";
|
||||
record.Active = false;
|
||||
return;
|
||||
}
|
||||
ClientlessTravelRouteStep const& start = record.Route[record.RouteStep];
|
||||
TC_LOG_INFO("server.adventurebot.navigation",
|
||||
"C819_START_ENDPOINT guid=%u node='%s' pos=(%.3f %.3f %.3f)",
|
||||
characterGuid, start.NodeKey.c_str(),
|
||||
player->GetPositionX(), player->GetPositionY(), player->GetPositionZ());
|
||||
uint32 triggerId = 0;
|
||||
if (ParsePortalSourceNodeKey(start.NodeKey, triggerId))
|
||||
{
|
||||
AreaTriggerEntry const* trigger = sAreaTriggerStore.LookupEntry(triggerId);
|
||||
if (trigger && player->GetMapId() == uint32(trigger->ContinentID) &&
|
||||
player->IsInAreaTriggerRadius(trigger))
|
||||
{
|
||||
// Only the real in-radius check authorizes the portal edge.
|
||||
TC_LOG_INFO("server.adventurebot.navigation",
|
||||
"C819_START_PORTAL_READY node='%s' trigger=%u",
|
||||
start.NodeKey.c_str(), triggerId);
|
||||
++record.RouteStep;
|
||||
StartRouteStep(player, characterGuid, record);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Real replan from the new physical position, never EDGE_COMPLETE.
|
||||
RecoverTravel(player, characterGuid, record, 0,
|
||||
"deferred-start-endpoint-replan");
|
||||
return;
|
||||
}
|
||||
|
||||
if (travel->State != ClientlessTravelState::Arrived)
|
||||
{
|
||||
uint32 const failedEdgeId =
|
||||
|
||||
@@ -52,6 +52,8 @@ private:
|
||||
|
||||
std::vector<ClientlessTravelRouteStep> Route;
|
||||
std::size_t RouteStep = 0;
|
||||
bool DeferredStartActive = false; // VCORE_C819_DEFERRED_START_NODES_V2
|
||||
uint32 DeferredStartAttempts = 0;
|
||||
|
||||
std::vector<ClientlessTravelEdgePoint> EdgePath;
|
||||
|
||||
|
||||
@@ -389,6 +389,10 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
std::unordered_set<uint32> const* blockedEdgeIds,
|
||||
std::unordered_set<std::string> const* blockedBridgeKeys)
|
||||
{
|
||||
// VCORE_C819_DEFERRED_START_NODES: run the unchanged C8.18 search in two tiers.
|
||||
// A partial ordinary route must not suppress a complete route via a deferred root.
|
||||
auto runSearch = [&](bool allowDeferred) -> bool
|
||||
{
|
||||
route.clear();
|
||||
reachedNode.clear();
|
||||
error.clear();
|
||||
@@ -491,16 +495,33 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
entryTarget.Relocate(node.X, node.Y, node.Z, node.O);
|
||||
ClientlessPathDiagnostics entryNav;
|
||||
sClientlessTravelMgr->AnalyzePath(const_cast<Player*>(player), entryTarget, entryNav);
|
||||
if (!entryNav.Calculated || !entryNav.Complete || entryNav.FinalDistance > 1.5f ||
|
||||
entryNav.MaxSegmentLength > 8.0f || entryNav.MaxZDelta > 5.0f ||
|
||||
entryNav.PathLength > 500.0f)
|
||||
bool const reachable = entryNav.Calculated && entryNav.Complete &&
|
||||
entryNav.FinalDistance <= 1.5f && entryNav.MaxSegmentLength <= 8.0f &&
|
||||
entryNav.MaxZDelta <= 5.0f && entryNav.PathLength <= 500.0f;
|
||||
// VCORE_C819_DEFERRED_START_NODES: incomplete is not the same as unsafe.
|
||||
// The endpoint is handled by the executor, not counted as arrival at node.
|
||||
bool const partial = entryNav.Calculated &&
|
||||
(entryNav.PathType & uint32(PATHFIND_INCOMPLETE)) != 0 &&
|
||||
(entryNav.PathType & uint32(PATHFIND_NOPATH)) == 0 &&
|
||||
entryNav.PointCount >= 2 && entryNav.PathLength >= 2.0f &&
|
||||
entryNav.PathLength <= 500.0f && entryNav.MaxSegmentLength <= 8.0f &&
|
||||
entryNav.MaxZDelta <= 5.0f &&
|
||||
entryNav.FinalDistance + 1.0f < entryNav.DirectDistance;
|
||||
if (!reachable && !(allowDeferred && partial))
|
||||
{
|
||||
TC_LOG_INFO("server.adventurebot.navigation",
|
||||
"START_NODE_REJECT node='%s' distance=%.2f reason='%s'",
|
||||
node.Key.c_str(), localDistance, entryNav.Reason.c_str());
|
||||
if (partial && !allowDeferred)
|
||||
TC_LOG_INFO("server.adventurebot.navigation",
|
||||
"C819_START_NODE_DEFER node='%s' path=%.2f final=%.2f",
|
||||
node.Key.c_str(), entryNav.PathLength, entryNav.FinalDistance);
|
||||
else if (!partial)
|
||||
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;
|
||||
}
|
||||
startCost = entryNav.PathLength;
|
||||
if (!reachable)
|
||||
startCost += 100000.0f; // Tier 2 only: deferred roots rank after reachable roots
|
||||
}
|
||||
State state{ node.Key, false };
|
||||
std::string const key = stateKey(state);
|
||||
@@ -511,7 +532,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
distance[key] = startCost;
|
||||
states[key] = state;
|
||||
persistentHops[key] = 0;
|
||||
open.push({ localDistance, state });
|
||||
open.push({ startCost, state });
|
||||
++startCount;
|
||||
}
|
||||
|
||||
@@ -1158,6 +1179,48 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
error = "destination not fully reachable; returning best-effort partial route";
|
||||
|
||||
return !route.empty();
|
||||
|
||||
};
|
||||
|
||||
bool const firstOk = runSearch(false);
|
||||
if (firstOk && complete)
|
||||
{
|
||||
TC_LOG_INFO("server.adventurebot.navigation", "C819_ROUTE_TIER tier=reachable complete=1");
|
||||
return true;
|
||||
}
|
||||
std::vector<ClientlessTravelRouteStep> firstRoute = route;
|
||||
bool const firstComplete = complete;
|
||||
std::string firstReached = reachedNode;
|
||||
std::string firstError = error;
|
||||
bool const secondOk = runSearch(true);
|
||||
if (secondOk && complete)
|
||||
{
|
||||
TC_LOG_INFO("server.adventurebot.navigation", "C819_ROUTE_TIER tier=deferred complete=1");
|
||||
return true;
|
||||
}
|
||||
// If no complete route exists, a selected deferred root is still a useful
|
||||
// approach to real graph infrastructure, provided that its execution is safe.
|
||||
if (secondOk && !complete && !route.empty() &&
|
||||
route.front().EdgeType == "start" &&
|
||||
route.front().NodeKey != "virtual_ground_entry" &&
|
||||
firstOk && !firstRoute.empty() &&
|
||||
route.front().NodeKey != firstRoute.front().NodeKey)
|
||||
{
|
||||
TC_LOG_INFO("server.adventurebot.navigation",
|
||||
"C819_ROUTE_TIER tier=deferred-approach complete=0");
|
||||
return true;
|
||||
}
|
||||
if (firstOk)
|
||||
{
|
||||
route = std::move(firstRoute);
|
||||
complete = firstComplete;
|
||||
reachedNode = std::move(firstReached);
|
||||
error = std::move(firstError);
|
||||
TC_LOG_INFO("server.adventurebot.navigation", "C819_ROUTE_TIER tier=reachable-partial complete=0");
|
||||
return true;
|
||||
}
|
||||
TC_LOG_INFO("server.adventurebot.navigation", "C819_ROUTE_TIER tier=deferred-partial complete=%u", complete ? 1u : 0u);
|
||||
return secondOk;
|
||||
}
|
||||
|
||||
bool ClientlessTravelGraphMgr::PlanRouteToPosition(
|
||||
|
||||
Reference in new issue
Block a user