fixed travel to location
This commit is contained in:
1 parent
b14c864814
commit
4c04cd88de
7 files changed
+528
-4
No files matched your search
@@ -17,6 +17,7 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
@@ -127,6 +128,15 @@ bool ClientlessActionExecutorMgr::StartAction(Player* player, ClientlessAction c
|
||||
record.Status = {};
|
||||
record.Status.ActionName = action.Name;
|
||||
record.Status.State = ClientlessActionExecutionState::SelectingTarget;
|
||||
record.FinalLocalLegPending = false;
|
||||
record.FinalLocalLegActive = false;
|
||||
record.FinalMapId = 0;
|
||||
record.FinalX = 0.0f;
|
||||
record.FinalY = 0.0f;
|
||||
record.FinalZ = 0.0f;
|
||||
record.FinalO = 0.0f;
|
||||
record.FinalDatabaseId = 0;
|
||||
record.FinalLocationId.clear();
|
||||
|
||||
if (action.Name == "approach")
|
||||
return StartApproach(player, action, record);
|
||||
@@ -137,6 +147,9 @@ bool ClientlessActionExecutorMgr::StartAction(Player* player, ClientlessAction c
|
||||
if (action.Name == "travel_to_node")
|
||||
return StartTravelToNode(player, action, record);
|
||||
|
||||
if (action.Name == "travel_to_location")
|
||||
return StartTravelToLocation(player, action, record);
|
||||
|
||||
record.Status.State = ClientlessActionExecutionState::Failed;
|
||||
record.Status.Detail = "unsupported action";
|
||||
record.Active = false;
|
||||
@@ -496,6 +509,200 @@ bool ClientlessActionExecutorMgr::StartTravelToNode(
|
||||
return StartRouteStep(player, characterGuid, record);
|
||||
}
|
||||
|
||||
|
||||
bool ClientlessActionExecutorMgr::StartTravelToLocation(
|
||||
Player* player,
|
||||
ClientlessAction const& action,
|
||||
Record& record)
|
||||
{
|
||||
uint32 const characterGuid = player->GetGUID().GetCounter();
|
||||
|
||||
auto find = [&](char const* key) -> std::string const*
|
||||
{
|
||||
auto itr = action.Parameters.find(key);
|
||||
return itr != action.Parameters.end() ? &itr->second : nullptr;
|
||||
};
|
||||
|
||||
std::string locationId;
|
||||
if (std::string const* id = find("id"))
|
||||
locationId = *id;
|
||||
else if (std::string const* location = find("location"))
|
||||
locationId = *location;
|
||||
|
||||
if (locationId.empty())
|
||||
{
|
||||
record.Status.State = ClientlessActionExecutionState::Failed;
|
||||
record.Status.Detail = "travel_to_location requires id";
|
||||
record.Active = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
ClientlessWorldPoint const* point = sClientlessWorldPointMgr->FindById(locationId);
|
||||
if (!point)
|
||||
{
|
||||
record.Status.State = ClientlessActionExecutionState::Failed;
|
||||
record.Status.Detail = "unknown location: " + locationId;
|
||||
record.Active = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
float startRadius = 500.0f;
|
||||
float directRadius = 300.0f;
|
||||
float anchorRadius = 1000.0f;
|
||||
|
||||
if (std::string const* radius = find("start_radius"))
|
||||
startRadius = ParseFloat(*radius, startRadius);
|
||||
|
||||
// Backward compatibility with the first C8.9 API. Legacy local_radius
|
||||
// initializes both concepts; the new explicit parameters below win.
|
||||
if (std::string const* radius = find("local_radius"))
|
||||
{
|
||||
float const legacyRadius = ParseFloat(*radius, 0.0f);
|
||||
if (legacyRadius > 0.0f)
|
||||
{
|
||||
directRadius = legacyRadius;
|
||||
anchorRadius = legacyRadius;
|
||||
}
|
||||
}
|
||||
|
||||
if (std::string const* radius = find("direct_radius"))
|
||||
directRadius = ParseFloat(*radius, directRadius);
|
||||
|
||||
if (std::string const* radius = find("anchor_radius"))
|
||||
anchorRadius = ParseFloat(*radius, anchorRadius);
|
||||
|
||||
if (startRadius <= 0.0f)
|
||||
startRadius = 500.0f;
|
||||
if (directRadius <= 0.0f)
|
||||
directRadius = 300.0f;
|
||||
if (anchorRadius <= 0.0f)
|
||||
anchorRadius = 1000.0f;
|
||||
|
||||
record.FinalMapId = point->MapId;
|
||||
record.FinalX = point->X;
|
||||
record.FinalY = point->Y;
|
||||
record.FinalZ = point->Z;
|
||||
record.FinalO = point->O;
|
||||
record.FinalDatabaseId = point->DatabaseId;
|
||||
record.FinalLocationId = point->Id;
|
||||
record.Status.TargetName = "location:" + point->Id;
|
||||
record.Status.TargetGuid = 0;
|
||||
record.Status.TargetEntry = point->DatabaseId;
|
||||
|
||||
float directDistance = std::numeric_limits<float>::infinity();
|
||||
if (player->GetMapId() == point->MapId)
|
||||
{
|
||||
float const dx = player->GetPositionX() - point->X;
|
||||
float const dy = player->GetPositionY() - point->Y;
|
||||
float const dz = player->GetPositionZ() - point->Z;
|
||||
directDistance = std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
// Only genuinely local destinations may bypass the macro graph. The
|
||||
// destination anchor may intentionally be much farther from the exact
|
||||
// Location, so directRadius must stay independent from anchorRadius.
|
||||
if (directDistance <= directRadius)
|
||||
{
|
||||
record.FinalLocalLegPending = true;
|
||||
record.Active = true;
|
||||
return StartFinalLocalLeg(player, characterGuid, record);
|
||||
}
|
||||
|
||||
record.Route.clear();
|
||||
record.RouteStep = 0;
|
||||
record.EdgePath.clear();
|
||||
|
||||
std::string anchorNode;
|
||||
std::string error;
|
||||
if (!sClientlessTravelGraphMgr->PlanRouteToPosition(
|
||||
player,
|
||||
point->MapId,
|
||||
point->X,
|
||||
point->Y,
|
||||
point->Z,
|
||||
startRadius,
|
||||
anchorRadius,
|
||||
record.Route,
|
||||
anchorNode,
|
||||
error))
|
||||
{
|
||||
record.Status.State = ClientlessActionExecutionState::Failed;
|
||||
record.Status.Detail = error;
|
||||
record.Active = false;
|
||||
|
||||
TC_LOG_INFO("server.adventurebot",
|
||||
"[AdventureBot] travel-location-failed guid=%u name='%s' location='%s' reason='%s'",
|
||||
characterGuid,
|
||||
player->GetName().c_str(),
|
||||
locationId.c_str(),
|
||||
error.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
record.FinalLocalLegPending = true;
|
||||
record.Active = true;
|
||||
|
||||
TC_LOG_INFO("server.adventurebot",
|
||||
"[AdventureBot] travel-location-planned guid=%u name='%s' location='%s' anchor='%s' steps=%u directRadius=%.1f anchorRadius=%.1f",
|
||||
characterGuid,
|
||||
player->GetName().c_str(),
|
||||
point->Id.c_str(),
|
||||
anchorNode.c_str(),
|
||||
uint32(record.Route.size()),
|
||||
directRadius,
|
||||
anchorRadius);
|
||||
|
||||
return StartRouteStep(player, characterGuid, record);
|
||||
}
|
||||
|
||||
bool ClientlessActionExecutorMgr::StartFinalLocalLeg(
|
||||
Player* player,
|
||||
uint32 characterGuid,
|
||||
Record& record)
|
||||
{
|
||||
if (!player || player->GetMapId() != record.FinalMapId)
|
||||
{
|
||||
record.Status.State = ClientlessActionExecutionState::Failed;
|
||||
record.Status.Detail = "location final leg is on another map";
|
||||
record.Active = false;
|
||||
record.FinalLocalLegPending = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
Position destination;
|
||||
destination.Relocate(
|
||||
record.FinalX,
|
||||
record.FinalY,
|
||||
record.FinalZ,
|
||||
record.FinalO);
|
||||
|
||||
if (!sClientlessTravelMgr->StartTravel(player, destination))
|
||||
{
|
||||
record.Status.State = ClientlessActionExecutionState::Failed;
|
||||
record.Status.Detail = "location final local travel start failed";
|
||||
record.Active = false;
|
||||
record.FinalLocalLegPending = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
record.FinalLocalLegPending = false;
|
||||
record.FinalLocalLegActive = true;
|
||||
record.Status.State = ClientlessActionExecutionState::Moving;
|
||||
record.Status.TargetName = "location:" + record.FinalLocationId;
|
||||
record.Status.TargetGuid = 0;
|
||||
record.Status.TargetEntry = record.FinalDatabaseId;
|
||||
record.Status.Detail = "local navigation to final location";
|
||||
record.Active = true;
|
||||
|
||||
TC_LOG_INFO("server.adventurebot",
|
||||
"[AdventureBot] travel-location-local-leg guid=%u name='%s' location='%s'",
|
||||
characterGuid,
|
||||
player->GetName().c_str(),
|
||||
record.FinalLocationId.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientlessActionExecutorMgr::StartRouteStep(
|
||||
Player* player,
|
||||
uint32 characterGuid,
|
||||
@@ -503,6 +710,9 @@ bool ClientlessActionExecutorMgr::StartRouteStep(
|
||||
{
|
||||
if (record.RouteStep >= record.Route.size())
|
||||
{
|
||||
if (record.FinalLocalLegPending)
|
||||
return StartFinalLocalLeg(player, characterGuid, record);
|
||||
|
||||
record.Status.State = ClientlessActionExecutionState::Completed;
|
||||
record.Status.Detail = "travel route completed";
|
||||
record.Active = false;
|
||||
@@ -688,7 +898,11 @@ void ClientlessActionExecutorMgr::UpdateTravelToNode(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player->IsStopped())
|
||||
// A clientless Player can be reported as "stopped" while a server-
|
||||
// controlled MoveSpline is still running. Unit::UpdateSplineMovement()
|
||||
// owns the authoritative server position for this movement and updates
|
||||
// it until the spline is finalized. Do not evaluate arrival before then.
|
||||
if (player->IsSplineEnabled())
|
||||
return;
|
||||
|
||||
float dx = player->GetPositionX() - destination.X;
|
||||
@@ -740,6 +954,57 @@ void ClientlessActionExecutorMgr::UpdateTravelToNode(
|
||||
StartRouteStep(player, characterGuid, record);
|
||||
}
|
||||
|
||||
|
||||
void ClientlessActionExecutorMgr::UpdateTravelToLocation(
|
||||
Player* player,
|
||||
uint32 characterGuid,
|
||||
Record& record)
|
||||
{
|
||||
if (!record.FinalLocalLegActive)
|
||||
{
|
||||
UpdateTravelToNode(player, characterGuid, record);
|
||||
return;
|
||||
}
|
||||
|
||||
ClientlessTravelStatus const* travel = sClientlessTravelMgr->GetStatus(characterGuid);
|
||||
if (!travel)
|
||||
{
|
||||
record.Status.State = ClientlessActionExecutionState::Failed;
|
||||
record.Status.Detail = "location local travel state disappeared";
|
||||
record.Active = false;
|
||||
record.FinalLocalLegActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (travel->State == ClientlessTravelState::Moving)
|
||||
return;
|
||||
|
||||
if (travel->State == ClientlessTravelState::Arrived)
|
||||
{
|
||||
record.Status.State = ClientlessActionExecutionState::Completed;
|
||||
record.Status.TargetName = "location:" + record.FinalLocationId;
|
||||
record.Status.TargetGuid = 0;
|
||||
record.Status.TargetEntry = record.FinalDatabaseId;
|
||||
record.Status.Detail = "arrived at location";
|
||||
record.Active = false;
|
||||
record.FinalLocalLegActive = false;
|
||||
|
||||
TC_LOG_INFO("server.adventurebot",
|
||||
"[AdventureBot] travel-location-complete guid=%u name='%s' location='%s'",
|
||||
characterGuid,
|
||||
player->GetName().c_str(),
|
||||
record.FinalLocationId.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
record.Status.State = ClientlessActionExecutionState::Failed;
|
||||
record.Status.Detail =
|
||||
std::string("location local travel ended in state ") +
|
||||
ClientlessTravelMgr::StateName(travel->State);
|
||||
record.Active = false;
|
||||
record.FinalLocalLegActive = false;
|
||||
}
|
||||
|
||||
void ClientlessActionExecutorMgr::UpdateActive(
|
||||
Player* player,
|
||||
uint32 characterGuid,
|
||||
@@ -751,6 +1016,12 @@ void ClientlessActionExecutorMgr::UpdateActive(
|
||||
return;
|
||||
}
|
||||
|
||||
if (record.Status.ActionName == "travel_to_location")
|
||||
{
|
||||
UpdateTravelToLocation(player, characterGuid, record);
|
||||
return;
|
||||
}
|
||||
|
||||
ClientlessTravelStatus const* travel = sClientlessTravelMgr->GetStatus(characterGuid);
|
||||
if (!travel)
|
||||
{
|
||||
|
||||
@@ -53,15 +53,28 @@ private:
|
||||
std::size_t RouteStep = 0;
|
||||
|
||||
std::vector<ClientlessTravelEdgePoint> EdgePath;
|
||||
|
||||
bool FinalLocalLegPending = false;
|
||||
bool FinalLocalLegActive = false;
|
||||
uint32 FinalMapId = 0;
|
||||
float FinalX = 0.0f;
|
||||
float FinalY = 0.0f;
|
||||
float FinalZ = 0.0f;
|
||||
float FinalO = 0.0f;
|
||||
uint32 FinalDatabaseId = 0;
|
||||
std::string FinalLocationId;
|
||||
};
|
||||
|
||||
bool StartAction(Player* player, ClientlessAction const& action, Record& record);
|
||||
bool StartApproach(Player* player, ClientlessAction const& action, Record& record);
|
||||
bool StartMoveToPoint(Player* player, ClientlessAction const& action, Record& record);
|
||||
bool StartTravelToNode(Player* player, ClientlessAction const& action, Record& record);
|
||||
bool StartTravelToLocation(Player* player, ClientlessAction const& action, Record& record);
|
||||
bool StartFinalLocalLeg(Player* player, uint32 characterGuid, Record& record);
|
||||
bool StartRouteStep(Player* player, uint32 characterGuid, Record& record);
|
||||
bool StartRecordedEdgePath(Player* player, uint32 characterGuid, Record& record);
|
||||
void UpdateTravelToNode(Player* player, uint32 characterGuid, Record& record);
|
||||
void UpdateTravelToLocation(Player* player, uint32 characterGuid, Record& record);
|
||||
void UpdateActive(Player* player, uint32 characterGuid, Record& record);
|
||||
|
||||
std::unordered_map<uint32, Record> _records;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -82,6 +82,18 @@ public:
|
||||
std::vector<ClientlessTravelRouteStep>& route,
|
||||
std::string& error);
|
||||
|
||||
bool 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);
|
||||
|
||||
uint32 GetNodeCount() const { return uint32(_nodes.size()); }
|
||||
uint32 GetEdgeCount() const { return _edgeCount; }
|
||||
|
||||
|
||||
@@ -106,3 +106,17 @@ ClientlessWorldPoint const* ClientlessWorldPointMgr::FindNearest(
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
ClientlessWorldPoint const* ClientlessWorldPointMgr::FindById(std::string const& id)
|
||||
{
|
||||
if (id.empty())
|
||||
return nullptr;
|
||||
|
||||
EnsureLoaded();
|
||||
|
||||
for (ClientlessWorldPoint const& point : _points)
|
||||
if (point.Id == id)
|
||||
return &point;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -41,6 +41,8 @@ public:
|
||||
Player const* player,
|
||||
ClientlessWorldPointQuery const& query);
|
||||
|
||||
ClientlessWorldPoint const* FindById(std::string const& id);
|
||||
|
||||
uint32 GetPointCount() const { return uint32(_points.size()); }
|
||||
float GetDefaultSearchRadius() const { return DefaultSearchRadius; }
|
||||
|
||||
|
||||
@@ -9,9 +9,8 @@ intend("socialize", { city = "Ironforge", venue = "tavern", activity = "drink",
|
||||
-- max_distance = 40,
|
||||
-- approach_distance = 2
|
||||
--})
|
||||
queue_action("travel_to_node", {
|
||||
node = "goldshire_center",
|
||||
start_radius = 500
|
||||
queue_action("travel_to_location", {
|
||||
id = "goldshire_tavern"
|
||||
})
|
||||
-- C8.6 incremental travel-graph example.
|
||||
-- Enable after the corresponding graph nodes/edges exist in cata_world.
|
||||
|
||||
Reference in new issue
Block a user