mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
765 lines
24 KiB
C++
765 lines
24 KiB
C++
/*
|
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
*/
|
|
|
|
#ifndef _PLAYERBOT_TRAVELNODE_H
|
|
#define _PLAYERBOT_TRAVELNODE_H
|
|
|
|
#include <shared_mutex>
|
|
|
|
#include "G3D/Vector3.h"
|
|
#include "TravelMgr.h"
|
|
|
|
// THEORY
|
|
//
|
|
// Pathfinding uses the detour recast navmesh engine for mob, npc, and bot movement.
|
|
// Because mobs and npc movement is based on following a player or a set path the PathGenerator is limited to 296y.
|
|
// This means that when trying to find a path from A to B distances beyond 296y will be a best guess often moving in a
|
|
// straight path. Bots would get stuck moving from Northshire to Stormwind because there is no 296y path that doesn't
|
|
// go (initially) the wrong direction.
|
|
//
|
|
// To remedy this limitation without altering the PathGenerator limits too much this node system was introduced.
|
|
//
|
|
// <S> ---> [N1] ---> [N2] ---> [N3] ---> <E>
|
|
//
|
|
// Bot at <S> wants to move to <E>
|
|
// [N1],[N2],[N3] are predefined nodes for which we know we can move from [N1] to [N2] and from [N2] to [N3] but not
|
|
// from [N1] to [N3]. If we can move from [S] to [N1] and from [N3] to [E] we have a complete route to travel.
|
|
//
|
|
// Terminology:
|
|
// Node: A location on a map for which we know bots are likely to want to travel to or need to travel past to reach
|
|
// other nodes. Stored in DB table `playerbots_travelnode`.
|
|
// Link: The connection between two nodes. A link signifies that the bot can travel from one node to another.
|
|
// A link is one-directional. Stored in `playerbots_travelnode_link`.
|
|
// Path: The waypoint path returned by the standard PathGenerator to move from one node (or position) to another.
|
|
// A path can be incomplete or empty which means there is no link. Stored in `playerbots_travelnode_path`.
|
|
// Route: The list of nodes that give the shortest route from a node to a distant node. Routes are calculated using
|
|
// a standard A* search based on links.
|
|
//
|
|
// Edge types (TravelNodePathType):
|
|
// walk(1) — Walk via navmesh waypoints (stored in DB)
|
|
// areaTrigger(2) — AreaTrigger teleport (auto-discovered at startup)
|
|
// transport(3) — Boat/zeppelin (auto-discovered from MO_TRANSPORT)
|
|
// flightPath(4) — Taxi flight between flight masters
|
|
// teleportSpell(5) — Spell-based teleport (e.g. mage portals)
|
|
// staticPortal(6) — Manually defined teleport link (DB only, not pruned by generation)
|
|
//
|
|
// On server start saved nodes and links are loaded via TravelNodeMap::Init(). An index of nodes by zone is prepared
|
|
// (instead of scanning all ~4000 nodes), precomputes connected components for O(1) reachability checks, and builds
|
|
// a taxi BFS graph. Paths and routes are calculated on the fly and saved for future use. Nodes are only added at
|
|
// startup or via the console `.generate` command — runtime mutation was removed because taking a unique_lock
|
|
// caused 100-250ms contention spikes against bot threads.
|
|
//
|
|
// Initially the current nodes have been made:
|
|
// Flightmasters and Inns (Bots can use these to fast-travel so eventually they will be included in the route
|
|
// calculation) WorldBosses and Unique bosses in instances (These are logical places bots might want to go in
|
|
// instances) Player start spawns (Obviously all lvl1 bots will spawn and move from here) Area triggers locations with
|
|
// teleport and their teleport destinations (These used to travel in or between maps) Transports including elevators
|
|
// (Again used to travel in and in maps) (sub)Zone means (These are the center most point for each sub-zone which is
|
|
// good for global coverage).
|
|
//
|
|
// To increase coverage/linking extra nodes must be manually created via the "playerbot travel generatenode"
|
|
// console command after importing the specified node. Current implementation places nodes on paths (including
|
|
// complete) at sub-zone transitions or randomly. After calculating possible links the node is removed if it
|
|
// does not create local coverage (.fullgenerate only).
|
|
//
|
|
// Travel Flow:
|
|
//
|
|
// GetFullPath finds nearest nodes (zone-indexed), runs A* to get a node route, then
|
|
// BuildPath assembles a flat TravelPath with typed waypoints (walk, portal, transport, flight).
|
|
// ExecuteTravelPlan iterates the path by stepIdx, dispatching on each point's PathNodeType.
|
|
// Cross-map travel is handled naturally by portal/transport edges in the A* graph.
|
|
//
|
|
// If setup cannot resolve (no node, no route, no flight), the bot teleports directly to the destination
|
|
// as a fallback.
|
|
//
|
|
// The use of hearthstones and mage teleporting was removed — it caused route mutations requiring locking that no longer made sense. Mage portals may be future item.
|
|
//
|
|
// Thread Safety:
|
|
//
|
|
// The node graph is immutable at runtime (no adds/removes after Init). A shared_timed_mutex (m_nMapMtx) still
|
|
// exists and shared_locks are taken in GetFullPath and GenerateWalkPath for safety, but since there are no
|
|
// runtime mutations these are effectively uncontested. The only exclusive locks are taken at startup
|
|
// (saveNodeStore) and by the debug dump command.
|
|
//
|
|
|
|
constexpr float MAX_PATHFINDING_DISTANCE = 296.0f;
|
|
|
|
enum class TravelNodePathType : uint8
|
|
{
|
|
none = 0,
|
|
walk = 1,
|
|
areaTrigger = 2,
|
|
transport = 3,
|
|
flightPath = 4,
|
|
teleportSpell = 5,
|
|
staticPortal = 6
|
|
};
|
|
|
|
// A connection between two nodes.
|
|
class TravelNodePath
|
|
{
|
|
public:
|
|
// Constructor
|
|
TravelNodePath(float distance = 0.1f, float extraCost = 0,
|
|
uint8 pathType = (uint8)TravelNodePathType::walk,
|
|
uint32 pathObject = 0, bool calculated = false,
|
|
std::vector<uint8> maxLevelCreature = {0, 0, 0},
|
|
float swimDistance = 0)
|
|
: extraCost(extraCost),
|
|
calculated(calculated),
|
|
distance(distance),
|
|
maxLevelCreature(maxLevelCreature),
|
|
swimDistance(swimDistance),
|
|
pathType(TravelNodePathType(pathType)),
|
|
pathObject(pathObject) // reorder args - whipowill
|
|
{
|
|
if (pathType != (uint8)TravelNodePathType::walk)
|
|
complete = true;
|
|
}
|
|
|
|
TravelNodePath(TravelNodePath* basePath)
|
|
{
|
|
complete = basePath->complete;
|
|
path = basePath->path;
|
|
extraCost = basePath->extraCost;
|
|
calculated = basePath->calculated;
|
|
distance = basePath->distance;
|
|
maxLevelCreature = basePath->maxLevelCreature;
|
|
swimDistance = basePath->swimDistance;
|
|
pathType = basePath->pathType;
|
|
pathObject = basePath->pathObject;
|
|
}
|
|
|
|
// Getters
|
|
bool getComplete() { return complete || pathType != TravelNodePathType::walk; }
|
|
std::vector<WorldPosition> GetPath() { return path; }
|
|
|
|
TravelNodePathType getPathType() { return pathType; }
|
|
uint32 getPathObject() { return pathObject; }
|
|
|
|
float getDistance() { return distance; }
|
|
float getSwimDistance() { return swimDistance; }
|
|
float getExtraCost() { return extraCost; }
|
|
std::vector<uint8> getMaxLevelCreature() { return maxLevelCreature; }
|
|
|
|
void setCalculated(bool calculated1 = true) { calculated = calculated1; }
|
|
|
|
bool getCalculated() { return calculated; }
|
|
|
|
std::string const print();
|
|
|
|
// Setters
|
|
void setComplete(bool complete1) { complete = complete1; }
|
|
|
|
void setPath(std::vector<WorldPosition> path1) { path = path1; }
|
|
|
|
void setPathAndCost(std::vector<WorldPosition> path1, float speed)
|
|
{
|
|
setPath(path1);
|
|
calculateCost(true);
|
|
extraCost = distance / speed;
|
|
}
|
|
|
|
void setPathType(TravelNodePathType pathType1) { pathType = pathType1; }
|
|
|
|
void setPathObject(uint32 pathObject1) { pathObject = pathObject1; }
|
|
|
|
void calculateCost(bool distanceOnly = false);
|
|
|
|
float getCost(Player* bot = nullptr, uint32 cGold = 0);
|
|
uint32 getPrice();
|
|
|
|
private:
|
|
// Does the path have all the points to get to the destination?
|
|
bool complete = false;
|
|
|
|
// List of WorldPositions to get to the destination.
|
|
std::vector<WorldPosition> path = {};
|
|
|
|
// The extra (loading/transport) time it takes to take this path.
|
|
float extraCost = 0;
|
|
|
|
bool calculated = false;
|
|
|
|
// Derived distance in yards
|
|
float distance = 0.1f;
|
|
|
|
// Calculated mobs level along the way.
|
|
std::vector<uint8> maxLevelCreature = {0, 0, 0}; // mobs, horde, alliance
|
|
|
|
// Calculated swiming distances along the way.
|
|
float swimDistance = 0;
|
|
|
|
TravelNodePathType pathType = TravelNodePathType::walk;
|
|
uint32 pathObject = 0;
|
|
|
|
/*
|
|
//Is the path a portal/teleport to the destination?
|
|
bool portal = false;
|
|
//Area trigger Id
|
|
uint32 portalId = 0;
|
|
|
|
//Is the path transport based?
|
|
bool transport = false;
|
|
|
|
// Is the path a flightpath?
|
|
bool flightPath = false;
|
|
*/
|
|
};
|
|
|
|
// A waypoint to travel from or to.
|
|
// Each node knows which other nodes can be reached without help.
|
|
class TravelNode
|
|
{
|
|
public:
|
|
// Constructors
|
|
TravelNode() {}
|
|
|
|
TravelNode(WorldPosition point1, std::string const nodeName1 = "Travel Node",
|
|
bool important1 = false)
|
|
{
|
|
nodeName = nodeName1;
|
|
point = point1;
|
|
important = important1;
|
|
}
|
|
|
|
TravelNode(TravelNode* baseNode)
|
|
{
|
|
nodeName = baseNode->nodeName;
|
|
point = baseNode->point;
|
|
important = baseNode->important;
|
|
}
|
|
|
|
// Setters
|
|
void setLinked(bool linked1) { linked = linked1; }
|
|
void setPoint(WorldPosition point1) { point = point1; }
|
|
|
|
// Getters
|
|
std::string const getName() { return nodeName; }
|
|
WorldPosition* getPosition() { return &point; }
|
|
std::unordered_map<TravelNode*, TravelNodePath>* getPaths() { return &paths; }
|
|
std::unordered_map<TravelNode*, TravelNodePath*>* getLinks() { return &links; }
|
|
bool isImportant() { return important; }
|
|
bool isLinked() { return linked; }
|
|
|
|
bool isTransport()
|
|
{
|
|
for (auto const& link : *getLinks())
|
|
if (link.second->getPathType() == TravelNodePathType::transport)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
uint32 getTransportId()
|
|
{
|
|
for (auto const& link : *getLinks())
|
|
if (link.second->getPathType() == TravelNodePathType::transport)
|
|
return link.second->getPathObject();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool isPortal()
|
|
{
|
|
for (auto const& link : *getLinks())
|
|
if (link.second->getPathType() == TravelNodePathType::areaTrigger ||
|
|
link.second->getPathType() == TravelNodePathType::staticPortal)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool isWalking()
|
|
{
|
|
for (auto link : *getLinks())
|
|
if (link.second->getPathType() == TravelNodePathType::walk)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// WorldLocation shortcuts
|
|
uint32 GetMapId() { return point.GetMapId(); }
|
|
float getX() { return point.GetPositionX(); }
|
|
float getY() { return point.GetPositionY(); }
|
|
float getZ() { return point.GetPositionZ(); }
|
|
float getO() { return point.GetOrientation(); }
|
|
float getDistance(WorldPosition pos) { return point.distance(pos); }
|
|
float getDistance(TravelNode* node)
|
|
{
|
|
return point.distance(node->getPosition());
|
|
}
|
|
float fDist(TravelNode* node)
|
|
{
|
|
return point.fDist(node->getPosition());
|
|
}
|
|
float fDist(WorldPosition pos) { return point.fDist(pos); }
|
|
|
|
TravelNodePath* setPathTo(TravelNode* node,
|
|
TravelNodePath path = TravelNodePath(),
|
|
bool isLink = true)
|
|
{
|
|
if (this != node)
|
|
{
|
|
paths[node] = path;
|
|
if (isLink)
|
|
links[node] = &paths[node];
|
|
|
|
return &paths[node];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool hasPathTo(TravelNode* node)
|
|
{
|
|
return paths.find(node) != paths.end();
|
|
}
|
|
TravelNodePath* getPathTo(TravelNode* node)
|
|
{
|
|
return &paths[node];
|
|
}
|
|
bool hasCompletePathTo(TravelNode* node)
|
|
{
|
|
return hasPathTo(node) && getPathTo(node)->getComplete();
|
|
}
|
|
TravelNodePath* BuildPath(TravelNode* endNode, Unit* bot,
|
|
bool postProcess = false);
|
|
|
|
void setLinkTo(TravelNode* node, float distance = 0.1f)
|
|
{
|
|
if (this != node)
|
|
{
|
|
if (!hasPathTo(node))
|
|
setPathTo(node, TravelNodePath(distance));
|
|
else
|
|
links[node] = &paths[node];
|
|
}
|
|
}
|
|
|
|
bool hasLinkTo(TravelNode* node)
|
|
{
|
|
return links.find(node) != links.end();
|
|
}
|
|
float linkCostTo(TravelNode* node)
|
|
{
|
|
return paths.find(node)->second.getDistance();
|
|
}
|
|
float linkDistanceTo(TravelNode* node)
|
|
{
|
|
return paths.find(node)->second.getDistance();
|
|
}
|
|
void removeLinkTo(TravelNode* node, bool removePaths = false);
|
|
|
|
bool isEqual(TravelNode* compareNode);
|
|
|
|
// Removes links to other nodes that can also be reached by passing another node.
|
|
bool isUselessLink(TravelNode* farNode);
|
|
void cropUselessLink(TravelNode* farNode);
|
|
bool cropUselessLinks();
|
|
|
|
// Returns all nodes that can be reached from this node.
|
|
std::vector<TravelNode*> getNodeMap(bool importantOnly = false,
|
|
std::vector<TravelNode*> ignoreNodes = {});
|
|
|
|
// Checks if it is even possible to route to this node.
|
|
bool hasRouteTo(TravelNode* node)
|
|
{
|
|
if (routes.empty())
|
|
for (auto mNode : getNodeMap())
|
|
routes[mNode] = true;
|
|
|
|
return routes.find(node) != routes.end();
|
|
}
|
|
|
|
void clearRoutes() { routes.clear(); }
|
|
void setRouteTo(TravelNode* node) { routes[node] = true; }
|
|
|
|
void print(bool printFailed = true);
|
|
|
|
protected:
|
|
// Logical name of the node
|
|
std::string nodeName;
|
|
// WorldPosition of the node.
|
|
WorldPosition point;
|
|
|
|
// List of paths to other nodes.
|
|
std::unordered_map<TravelNode*, TravelNodePath> paths;
|
|
// List of links to other nodes.
|
|
std::unordered_map<TravelNode*, TravelNodePath*> links;
|
|
|
|
// List of nodes and if there is 'any' route possible
|
|
std::unordered_map<TravelNode*, bool> routes;
|
|
|
|
// This node should not be removed
|
|
bool important = false;
|
|
|
|
// This node has been checked for nearby links
|
|
bool linked = false;
|
|
|
|
// This node is a (moving) transport.
|
|
// bool transport = false;
|
|
// Entry of transport.
|
|
// uint32 transportId = 0;
|
|
};
|
|
|
|
// Route step type
|
|
enum class PathNodeType : uint8
|
|
{
|
|
NODE_PREPATH = 0,
|
|
NODE_PATH = 1,
|
|
NODE_NODE = 2,
|
|
NODE_AREA_TRIGGER = 3,
|
|
NODE_TRANSPORT = 4,
|
|
NODE_FLIGHTPATH = 5,
|
|
NODE_TELEPORT = 6,
|
|
NODE_STATIC_PORTAL = 7
|
|
};
|
|
|
|
struct PathNodePoint
|
|
{
|
|
WorldPosition point;
|
|
PathNodeType type = PathNodeType::NODE_PATH;
|
|
uint32 entry = 0;
|
|
|
|
bool operator==(const PathNodePoint& p1) const
|
|
{
|
|
return point == p1.point && type == p1.type && entry == p1.entry;
|
|
}
|
|
// A "walkable" node is one we traverse on foot. Portals/transports/
|
|
// taxis/teleports are entry/exit hops, not points to anchor a
|
|
// shortcut on. Used by makeShortCut to skip them when picking the
|
|
// closest-point-on-path to the bot.
|
|
bool isWalkable() const { return (uint8)type <= (uint8)PathNodeType::NODE_NODE; }
|
|
};
|
|
|
|
// A complete list of points the bots has to walk to or teleport to.
|
|
class TravelPath
|
|
{
|
|
public:
|
|
TravelPath() {}
|
|
TravelPath(std::vector<PathNodePoint> fullPath1)
|
|
{
|
|
fullPath = fullPath1;
|
|
}
|
|
TravelPath(std::vector<WorldPosition> path,
|
|
PathNodeType type = PathNodeType::NODE_PATH,
|
|
uint32 entry = 0)
|
|
{
|
|
addPath(path, type, entry);
|
|
}
|
|
|
|
void addPoint(PathNodePoint point) { fullPath.push_back(point); }
|
|
void addPoint(WorldPosition point,
|
|
PathNodeType type = PathNodeType::NODE_PATH,
|
|
uint32 entry = 0)
|
|
{
|
|
fullPath.push_back(PathNodePoint{point, type, entry});
|
|
}
|
|
void addPath(std::vector<WorldPosition> path,
|
|
PathNodeType type = PathNodeType::NODE_PATH,
|
|
uint32 entry = 0)
|
|
{
|
|
for (auto& p : path)
|
|
fullPath.push_back(PathNodePoint{p, type, entry});
|
|
}
|
|
void addPath(std::vector<PathNodePoint> newPath)
|
|
{
|
|
fullPath.insert(fullPath.end(), newPath.begin(), newPath.end());
|
|
}
|
|
void clear() { fullPath.clear(); }
|
|
|
|
bool empty() const { return fullPath.empty(); }
|
|
size_t size() const { return fullPath.size(); }
|
|
const PathNodePoint& operator[](size_t idx) const { return fullPath[idx]; }
|
|
std::vector<PathNodePoint> GetPath() { return fullPath; }
|
|
const std::vector<PathNodePoint>& GetPathRef() const { return fullPath; }
|
|
WorldPosition getFront() { return fullPath.front().point; }
|
|
WorldPosition getBack() { return fullPath.back().point; }
|
|
|
|
std::vector<WorldPosition> getPointPath()
|
|
{
|
|
std::vector<WorldPosition> retVec;
|
|
for (auto const& p : fullPath)
|
|
retVec.push_back(p.point);
|
|
return retVec;
|
|
}
|
|
|
|
bool makeShortCut(WorldPosition startPos, float maxDist, Unit* bot = nullptr);
|
|
|
|
// Reject paths the navmesh accepts but a player can't walk:
|
|
// 2-point shortcut over 5y, or > 10y vertical drop with slope steeper than 2:1.
|
|
static bool IsPathCheating(std::vector<WorldPosition> const& path,
|
|
float endpointDistance);
|
|
|
|
std::ostringstream const print();
|
|
|
|
private:
|
|
std::vector<PathNodePoint> fullPath;
|
|
};
|
|
|
|
// An stored A* search that gives a complete route from one node to another.
|
|
class TravelNodeRoute
|
|
{
|
|
public:
|
|
TravelNodeRoute() {}
|
|
TravelNodeRoute(std::vector<TravelNode*> nodes1)
|
|
{
|
|
nodes = nodes1;
|
|
}
|
|
|
|
bool isEmpty() { return nodes.empty(); }
|
|
|
|
bool hasNode(TravelNode* node)
|
|
{
|
|
return findNode(node) != nodes.end();
|
|
}
|
|
float getTotalDistance();
|
|
|
|
std::vector<TravelNode*> getNodes() { return nodes; }
|
|
|
|
TravelPath BuildPath(
|
|
std::vector<WorldPosition> pathToStart = {},
|
|
std::vector<WorldPosition> pathToEnd = {},
|
|
Unit* bot = nullptr);
|
|
|
|
std::ostringstream const print();
|
|
|
|
private:
|
|
std::vector<TravelNode*>::iterator findNode(TravelNode* node)
|
|
{
|
|
return std::find(nodes.begin(), nodes.end(), node);
|
|
}
|
|
std::vector<TravelNode*> nodes;
|
|
};
|
|
|
|
// A node container to aid A* calculations with nodes.
|
|
class TravelNodeStub
|
|
{
|
|
public:
|
|
TravelNodeStub(TravelNode* dataNode1) { dataNode = dataNode1; }
|
|
|
|
TravelNode* dataNode;
|
|
float totalCost = 0.0;
|
|
float costFromStart = 0.0;
|
|
float heuristic = 0.0;
|
|
bool open = false;
|
|
bool closed = false;
|
|
TravelNodeStub* parent = nullptr;
|
|
uint32 currentGold = 0;
|
|
};
|
|
|
|
struct TravelPlan
|
|
{
|
|
WorldPosition destination;
|
|
|
|
// Flat waypoint path built upfront by GetFullPath:
|
|
TravelPath steps;
|
|
uint32 stepIdx{0};
|
|
|
|
// Spline scratch (used by executor):
|
|
std::vector<G3D::Vector3> walkPoints;
|
|
bool splineActive{false};
|
|
uint32 splineStartTime{0};
|
|
uint32 expectedDuration{0};
|
|
|
|
// Taxi scratch:
|
|
std::vector<uint32> route;
|
|
|
|
bool IsActive() const { return !steps.empty(); }
|
|
|
|
void Reset()
|
|
{
|
|
destination = WorldPosition();
|
|
steps.clear();
|
|
stepIdx = 0;
|
|
walkPoints.clear();
|
|
splineActive = false;
|
|
splineStartTime = 0;
|
|
expectedDuration = 0;
|
|
route.clear();
|
|
}
|
|
};
|
|
|
|
// The container of all nodes.
|
|
class TravelNodeMap
|
|
{
|
|
public:
|
|
static TravelNodeMap& instance()
|
|
{
|
|
static TravelNodeMap instance;
|
|
|
|
return instance;
|
|
}
|
|
|
|
TravelNode* addNode(WorldPosition pos,
|
|
std::string const preferedName = "Travel Node",
|
|
bool isImportant = false,
|
|
bool checkDuplicate = true,
|
|
bool transport = false,
|
|
uint32 transportId = 0);
|
|
void removeNode(TravelNode* node);
|
|
bool removeNodes()
|
|
{
|
|
if (m_nMapMtx.try_lock_for(std::chrono::seconds(10)))
|
|
{
|
|
for (auto& node : nodes)
|
|
removeNode(node);
|
|
|
|
m_nMapMtx.unlock();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void fullLinkNode(TravelNode* startNode, Unit* bot);
|
|
|
|
// Get all nodes
|
|
std::vector<TravelNode*> getNodes() { return nodes; }
|
|
std::vector<TravelNode*> getNodes(WorldPosition pos, float range = -1);
|
|
|
|
// Find nearest node.
|
|
TravelNode* getNode(TravelNode* sameNode)
|
|
{
|
|
for (auto& node : nodes)
|
|
{
|
|
if (node->getName() == sameNode->getName()
|
|
&& node->getPosition() == sameNode->getPosition())
|
|
return node;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
TravelNode* getNode(WorldPosition pos,
|
|
std::vector<WorldPosition>& ppath,
|
|
Unit* bot = nullptr, float range = -1);
|
|
TravelNode* getNode(WorldPosition pos, Unit* bot = nullptr,
|
|
float range = -1)
|
|
{
|
|
std::vector<WorldPosition> ppath;
|
|
return getNode(pos, ppath, bot, range);
|
|
}
|
|
|
|
// Get Random Node
|
|
TravelNode* getRandomNode(WorldPosition pos)
|
|
{
|
|
std::vector<TravelNode*> rNodes = getNodes(pos);
|
|
if (rNodes.empty())
|
|
return nullptr;
|
|
|
|
return rNodes[urand(0, rNodes.size() - 1)];
|
|
}
|
|
|
|
// Finds the best nodePath between two nodes (A* over the node graph)
|
|
TravelNodeRoute GetNodeRoute(TravelNode* start, TravelNode* goal,
|
|
Player* bot);
|
|
|
|
// Picks the nearest start/end nodes for two world positions and runs A*
|
|
// over the node graph to return a full route between them.
|
|
TravelNodeRoute FindRouteNearestNodes(WorldPosition startPos,
|
|
WorldPosition endPos,
|
|
std::vector<WorldPosition>& startPath,
|
|
Player* bot = nullptr);
|
|
|
|
void setHasToGen() { hasToGen = true; }
|
|
|
|
void generateNpcNodes();
|
|
void generateStartNodes();
|
|
void generateAreaTriggerNodes();
|
|
void generateNodes();
|
|
void generateTransportNodes();
|
|
void generateZoneMeanNodes();
|
|
|
|
void generateWalkPaths();
|
|
void removeLowNodes();
|
|
void removeUselessPaths();
|
|
void calculatePathCosts();
|
|
void generateTaxiPaths();
|
|
void generatePaths(bool fullGen = false);
|
|
|
|
void generateAll();
|
|
|
|
void Init();
|
|
|
|
void printMap();
|
|
|
|
void printNodeStore();
|
|
void saveNodeStore();
|
|
void LoadNodeStore();
|
|
|
|
bool cropUselessNode(TravelNode* startNode);
|
|
TravelNode* addZoneLinkNode(TravelNode* startNode);
|
|
TravelNode* addRandomExtNode(TravelNode* startNode);
|
|
|
|
void calcMapOffset();
|
|
WorldPosition getMapOffset(uint32 mapId);
|
|
|
|
// Taxi graph (BFS-based path lookup between taxi nodes)
|
|
void InitTaxiGraph();
|
|
std::vector<uint32> FindTaxiPath(uint32 fromNode, uint32 toNode);
|
|
|
|
void BuildZoneIndex();
|
|
void PrecomputeReachability();
|
|
|
|
TravelNode* GetNearestNodeInZone(WorldPosition pos, uint32 zoneId);
|
|
TravelNode* GetNearestNodeOnMap(WorldPosition pos);
|
|
|
|
// All nodes registered to a zone (post-BuildZoneIndex). Returns an
|
|
// empty static vector for unknown zones.
|
|
std::vector<TravelNode*> const& GetNodesInZone(uint32 zoneId) const;
|
|
|
|
bool GetFullPath(TravelPlan& plan, WorldPosition botPos,
|
|
uint32 botZoneId, WorldPosition destination, Unit* bot = nullptr);
|
|
|
|
// Resolve A* route between two world positions (returns node vector)
|
|
std::vector<TravelNode*> ResolveRoute(WorldPosition startPos,
|
|
WorldPosition endPos);
|
|
|
|
// Get stored walk points for one edge (from→to). Empty if no path.
|
|
std::vector<G3D::Vector3> GetEdgeWalkPoints(TravelNode* from,
|
|
TravelNode* to);
|
|
|
|
std::shared_timed_mutex m_nMapMtx;
|
|
|
|
private:
|
|
TravelNodeMap() = default;
|
|
~TravelNodeMap() = default;
|
|
|
|
TravelNodeMap(const TravelNodeMap&) = delete;
|
|
TravelNodeMap& operator=(const TravelNodeMap&) = delete;
|
|
|
|
TravelNodeMap(TravelNodeMap&&) = delete;
|
|
TravelNodeMap& operator=(TravelNodeMap&&) = delete;
|
|
|
|
// Taxi graph internals
|
|
void BuildTaxiGraph();
|
|
void ComputeAllPaths();
|
|
std::unordered_map<uint32, uint32> BFS(uint32 startNode);
|
|
std::vector<uint32> BuildPath(
|
|
uint32 fromNode, uint32 toNode,
|
|
const std::unordered_map<uint32, uint32>& parentMap);
|
|
|
|
std::unordered_map<uint32, std::vector<uint32>> m_taxiGraph;
|
|
std::map<uint32, std::map<uint32, std::vector<uint32>>>
|
|
m_taxiPathCache;
|
|
|
|
std::vector<TravelNode*> nodes;
|
|
|
|
std::unordered_map<uint32, std::vector<TravelNode*>> m_zoneIndex;
|
|
std::unordered_map<uint32, std::vector<TravelNode*>> m_mapIndex;
|
|
|
|
std::vector<std::pair<uint32, WorldPosition>> mapOffsets;
|
|
|
|
bool hasToSave = false;
|
|
bool hasToGen = false;
|
|
bool hasToFullGen = false;
|
|
};
|
|
|
|
#define sTravelNodeMap TravelNodeMap::instance()
|
|
|
|
#endif
|