importet Areateleports as Nodes
This commit is contained in:
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;
|
||||
|
||||
Reference in new issue
Block a user