Implemented travel graph
This commit is contained in:
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;
|
||||
}
|
||||
Reference in new issue
Block a user