importet Areateleports as Nodes
clang PCH / build (push) Canceled after 0s
GCC PCH / build (push) Canceled after 0s

This commit is contained in:
Nico committed 2026-10-16 09:36:33 +00:00
1 parent 93c3df1c95
commit fe7b5e0f02
12 files changed
+2087 -218

No files matched your search

@@ -184,7 +184,8 @@ bool ClientlessTravelGraphMgr::PlanRoute(
std::string const& destinationNode,
float startRadius,
std::vector<ClientlessTravelRouteStep>& route,
std::string& error)
std::string& error,
std::unordered_set<uint32> const* blockedEdgeIds)
{
route.clear();
error.clear();
@@ -259,6 +260,11 @@ bool ClientlessTravelGraphMgr::PlanRoute(
for (ClientlessTravelEdge const& edge : edgeItr->second)
{
if (blockedEdgeIds &&
edge.DatabaseId &&
blockedEdgeIds->find(edge.DatabaseId) != blockedEdgeIds->end())
continue;
float const candidate = current.Cost + edge.Cost;
if (candidate >= distance[edge.ToNode])
continue;
@@ -329,6 +335,381 @@ bool ClientlessTravelGraphMgr::PlanRoute(
}
namespace
{
std::string MakeLocalBridgeKey(std::string const& from, std::string const& to)
{
return from + "->" + to;
}
}
bool ClientlessTravelGraphMgr::FindNearestNodeToPosition(
uint32 mapId,
float x,
float y,
float z,
std::string& nodeKey,
float& resultDistance) const
{
nodeKey.clear();
resultDistance = std::numeric_limits<float>::infinity();
for (auto const& pair : _nodes)
{
ClientlessTravelNode const& node = pair.second;
if (node.MapId != mapId)
continue;
float const dx = node.X - x;
float const dy = node.Y - y;
float const dz = node.Z - z;
float const distance = std::sqrt(dx * dx + dy * dy + dz * dz);
if (distance >= resultDistance)
continue;
resultDistance = distance;
nodeKey = node.Key;
}
return !nodeKey.empty();
}
bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
Player const* player,
std::string const& destinationNode,
float startRadius,
float bridgeRadius,
std::vector<ClientlessTravelRouteStep>& route,
bool& complete,
std::string& reachedNode,
std::string& error,
std::unordered_set<uint32> const* blockedEdgeIds,
std::unordered_set<std::string> const* blockedBridgeKeys)
{
route.clear();
reachedNode.clear();
error.clear();
complete = false;
EnsureLoaded();
if (!player || !player->IsInWorld())
{
error = "player is not in world";
return false;
}
auto destinationItr = _nodes.find(destinationNode);
if (destinationItr == _nodes.end())
{
error = "unknown destination node: " + destinationNode;
return false;
}
if (startRadius <= 0.0f)
startRadius = 500.0f;
if (bridgeRadius <= 0.0f)
bridgeRadius = 300.0f;
struct State
{
std::string Node;
bool LastWasBridge = false;
};
auto stateKey = [](State const& state)
{
return state.Node + (state.LastWasBridge ? "\x1f" "B" : "\x1f" "R");
};
struct QueueItem
{
float Cost = 0.0f;
State Value;
bool operator>(QueueItem const& other) const { return Cost > other.Cost; }
};
struct Previous
{
std::string StateKey;
std::string Node;
std::string EdgeType;
uint32 EdgeDatabaseId = 0;
bool EdgeReverse = false;
};
std::priority_queue<QueueItem, std::vector<QueueItem>, std::greater<QueueItem>> open;
std::unordered_map<std::string, float> distance;
std::unordered_map<std::string, Previous> previous;
std::unordered_map<std::string, State> states;
std::unordered_map<std::string, uint32> persistentHops;
uint32 startCount = 0;
for (auto const& pair : _nodes)
{
ClientlessTravelNode const& node = pair.second;
if (node.MapId != player->GetMapId())
continue;
float const dx = node.X - player->GetPositionX();
float const dy = node.Y - player->GetPositionY();
float const dz = node.Z - player->GetPositionZ();
float const localDistance = std::sqrt(dx * dx + dy * dy + dz * dz);
if (localDistance > startRadius)
continue;
State state{ node.Key, false };
std::string const key = stateKey(state);
auto found = distance.find(key);
if (found != distance.end() && found->second <= localDistance)
continue;
distance[key] = localDistance;
states[key] = state;
persistentHops[key] = 0;
open.push({ localDistance, state });
++startCount;
}
if (!startCount)
{
error = "no travel node near current position";
return false;
}
std::string bestGoalState;
while (!open.empty())
{
QueueItem current = open.top();
open.pop();
std::string const currentKey = stateKey(current.Value);
auto distanceItr = distance.find(currentKey);
if (distanceItr == distance.end() || current.Cost != distanceItr->second)
continue;
if (current.Value.Node == destinationNode)
{
bestGoalState = currentKey;
break;
}
// Persistent TravelGraph edges always reset the bridge guard.
auto edgeItr = _edges.find(current.Value.Node);
if (edgeItr != _edges.end())
{
for (ClientlessTravelEdge const& edge : edgeItr->second)
{
if (blockedEdgeIds && edge.DatabaseId && blockedEdgeIds->count(edge.DatabaseId))
continue;
State next{ edge.ToNode, false };
std::string const nextKey = stateKey(next);
float const candidate = current.Cost + edge.Cost;
auto old = distance.find(nextKey);
if (old != distance.end() && candidate >= old->second)
continue;
distance[nextKey] = candidate;
states[nextKey] = next;
persistentHops[nextKey] = persistentHops[currentKey] + 1;
previous[nextKey] = {
currentKey, current.Value.Node, edge.Type,
edge.DatabaseId, edge.Reverse
};
open.push({ candidate, next });
}
}
// A local bridge is deliberately impossible immediately after another
// local bridge. This prevents MMAP from silently becoming a long-range
// planner while still allowing unlimited bridges over a long journey
// when real transport/ground edges lie between them.
if (current.Value.LastWasBridge)
continue;
ClientlessTravelNode const& from = _nodes.at(current.Value.Node);
for (auto const& pair : _nodes)
{
ClientlessTravelNode const& to = pair.second;
if (to.Key == from.Key || to.MapId != from.MapId)
continue;
float const dx = to.X - from.X;
float const dy = to.Y - from.Y;
float const dz = to.Z - from.Z;
float const direct = std::sqrt(dx * dx + dy * dy + dz * dz);
if (direct > bridgeRadius)
continue;
std::string const bridgeKey = MakeLocalBridgeKey(from.Key, to.Key);
if (blockedBridgeKeys && blockedBridgeKeys->count(bridgeKey))
continue;
// A bridge must lead back into real TravelGraph infrastructure.
// The sole exception is the actual requested destination, where a
// local bridge may be the final leg of the graph journey.
bool hasPersistentContinuation = to.Key == destinationNode;
auto continuationItr = _edges.find(to.Key);
if (!hasPersistentContinuation && continuationItr != _edges.end())
{
for (ClientlessTravelEdge const& continuation : continuationItr->second)
{
if (blockedEdgeIds && continuation.DatabaseId && blockedEdgeIds->count(continuation.DatabaseId))
continue;
hasPersistentContinuation = true;
break;
}
}
if (!hasPersistentContinuation)
continue;
State next{ to.Key, true };
std::string const nextKey = stateKey(next);
// Large fixed penalty makes a real graph path preferable whenever
// one exists; distance then ranks alternative local bridges.
float const candidate = current.Cost + 1000.0f + direct;
auto old = distance.find(nextKey);
if (old != distance.end() && candidate >= old->second)
continue;
distance[nextKey] = candidate;
states[nextKey] = next;
persistentHops[nextKey] = persistentHops[currentKey];
previous[nextKey] = {
currentKey, current.Value.Node, "local_bridge", 0, false
};
open.push({ candidate, next });
}
}
std::string selectedState = bestGoalState;
if (!selectedState.empty())
complete = true;
if (selectedState.empty())
{
ClientlessTravelNode const& destination = destinationItr->second;
float bestSameMapDistance = std::numeric_limits<float>::infinity();
uint32 bestPersistentHops = 0;
float bestCost = std::numeric_limits<float>::infinity();
// Prefer any reachable point on the destination map. This gives a
// meaningful geographic partial route (e.g. Ironforge when Kharanos
// itself is disconnected). If the destination map was never reached,
// fall back to the state that made the most progress over real edges.
for (auto const& pair : states)
{
auto distItr = distance.find(pair.first);
if (distItr == distance.end() || !std::isfinite(distItr->second))
continue;
ClientlessTravelNode const& node = _nodes.at(pair.second.Node);
if (node.MapId != destination.MapId)
continue;
float const dx = node.X - destination.X;
float const dy = node.Y - destination.Y;
float const dz = node.Z - destination.Z;
float const localDistance = std::sqrt(dx * dx + dy * dy + dz * dz);
if (localDistance < bestSameMapDistance ||
(std::fabs(localDistance - bestSameMapDistance) < 0.5f && distItr->second < bestCost))
{
bestSameMapDistance = localDistance;
bestCost = distItr->second;
selectedState = pair.first;
}
}
if (selectedState.empty())
{
for (auto const& pair : states)
{
auto distItr = distance.find(pair.first);
if (distItr == distance.end() || !std::isfinite(distItr->second))
continue;
uint32 const hops = persistentHops[pair.first];
if (hops > bestPersistentHops ||
(hops == bestPersistentHops && distItr->second < bestCost))
{
bestPersistentHops = hops;
bestCost = distItr->second;
selectedState = pair.first;
}
}
}
}
if (selectedState.empty())
{
error = "no adaptive route candidate";
return false;
}
reachedNode = states.at(selectedState).Node;
struct Reconstructed
{
State Value;
std::string IncomingEdgeType;
uint32 EdgeDatabaseId = 0;
bool EdgeReverse = false;
};
std::vector<Reconstructed> reversed;
std::string cursor = selectedState;
while (true)
{
State const& state = states.at(cursor);
auto prevItr = previous.find(cursor);
if (prevItr == previous.end())
{
reversed.push_back({ state, "start", 0, false });
break;
}
reversed.push_back({
state,
prevItr->second.EdgeType,
prevItr->second.EdgeDatabaseId,
prevItr->second.EdgeReverse
});
cursor = prevItr->second.StateKey;
}
std::reverse(reversed.begin(), reversed.end());
route.reserve(reversed.size());
for (Reconstructed const& item : reversed)
{
ClientlessTravelNode const& node = _nodes.at(item.Value.Node);
ClientlessTravelRouteStep step;
step.NodeKey = node.Key;
step.NodeType = node.Type;
step.WorldPointKey = node.WorldPointKey;
step.EdgeType = item.IncomingEdgeType;
step.EdgeDatabaseId = item.EdgeDatabaseId;
step.EdgeReverse = item.EdgeReverse;
step.MapId = node.MapId;
step.X = node.X;
step.Y = node.Y;
step.Z = node.Z;
step.O = node.O;
route.push_back(std::move(step));
}
if (!complete)
error = "destination not fully reachable; returning best-effort partial route";
return !route.empty();
}
bool ClientlessTravelGraphMgr::PlanRouteToPosition(
Player const* player,
uint32 destinationMapId,
@@ -339,7 +720,8 @@ bool ClientlessTravelGraphMgr::PlanRouteToPosition(
float anchorRadius,
std::vector<ClientlessTravelRouteStep>& route,
std::string& anchorNode,
std::string& error)
std::string& error,
std::unordered_set<uint32> const* blockedEdgeIds)
{
route.clear();
anchorNode.clear();
@@ -429,6 +811,11 @@ bool ClientlessTravelGraphMgr::PlanRouteToPosition(
for (ClientlessTravelEdge const& edge : edgeItr->second)
{
if (blockedEdgeIds &&
edge.DatabaseId &&
blockedEdgeIds->find(edge.DatabaseId) != blockedEdgeIds->end())
continue;
float const candidate = current.Cost + edge.Cost;
if (candidate >= distance[edge.ToNode])
continue;