mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
579 lines
21 KiB
C++
579 lines
21 KiB
C++
/*
|
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
|
|
*/
|
|
|
|
#ifndef _PLAYERBOT_TRAVELNODE_H
|
|
#define _PLAYERBOT_TRAVELNODE_H
|
|
|
|
#include "TravelMgr.h"
|
|
#include <shared_mutex>
|
|
|
|
//THEORY
|
|
//
|
|
// Pathfinding in (c)mangos is based on detour recast an opensource nashmesh creation and pathfinding codebase.
|
|
// This system is used for mob and npc pathfinding and in this codebase also for bots.
|
|
// 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 wich we know we can move from [N1] to [N2] and from [N2] to [N3] but not from [N1] to [N3]
|
|
// If we can move fom [S] to [N1] and from [N3] to [E] we have a complete route to travel.
|
|
//
|
|
// Termonology:
|
|
// 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.
|
|
// Link: the connection between two nodes. A link signifies that the bot can travel from one node to another. A link is one-directional.
|
|
// Path: the waypointpath returned by the standard PathGenerator to move from one node (or position) to another. A path can be imcomplete or empty which means there is no link.
|
|
// 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.
|
|
//
|
|
// On server start saved nodes and links are loaded. Paths and routes are calculated on the fly but saved for future use.
|
|
// Nodes can be added and removed realtime however because bots access the nodes from different threads this requires a locking mechanism.
|
|
//
|
|
// 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 a 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 can be automatically be created.
|
|
// Current implentation 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.
|
|
//
|
|
|
|
enum class TravelNodePathType : uint8
|
|
{
|
|
none = 0,
|
|
walk = 1,
|
|
portal = 2,
|
|
transport = 3,
|
|
flightPath = 4,
|
|
teleportSpell = 5
|
|
};
|
|
|
|
//A connection between two nodes.
|
|
class TravelNodePath
|
|
{
|
|
public:
|
|
//Legacy Constructor for travelnodestore
|
|
//TravelNodePath(float distance1, float extraCost1, bool portal1 = false, uint32 portalId1 = 0, bool transport1 = false, bool calculated = false, uint8 maxLevelMob1 = 0, uint8 maxLevelAlliance1 = 0, uint8 maxLevelHorde1 = 0, float swimDistance1 = 0, bool flightPath1 = false);
|
|
|
|
//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)
|
|
: distance(distance), extraCost(extraCost), pathType(TravelNodePathType(pathType)), pathObject(pathObject), calculated(calculated),
|
|
maxLevelCreature(maxLevelCreature), swimDistance(swimDistance)
|
|
{
|
|
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 setPortal(bool portal1, uint32 portalId1 = 0) { portal = portal1; portalId = portalId1; }
|
|
//void setTransport(bool transport1) { transport = transport1; }
|
|
|
|
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::portal)
|
|
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.getX(); }
|
|
float getY() { return point.getY(); }
|
|
float getZ() { return point.getZ(); }
|
|
float getO() { return point.getO(); }
|
|
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 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;
|
|
};
|
|
|
|
class PortalNode : public TravelNode
|
|
{
|
|
public:
|
|
PortalNode(TravelNode* baseNode) : TravelNode(baseNode) { };
|
|
|
|
void SetPortal(TravelNode* baseNode, TravelNode* endNode, uint32 portalSpell)
|
|
{
|
|
nodeName = baseNode->getName();
|
|
point = *baseNode->getPosition();
|
|
paths.clear();
|
|
links.clear();
|
|
TravelNodePath path(0.1f, 0.1f, (uint8)TravelNodePathType::teleportSpell, portalSpell, true);
|
|
setPathTo(endNode, path);
|
|
};
|
|
};
|
|
|
|
//Route step type
|
|
enum PathNodeType
|
|
{
|
|
NODE_PREPATH = 0,
|
|
NODE_PATH = 1,
|
|
NODE_NODE = 2,
|
|
NODE_PORTAL = 3,
|
|
NODE_TRANSPORT = 4,
|
|
NODE_FLIGHTPATH = 5,
|
|
NODE_TELEPORT = 6
|
|
};
|
|
|
|
struct PathNodePoint
|
|
{
|
|
WorldPosition point;
|
|
PathNodeType type = NODE_PATH;
|
|
uint32 entry = 0;
|
|
};
|
|
|
|
//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 = NODE_PATH, uint32 entry = 0)
|
|
{
|
|
addPath(path, type, entry);
|
|
}
|
|
|
|
void addPoint(PathNodePoint point) { fullPath.push_back(point); }
|
|
void addPoint(WorldPosition point, PathNodeType type = NODE_PATH, uint32 entry = 0) { fullPath.push_back(PathNodePoint{ point, type, entry }); }
|
|
void addPath(std::vector<WorldPosition> path, PathNodeType type = 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() { return fullPath.empty(); }
|
|
std::vector<PathNodePoint> getPath() { 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);
|
|
bool shouldMoveToNextPoint(WorldPosition startPos, std::vector<PathNodePoint>::iterator beg, std::vector<PathNodePoint>::iterator ed, std::vector<PathNodePoint>::iterator p, float& moveDist, float maxDist);
|
|
WorldPosition getNextPoint(WorldPosition startPos, float maxDist, TravelNodePathType& pathType, uint32& entry);
|
|
|
|
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; /*currentNode = route.begin();*/ }
|
|
|
|
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 m_f = 0.0, m_g = 0.0, m_h = 0.0;
|
|
bool open = false, close = false;
|
|
TravelNodeStub* parent = nullptr;
|
|
uint32 currentGold = 0;
|
|
};
|
|
|
|
//The container of all nodes.
|
|
class TravelNodeMap
|
|
{
|
|
public:
|
|
TravelNodeMap() { };
|
|
TravelNodeMap(TravelNodeMap* baseMap);
|
|
|
|
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 : m_nodes)
|
|
removeNode(node);
|
|
|
|
m_nMapMtx.unlock();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
void fullLinkNode(TravelNode* startNode, Unit* bot);
|
|
|
|
//Get all nodes
|
|
std::vector<TravelNode*> getNodes() { return m_nodes; }
|
|
std::vector<TravelNode*> getNodes(WorldPosition pos, float range = -1);
|
|
|
|
//Find nearest node.
|
|
TravelNode* getNode(TravelNode* sameNode)
|
|
{
|
|
for (auto& node : m_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
|
|
TravelNodeRoute getRoute(TravelNode* start, TravelNode* goal, Player* bot = nullptr);
|
|
|
|
//Find the best node between two positions
|
|
TravelNodeRoute getRoute(WorldPosition startPos, WorldPosition endPos, std::vector<WorldPosition>& startPath, Player* bot = nullptr);
|
|
|
|
//Find the full path between those locations
|
|
static TravelPath getFullPath(WorldPosition startPos, WorldPosition endPos, Player* bot = nullptr);
|
|
|
|
//Manage/update nodes
|
|
void manageNodes(Unit* bot, bool mapFull = false);
|
|
|
|
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();
|
|
|
|
void generateAll();
|
|
|
|
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);
|
|
|
|
std::shared_timed_mutex m_nMapMtx;
|
|
std::unordered_map<ObjectGuid, std::unordered_map<uint32, TravelNode*>> teleportNodes;
|
|
|
|
private:
|
|
std::vector<TravelNode*> m_nodes;
|
|
|
|
std::vector<std::pair<uint32, WorldPosition>> mapOffsets;
|
|
|
|
bool hasToSave = false;
|
|
bool hasToGen = false;
|
|
bool hasToFullGen = false;
|
|
};
|
|
|
|
#define sTravelNodeMap TravelNodeMap::instance()
|
|
|
|
#endif
|