Implemented travel graph
clang PCH / build (push) Canceled after 0s
GCC PCH / build (push) Canceled after 0s

This commit is contained in:
Nico committed 2026-10-13 19:39:53 +00:00
1 parent 05fd748537
commit b14c864814
19 files changed
+3835

No files matched your search

@@ -0,0 +1,337 @@
#include "ClientlessTravelGraphMgr.h"
#include "DatabaseEnv.h"
#include "Log.h"
#include "Player.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <queue>
#include <utility>
ClientlessTravelGraphMgr* ClientlessTravelGraphMgr::instance()
{
static ClientlessTravelGraphMgr instance;
return &instance;
}
bool ClientlessTravelGraphMgr::EnsureLoaded()
{
if (_loaded)
return true;
return Reload();
}
bool ClientlessTravelGraphMgr::Reload()
{
_nodes.clear();
_edges.clear();
_edgePoints.clear();
_edgeCount = 0;
QueryResult nodeResult = WorldDatabase.Query(
"SELECT n.id, n.node_key, n.node_type, n.world_point_key, "
"w.map, w.position_x, w.position_y, w.position_z, w.orientation "
"FROM vcore_clientless_travel_node n "
"INNER JOIN vcore_clientless_world_point w "
"ON w.point_key = n.world_point_key "
"WHERE n.enabled = 1 AND w.enabled = 1 "
"ORDER BY n.id");
if (nodeResult)
{
do
{
Field* fields = nodeResult->Fetch();
ClientlessTravelNode node;
node.DatabaseId = fields[0].GetUInt32();
node.Key = fields[1].GetString();
node.Type = fields[2].GetString();
node.WorldPointKey = fields[3].GetString();
node.MapId = fields[4].GetUInt16();
node.X = fields[5].GetFloat();
node.Y = fields[6].GetFloat();
node.Z = fields[7].GetFloat();
node.O = fields[8].GetFloat();
if (!node.Key.empty())
_nodes[node.Key] = std::move(node);
}
while (nodeResult->NextRow());
}
QueryResult edgeResult = WorldDatabase.Query(
"SELECT id, from_node_key, to_node_key, edge_type, cost, bidirectional "
"FROM vcore_clientless_travel_edge "
"WHERE enabled = 1 "
"ORDER BY id");
if (edgeResult)
{
do
{
Field* fields = edgeResult->Fetch();
ClientlessTravelEdge edge;
edge.DatabaseId = fields[0].GetUInt32();
edge.FromNode = fields[1].GetString();
edge.ToNode = fields[2].GetString();
edge.Type = fields[3].GetString();
edge.Cost = fields[4].GetFloat();
bool const bidirectional = fields[5].GetBool();
if (_nodes.find(edge.FromNode) == _nodes.end() ||
_nodes.find(edge.ToNode) == _nodes.end())
{
TC_LOG_WARN("server.adventurebot",
"[AdventureBot] ignoring travel edge id=%u from='%s' to='%s': node missing",
edge.DatabaseId, edge.FromNode.c_str(), edge.ToNode.c_str());
continue;
}
if (edge.Type.empty())
edge.Type = "ground";
if (edge.Cost <= 0.0f)
edge.Cost = 1.0f;
_edges[edge.FromNode].push_back(edge);
++_edgeCount;
if (bidirectional)
{
ClientlessTravelEdge reverse = edge;
reverse.FromNode = edge.ToNode;
reverse.ToNode = edge.FromNode;
reverse.Reverse = true;
_edges[reverse.FromNode].push_back(std::move(reverse));
++_edgeCount;
}
}
while (edgeResult->NextRow());
}
QueryResult pointResult = WorldDatabase.Query(
"SELECT edge_id, point_index, map, position_x, position_y, position_z, orientation "
"FROM vcore_clientless_travel_edge_point "
"ORDER BY edge_id, point_index");
if (pointResult)
{
do
{
Field* fields = pointResult->Fetch();
ClientlessTravelEdgePoint point;
uint32 edgeId = fields[0].GetUInt32();
point.MapId = fields[2].GetUInt16();
point.X = fields[3].GetFloat();
point.Y = fields[4].GetFloat();
point.Z = fields[5].GetFloat();
point.O = fields[6].GetFloat();
_edgePoints[edgeId].push_back(point);
}
while (pointResult->NextRow());
}
_loaded = true;
TC_LOG_INFO("server.adventurebot",
"[AdventureBot] loaded travel graph: %u node(s), %u directed edge(s).",
uint32(_nodes.size()), _edgeCount);
return !_nodes.empty();
}
std::string ClientlessTravelGraphMgr::FindNearestStartNode(
Player const* player,
float maxDistance) const
{
if (!player || !player->IsInWorld())
return {};
std::string bestKey;
float bestDistance = std::numeric_limits<float>::max();
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 distance = std::sqrt(dx * dx + dy * dy + dz * dz);
if (distance > maxDistance || distance >= bestDistance)
continue;
bestDistance = distance;
bestKey = pair.first;
}
return bestKey;
}
bool ClientlessTravelGraphMgr::PlanRoute(
Player const* player,
std::string const& destinationNode,
float startRadius,
std::vector<ClientlessTravelRouteStep>& route,
std::string& error)
{
route.clear();
error.clear();
EnsureLoaded();
if (!player || !player->IsInWorld())
{
error = "player is not in world";
return false;
}
if (_nodes.find(destinationNode) == _nodes.end())
{
error = "unknown destination node: " + destinationNode;
return false;
}
if (startRadius <= 0.0f)
startRadius = 500.0f;
std::string const startNode = FindNearestStartNode(player, startRadius);
if (startNode.empty())
{
error = "no travel node near current position";
return false;
}
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();
distance[startNode] = 0.0f;
open.push({ 0.0f, startNode });
while (!open.empty())
{
QueueItem current = open.top();
open.pop();
if (current.Cost != distance[current.Node])
continue;
if (current.Node == destinationNode)
break;
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 });
}
}
if (!std::isfinite(distance[destinationNode]))
{
error = "no known route from " + startNode + " to " + destinationNode;
return false;
}
std::vector<std::string> nodeKeys;
std::string cursor = destinationNode;
nodeKeys.push_back(cursor);
while (cursor != startNode)
{
auto previousItr = previous.find(cursor);
if (previousItr == previous.end())
{
error = "route reconstruction failed";
return false;
}
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
{
auto itr = _edgePoints.find(edgeDatabaseId);
return itr != _edgePoints.end() ? &itr->second : nullptr;
}