fixed travel to location
clang PCH / build (push) Canceled after 0s
GCC PCH / build (push) Canceled after 0s

This commit is contained in:
Nico committed 2026-10-14 13:07:04 +00:00
1 parent b14c864814
commit 4c04cd88de
7 files changed
+528 -4

No files matched your search

@@ -329,6 +329,219 @@ bool ClientlessTravelGraphMgr::PlanRoute(
}
bool ClientlessTravelGraphMgr::PlanRouteToPosition(
Player const* player,
uint32 destinationMapId,
float destinationX,
float destinationY,
float destinationZ,
float startRadius,
float anchorRadius,
std::vector<ClientlessTravelRouteStep>& route,
std::string& anchorNode,
std::string& error)
{
route.clear();
anchorNode.clear();
error.clear();
EnsureLoaded();
if (!player || !player->IsInWorld())
{
error = "player is not in world";
return false;
}
if (startRadius <= 0.0f)
startRadius = 500.0f;
if (anchorRadius <= 0.0f)
anchorRadius = 1000.0f;
struct QueueItem
{
float Cost;
std::string Node;
bool operator>(QueueItem const& other) const
{
return Cost > other.Cost;
}
};
struct Previous
{
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;
for (auto const& pair : _nodes)
distance[pair.first] = std::numeric_limits<float>::infinity();
// Multi-source Dijkstra: every graph node close enough to the player's
// actual position is a possible entry point. Seed cost includes the local
// distance needed to reach that node, so nearer usable entries are still
// preferred without letting an isolated node block the route entirely.
uint32 startCandidateCount = 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 localStartDistance = std::sqrt(dx * dx + dy * dy + dz * dz);
if (localStartDistance > startRadius)
continue;
distance[node.Key] = localStartDistance;
open.push({ localStartDistance, node.Key });
++startCandidateCount;
}
if (startCandidateCount == 0)
{
error = "no travel node near current position";
return false;
}
while (!open.empty())
{
QueueItem current = open.top();
open.pop();
if (current.Cost != distance[current.Node])
continue;
auto edgeItr = _edges.find(current.Node);
if (edgeItr == _edges.end())
continue;
for (ClientlessTravelEdge const& edge : edgeItr->second)
{
float const candidate = current.Cost + edge.Cost;
if (candidate >= distance[edge.ToNode])
continue;
distance[edge.ToNode] = candidate;
previous[edge.ToNode] = {
current.Node, edge.Type, edge.DatabaseId, edge.Reverse
};
open.push({ candidate, edge.ToNode });
}
}
// The destination anchor's primary job is to get the bot close to the
// requested Location. Do not combine macro-route cost and final local
// distance into one score: that can make the current/start node win simply
// because reaching it costs zero, leaving MMAP with a very long final leg.
//
// Instead choose the reachable node closest to the destination first.
// Graph cost is only a tie-breaker between equally suitable anchors.
float bestDestinationDistance = std::numeric_limits<float>::infinity();
float bestGraphCost = std::numeric_limits<float>::infinity();
for (auto const& pair : _nodes)
{
ClientlessTravelNode const& node = pair.second;
if (node.MapId != destinationMapId)
continue;
auto distanceItr = distance.find(node.Key);
if (distanceItr == distance.end() || !std::isfinite(distanceItr->second))
continue;
float const dx = node.X - destinationX;
float const dy = node.Y - destinationY;
float const dz = node.Z - destinationZ;
float const localDestinationDistance = std::sqrt(dx * dx + dy * dy + dz * dz);
if (localDestinationDistance > anchorRadius)
continue;
float const graphCost = distanceItr->second;
constexpr float DistanceTieEpsilon = 0.5f;
bool const closerToDestination =
localDestinationDistance + DistanceTieEpsilon < bestDestinationDistance;
bool const sameDestinationDistance =
std::fabs(localDestinationDistance - bestDestinationDistance) <= DistanceTieEpsilon;
if (!closerToDestination &&
!(sameDestinationDistance && graphCost < bestGraphCost))
continue;
bestDestinationDistance = localDestinationDistance;
bestGraphCost = graphCost;
anchorNode = node.Key;
}
if (anchorNode.empty())
{
error = "no reachable travel node near destination";
return false;
}
// Reconstruct until we reach whichever seeded start candidate actually won.
// Seed roots deliberately have no Previous entry.
std::vector<std::string> nodeKeys;
std::string cursor = anchorNode;
nodeKeys.push_back(cursor);
while (true)
{
auto previousItr = previous.find(cursor);
if (previousItr == previous.end())
break;
cursor = previousItr->second.Node;
nodeKeys.push_back(cursor);
}
std::reverse(nodeKeys.begin(), nodeKeys.end());
route.reserve(nodeKeys.size());
for (std::size_t i = 0; i < nodeKeys.size(); ++i)
{
ClientlessTravelNode const& node = _nodes.at(nodeKeys[i]);
ClientlessTravelRouteStep step;
step.NodeKey = node.Key;
step.NodeType = node.Type;
step.WorldPointKey = node.WorldPointKey;
step.MapId = node.MapId;
step.X = node.X;
step.Y = node.Y;
step.Z = node.Z;
step.O = node.O;
if (i == 0)
step.EdgeType = "start";
else
{
Previous const& prev = previous.at(nodeKeys[i]);
step.EdgeType = prev.EdgeType;
step.EdgeDatabaseId = prev.EdgeDatabaseId;
step.EdgeReverse = prev.EdgeReverse;
}
route.push_back(std::move(step));
}
return true;
}
std::vector<ClientlessTravelEdgePoint> const*
ClientlessTravelGraphMgr::GetEdgePoints(uint32 edgeDatabaseId) const
{