added virtual entry nodes
clang PCH / build (push) Canceled after 0s
GCC PCH / build (push) Canceled after 0s

This commit is contained in:
Nico committed 2026-10-17 16:45:58 +00:00
1 parent 6e17dedff2
commit ae2a4a0f08
3 files changed
+185 -3

No files matched your search

@@ -3,6 +3,7 @@
#include "DatabaseEnv.h"
#include "Log.h"
#include "Player.h"
#include "ClientlessTravelMgr.h"
#include <algorithm>
#include <cmath>
@@ -445,6 +446,16 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
std::unordered_map<std::string, State> states;
std::unordered_map<std::string, uint32> persistentHops;
// VCORE_VIRTUAL_GROUND_EDGE_ENTRY_V1: a root may be a virtual recorded-road entry.
struct VirtualEntry
{
uint32 EdgeId = 0;
uint32 PointIndex = 0; // original order of _edgePoints
bool Reverse = false;
ClientlessTravelEdgePoint Point;
};
std::unordered_map<std::string, VirtualEntry> virtualRoots;
uint32 startCount = 0;
for (auto const& pair : _nodes)
{
@@ -472,9 +483,122 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
++startCount;
}
if (!startCount)
// Use the nearest ten nodes merely to shortlist real recorded ground edges.
// All distances below are on the player's current map; no database mutation.
std::vector<std::pair<float, std::string>> nearestNodes;
for (auto const& pair : _nodes)
{
error = "no travel node near current position";
ClientlessTravelNode const& node = pair.second;
if (node.MapId != player->GetMapId())
continue;
float dx = node.X - player->GetPositionX();
float dy = node.Y - player->GetPositionY();
float dz = node.Z - player->GetPositionZ();
nearestNodes.emplace_back(dx * dx + dy * dy + dz * dz, pair.first);
}
std::sort(nearestNodes.begin(), nearestNodes.end());
if (nearestNodes.size() > 10)
nearestNodes.resize(10);
struct EntryCandidate
{
float DistanceSq;
ClientlessTravelEdge Edge;
uint32 Index;
ClientlessTravelEdgePoint Point;
uint32 Count;
};
std::vector<EntryCandidate> entryCandidates;
std::unordered_set<std::string> visitedDirectedEdges;
for (auto const& near : nearestNodes)
{
// Both outgoing and incoming records are considered for spatial discovery.
// A directed entry still follows only the direction present in _edges.
for (auto const& edgePair : _edges)
{
for (ClientlessTravelEdge const& edge : edgePair.second)
{
if (edge.Type != "ground" || !edge.DatabaseId ||
(edge.FromNode != near.second && edge.ToNode != near.second))
continue;
if (blockedEdgeIds && blockedEdgeIds->count(edge.DatabaseId))
continue;
std::string key = std::to_string(edge.DatabaseId) + (edge.Reverse ? "R" : "F");
if (!visitedDirectedEdges.insert(key).second)
continue;
auto pointIt = _edgePoints.find(edge.DatabaseId);
if (pointIt == _edgePoints.end() || pointIt->second.size() < 3)
continue;
std::vector<ClientlessTravelEdgePoint> const& points = pointIt->second;
// Candidates are stored in original point order, even for reverse travel.
for (uint32 i = 0; i < points.size(); ++i)
{
if ((!edge.Reverse && i + 1 >= points.size()) || (edge.Reverse && i == 0))
continue;
ClientlessTravelEdgePoint const& point = points[i];
if (point.MapId != player->GetMapId())
continue;
float dx = point.X - player->GetPositionX();
float dy = point.Y - player->GetPositionY();
float dz = point.Z - player->GetPositionZ();
float dsq = dx * dx + dy * dy + dz * dz;
// Keep local MMAP checks bounded; startRadius is a planning limit.
if (dsq > startRadius * startRadius)
continue;
entryCandidates.push_back({ dsq, edge, i, point, uint32(points.size()) });
}
}
}
}
std::sort(entryCandidates.begin(), entryCandidates.end(),
[](EntryCandidate const& a, EntryCandidate const& b) { return a.DistanceSq < b.DistanceSq; });
uint32 validated = 0;
uint32 accepted = 0;
for (EntryCandidate const& candidate : entryCandidates)
{
if (validated >= 24)
break;
++validated;
Position target;
target.Relocate(candidate.Point.X, candidate.Point.Y, candidate.Point.Z, candidate.Point.O);
ClientlessPathDiagnostics nav;
// A point on top of the player needs no pathfinder round trip.
bool const alreadyThere = candidate.DistanceSq <= 2.25f;
if (!alreadyThere)
{
sClientlessTravelMgr->AnalyzePath(const_cast<Player*>(player), target, nav);
if (!nav.Calculated || !nav.Complete || nav.Suspicious ||
nav.FinalDistance > 1.5f || nav.PathLength > 500.0f)
continue;
}
State state{ candidate.Edge.ToNode, false };
std::string key = stateKey(state);
float remainingFraction = candidate.Edge.Reverse
? float(candidate.Index) / float(candidate.Count - 1)
: float(candidate.Count - candidate.Index - 1) / float(candidate.Count - 1);
float newCost = (alreadyThere ? 0.0f : nav.PathLength) + candidate.Edge.Cost * remainingFraction;
auto old = distance.find(key);
if (old != distance.end() && old->second <= newCost)
continue;
distance[key] = newCost;
states[key] = state;
persistentHops[key] = 1;
previous.erase(key);
virtualRoots[key] = { candidate.Edge.DatabaseId, candidate.Index,
candidate.Edge.Reverse, candidate.Point };
open.push({ newCost, state });
++accepted;
}
if (accepted)
TC_LOG_INFO("server.adventurebot.navigation",
"VIRTUAL_EDGE_CANDIDATES map=%u tested=%u accepted=%u",
player->GetMapId(), validated, accepted);
if (!startCount && !accepted)
{
error = "no reachable travel node or recorded ground edge near current position";
return false;
}
@@ -684,7 +808,31 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
}
std::reverse(reversed.begin(), reversed.end());
route.reserve(reversed.size());
route.reserve(reversed.size() + 1);
// If the shortest root entered a recorded edge mid-way, precede the actual
// endpoint with an MMAP-reachable virtual waypoint; do NOT rewrite the DB edge.
if (!reversed.empty())
{
std::string rootKey = stateKey(reversed.front().Value);
auto root = virtualRoots.find(rootKey);
if (root != virtualRoots.end() && previous.find(rootKey) == previous.end())
{
VirtualEntry const& entry = root->second;
ClientlessTravelRouteStep initial;
initial.NodeKey = "virtual_ground_entry";
initial.NodeType = "road";
initial.EdgeType = "start";
initial.MapId = entry.Point.MapId;
initial.X = entry.Point.X;
initial.Y = entry.Point.Y;
initial.Z = entry.Point.Z;
initial.O = entry.Point.O;
route.push_back(initial);
TC_LOG_INFO("server.adventurebot.navigation",
"VIRTUAL_EDGE_SELECTED edgeDbId=%u pointIndex=%u reverse=%u",
entry.EdgeId, entry.PointIndex, entry.Reverse ? 1u : 0u);
}
}
for (Reconstructed const& item : reversed)
{
@@ -696,6 +844,20 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
step.EdgeType = item.IncomingEdgeType;
step.EdgeDatabaseId = item.EdgeDatabaseId;
step.EdgeReverse = item.EdgeReverse;
if (route.size() == 1 && route.front().NodeKey == "virtual_ground_entry" &&
item.IncomingEdgeType == "start")
{
auto root = virtualRoots.find(stateKey(item.Value));
if (root != virtualRoots.end())
{
step.EdgeType = "ground";
step.EdgeDatabaseId = root->second.EdgeId;
step.EdgeReverse = root->second.Reverse;
step.GroundStartIndex = root->second.Reverse
? int32(_edgePoints.at(root->second.EdgeId).size() - 1 - root->second.PointIndex)
: int32(root->second.PointIndex);
}
}
step.MapId = node.MapId;
step.X = node.X;
step.Y = node.Y;