added virtual entry nodes
This commit is contained in:
1 parent
6e17dedff2
commit
ae2a4a0f08
3 files changed
+185
-3
No files matched your search
@@ -1244,6 +1244,23 @@ bool ClientlessActionExecutorMgr::StartRouteStep(
|
||||
record.EdgePath = *points;
|
||||
if (step.EdgeReverse)
|
||||
std::reverse(record.EdgePath.begin(), record.EdgePath.end());
|
||||
// VCORE_VIRTUAL_GROUND_EDGE_ENTRY_V1: keep original recorded points,
|
||||
// only discard the portion before the validated virtual entry.
|
||||
if (step.GroundStartIndex >= 0)
|
||||
{
|
||||
std::size_t const offset = std::size_t(step.GroundStartIndex);
|
||||
if (offset >= record.EdgePath.size())
|
||||
{
|
||||
record.Status.State = ClientlessActionExecutionState::Failed;
|
||||
record.Status.Detail = "virtual ground entry index outside recorded edge";
|
||||
record.Active = false;
|
||||
return false;
|
||||
}
|
||||
record.EdgePath.erase(record.EdgePath.begin(), record.EdgePath.begin() + offset);
|
||||
TC_LOG_INFO("server.adventurebot.navigation",
|
||||
"VIRTUAL_EDGE_EXEC edgeDbId=%u pointIndex=%u remainingPoints=%u",
|
||||
step.EdgeDatabaseId, uint32(offset), uint32(record.EdgePath.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -66,6 +66,9 @@ struct TC_GAME_API ClientlessTravelRouteStep
|
||||
std::string EdgeType;
|
||||
uint32 EdgeDatabaseId = 0;
|
||||
bool EdgeReverse = false;
|
||||
// VCORE_VIRTUAL_GROUND_EDGE_ENTRY_V1: index in execution-direction edge points.
|
||||
// -1 means replay the entire recorded ground edge as before.
|
||||
int32 GroundStartIndex = -1;
|
||||
|
||||
uint32 MapId = 0;
|
||||
float X = 0.0f;
|
||||
|
||||
Reference in new issue
Block a user