mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
Compare commits
31 Commits
7772dc4c0d
...
e92af1cc06
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e92af1cc06 | ||
|
|
b97da5c741 | ||
|
|
cbd5f8748c | ||
|
|
1d85510a9e | ||
|
|
c82cd18677 | ||
|
|
566f4975e6 | ||
|
|
dae09388ad | ||
|
|
d00ad8d327 | ||
|
|
32b687f00a | ||
|
|
7f7cfb33d8 | ||
|
|
bd7422e98b | ||
|
|
97b3e345a8 | ||
|
|
0eff76f3ec | ||
|
|
2c822affd2 | ||
|
|
7cb9dc622c | ||
|
|
fa070f5e07 | ||
|
|
783210c4d0 | ||
|
|
d8c4425409 | ||
|
|
ae0baa3fcc | ||
|
|
cd65fda93b | ||
|
|
3813909341 | ||
|
|
a2d0b4530b | ||
|
|
1cf604dc60 | ||
|
|
c6e0cc9cef | ||
|
|
fff692e3f3 | ||
|
|
f6c41f57e4 | ||
|
|
6aea0c2ba7 | ||
|
|
e165a1e79b | ||
|
|
42768fe360 | ||
|
|
eb97533387 | ||
|
|
ec52e5c310 |
@ -1065,6 +1065,16 @@ AiPlayerbot.EnableNewRpgStrategy = 1
|
|||||||
# Default: 0 (disabled)
|
# Default: 0 (disabled)
|
||||||
AiPlayerbot.EnableTravelNodes = 0
|
AiPlayerbot.EnableTravelNodes = 0
|
||||||
|
|
||||||
|
# Transport ride mode (travel node graph only):
|
||||||
|
# 0 = bot walks to dock, teleport-snaps onto transport, rides, teleport-snaps off
|
||||||
|
# 1 = bot teleports directly across the transport route, skipping the ride
|
||||||
|
# Default 0 is the visible behavior. Set to 1 for invisible/random-bot mass
|
||||||
|
# travel performance — the bot never actually boards anything.
|
||||||
|
# (AC has no transport-surface mmap, so an on-deck walking mode can't be
|
||||||
|
# faithfully implemented; the on-board phase always teleport-snaps.)
|
||||||
|
# Default: 0
|
||||||
|
AiPlayerbot.TransportSkipRide = 0
|
||||||
|
|
||||||
# Control probability weights for RPG status of bots. Takes effect only when the status meets its premise.
|
# Control probability weights for RPG status of bots. Takes effect only when the status meets its premise.
|
||||||
# Sum of weights need not be 100. Set to 0 to disable the status.
|
# Sum of weights need not be 100. Set to 0 to disable the status.
|
||||||
#
|
#
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,6 @@ class Unit;
|
|||||||
class WorldObject;
|
class WorldObject;
|
||||||
class Position;
|
class Position;
|
||||||
|
|
||||||
#define ANGLE_45_DEG (static_cast<float>(M_PI) / 4.f)
|
|
||||||
#define ANGLE_90_DEG M_PI_2
|
#define ANGLE_90_DEG M_PI_2
|
||||||
#define ANGLE_120_DEG (2.f * static_cast<float>(M_PI) / 3.f)
|
#define ANGLE_120_DEG (2.f * static_cast<float>(M_PI) / 3.f)
|
||||||
|
|
||||||
@ -57,7 +56,54 @@ protected:
|
|||||||
bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false,
|
bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false,
|
||||||
bool normal_only = false, bool exact_waypoint = false,
|
bool normal_only = false, bool exact_waypoint = false,
|
||||||
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false,
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false,
|
||||||
bool backwards = false);
|
bool backwards = false, bool ignoreEnemyTargets = false);
|
||||||
|
|
||||||
|
// Path-aware funnel mirroring the reference movement implementation.
|
||||||
|
// Runs UpdateMovementState + IsMovingAllowed + WaitForTransport gates,
|
||||||
|
// applies the targetPosRecalcDistance short-stop, resolves a TravelPath
|
||||||
|
// via ResolveMovePath (which gates graph A* by sightDistance), trims
|
||||||
|
// with makeShortCut, handles special head segments
|
||||||
|
// (portal/area-trigger/transport/flight) via HandleSpecialMovement,
|
||||||
|
// clips at hostile creatures via ClipPath (unless ignoreEnemyTargets),
|
||||||
|
// and dispatches the resulting walk via DispatchMovement.
|
||||||
|
// MoveTo(mapId,...) delegates here unless an intentional bypass
|
||||||
|
// (exact_waypoint / disableMoveSplinePath / flying / swimming /
|
||||||
|
// backwards) routes the move straight to DoMovePoint.
|
||||||
|
// `react=true` opts the move out of the end-of-dispatch
|
||||||
|
// WaitForReach AI-loop block — combat callers should set this so the
|
||||||
|
// bot can keep re-evaluating mid-chase. Default false matches the
|
||||||
|
// reference's MoveTo2 default.
|
||||||
|
bool MoveTo2(WorldPosition endPos,
|
||||||
|
bool idle = false, bool react = false,
|
||||||
|
bool noPath = false, bool ignoreEnemyTargets = false,
|
||||||
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL,
|
||||||
|
bool lessDelay = false);
|
||||||
|
|
||||||
|
// Centralized walk dispatch. Mirrors the reference's DispatchMovement
|
||||||
|
// shape: takes a TravelPath, builds the PointsArray internally,
|
||||||
|
// applies inactive-bot teleport carve-out, masterWalking mode,
|
||||||
|
// pre-dispatch state cleanup (clear emote, stand, interrupt cast),
|
||||||
|
// transport-passenger coordinate sandwich
|
||||||
|
// (CalculatePassengerPosition → UpdateAllowedPositionZ → Offset)
|
||||||
|
// around the per-point Z snap, mm.Clear → MovePoint(last) →
|
||||||
|
// MoveSplinePath. Caches the destination + duration on lastMove.
|
||||||
|
//
|
||||||
|
// Divergence from reference: reference ends with WaitForReach(size)
|
||||||
|
// which blocks the AI loop until the move completes. AC's combat
|
||||||
|
// callers (ReachCombatTo) currently funnel through MoveTo → MoveTo2
|
||||||
|
// → DispatchMovement; blocking the AI loop here would suspend combat
|
||||||
|
// re-evaluation for the full move duration. Until combat dispatch is
|
||||||
|
// restructured to bypass MoveTo2, the WaitForReach is deliberately
|
||||||
|
// omitted.
|
||||||
|
// `react=true` skips the end-of-dispatch WaitForReach so the AI
|
||||||
|
// loop isn't blocked while the spline plays — combat callers use
|
||||||
|
// this to keep re-evaluating mid-chase.
|
||||||
|
bool DispatchMovement(TravelPath path,
|
||||||
|
WorldPosition dest,
|
||||||
|
char const* label,
|
||||||
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL,
|
||||||
|
bool lessDelay = false,
|
||||||
|
bool react = false);
|
||||||
bool MoveTo(WorldObject* target, float distance = 0.0f,
|
bool MoveTo(WorldObject* target, float distance = 0.0f,
|
||||||
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||||
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig.contactDistance,
|
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig.contactDistance,
|
||||||
@ -65,10 +111,22 @@ protected:
|
|||||||
float GetFollowAngle();
|
float GetFollowAngle();
|
||||||
bool Follow(Unit* target, float distance = sPlayerbotAIConfig.followDistance);
|
bool Follow(Unit* target, float distance = sPlayerbotAIConfig.followDistance);
|
||||||
bool Follow(Unit* target, float distance, float angle);
|
bool Follow(Unit* target, float distance, float angle);
|
||||||
|
// Handles the cross-transport follow case: when bot and target are
|
||||||
|
// on different transports (or one is off-transport) and within
|
||||||
|
// sight, this disembarks the bot from its current transport (if
|
||||||
|
// any), teleports it to the target's position, and boards the
|
||||||
|
// target's transport (if any). Returns true if the transport
|
||||||
|
// transition was performed this tick (caller should skip the
|
||||||
|
// engine-level follow for this tick).
|
||||||
|
bool FollowOnTransport(Unit* target);
|
||||||
bool ChaseTo(WorldObject* obj, float distance = 0.0f);
|
bool ChaseTo(WorldObject* obj, float distance = 0.0f);
|
||||||
bool ReachCombatTo(Unit* target, float distance = 0.0f);
|
bool ReachCombatTo(Unit* target, float distance = 0.0f);
|
||||||
float MoveDelay(float distance, bool backwards = false);
|
float MoveDelay(float distance, bool backwards = false);
|
||||||
void WaitForReach(float distance);
|
void WaitForReach(float distance);
|
||||||
|
// PointsArray overload: sums segment distances and calls the float
|
||||||
|
// version. Matches the reference's WaitForReach(PointsArray) used at
|
||||||
|
// the end of DispatchMovement.
|
||||||
|
void WaitForReach(Movement::PointsArray const& path);
|
||||||
void SetNextMovementDelay(float delayMillis);
|
void SetNextMovementDelay(float delayMillis);
|
||||||
bool IsMovingAllowed(WorldObject* target);
|
bool IsMovingAllowed(WorldObject* target);
|
||||||
bool IsDuplicateMove(float x, float y, float z);
|
bool IsDuplicateMove(float x, float y, float z);
|
||||||
|
|||||||
@ -12,7 +12,6 @@ LastMovement::LastMovement() { clear(); }
|
|||||||
LastMovement::LastMovement(LastMovement& other)
|
LastMovement::LastMovement(LastMovement& other)
|
||||||
: taxiNodes(other.taxiNodes),
|
: taxiNodes(other.taxiNodes),
|
||||||
taxiMaster(other.taxiMaster),
|
taxiMaster(other.taxiMaster),
|
||||||
lastFollow(other.lastFollow),
|
|
||||||
lastAreaTrigger(other.lastAreaTrigger),
|
lastAreaTrigger(other.lastAreaTrigger),
|
||||||
lastFlee(other.lastFlee)
|
lastFlee(other.lastFlee)
|
||||||
{
|
{
|
||||||
@ -27,7 +26,6 @@ void LastMovement::clear()
|
|||||||
{
|
{
|
||||||
lastMoveShort = WorldPosition();
|
lastMoveShort = WorldPosition();
|
||||||
lastPath.clear();
|
lastPath.clear();
|
||||||
lastFollow = nullptr;
|
|
||||||
lastAreaTrigger = 0;
|
lastAreaTrigger = 0;
|
||||||
lastFlee = 0;
|
lastFlee = 0;
|
||||||
nextTeleport = 0;
|
nextTeleport = 0;
|
||||||
@ -36,17 +34,18 @@ void LastMovement::clear()
|
|||||||
lastTransportEntry = 0;
|
lastTransportEntry = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LastMovement::Set(Unit* follow)
|
void LastMovement::Set([[maybe_unused]] Unit* follow)
|
||||||
{
|
{
|
||||||
|
// Legacy signature — `follow` is ignored (lastFollow field removed).
|
||||||
|
// The function still serves callers that want a soft-reset:
|
||||||
|
// clears short + path, resets msTime/priority via the chain below.
|
||||||
Set(0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
|
Set(0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
setShort(WorldPosition());
|
setShort(WorldPosition());
|
||||||
setPath(TravelPath());
|
setPath(TravelPath());
|
||||||
lastFollow = follow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LastMovement::Set(uint32 mapId, float x, float y, float z, float ori, float delayTime, MovementPriority pri)
|
void LastMovement::Set(uint32 mapId, float x, float y, float z, float ori, float delayTime, MovementPriority pri)
|
||||||
{
|
{
|
||||||
lastFollow = nullptr;
|
|
||||||
lastMoveShort = WorldPosition(mapId, x, y, z, ori);
|
lastMoveShort = WorldPosition(mapId, x, y, z, ori);
|
||||||
msTime = getMSTime();
|
msTime = getMSTime();
|
||||||
priority = pri;
|
priority = pri;
|
||||||
@ -55,7 +54,6 @@ void LastMovement::Set(uint32 mapId, float x, float y, float z, float ori, float
|
|||||||
void LastMovement::setShort(WorldPosition point)
|
void LastMovement::setShort(WorldPosition point)
|
||||||
{
|
{
|
||||||
lastMoveShort = point;
|
lastMoveShort = point;
|
||||||
lastFollow = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LastMovement::setPath(TravelPath path) { lastPath = path; }
|
void LastMovement::setPath(TravelPath path) { lastPath = path; }
|
||||||
|
|||||||
@ -33,7 +33,6 @@ public:
|
|||||||
{
|
{
|
||||||
taxiNodes = other.taxiNodes;
|
taxiNodes = other.taxiNodes;
|
||||||
taxiMaster = other.taxiMaster;
|
taxiMaster = other.taxiMaster;
|
||||||
lastFollow = other.lastFollow;
|
|
||||||
lastAreaTrigger = other.lastAreaTrigger;
|
lastAreaTrigger = other.lastAreaTrigger;
|
||||||
lastMoveShort = other.lastMoveShort;
|
lastMoveShort = other.lastMoveShort;
|
||||||
lastPath = other.lastPath;
|
lastPath = other.lastPath;
|
||||||
@ -53,7 +52,6 @@ public:
|
|||||||
|
|
||||||
std::vector<uint32> taxiNodes;
|
std::vector<uint32> taxiNodes;
|
||||||
ObjectGuid taxiMaster;
|
ObjectGuid taxiMaster;
|
||||||
Unit* lastFollow;
|
|
||||||
uint32 lastAreaTrigger;
|
uint32 lastAreaTrigger;
|
||||||
time_t lastFlee;
|
time_t lastFlee;
|
||||||
WorldPosition lastMoveShort;
|
WorldPosition lastMoveShort;
|
||||||
|
|||||||
@ -9,12 +9,8 @@
|
|||||||
#include "Creature.h"
|
#include "Creature.h"
|
||||||
#include "G3D/Vector2.h"
|
#include "G3D/Vector2.h"
|
||||||
#include "GameObject.h"
|
#include "GameObject.h"
|
||||||
#include "GossipDef.h"
|
|
||||||
#include "GridTerrainData.h"
|
|
||||||
#include "IVMapMgr.h"
|
|
||||||
#include "Item.h"
|
#include "Item.h"
|
||||||
#include "ItemTemplate.h"
|
#include "ItemTemplate.h"
|
||||||
#include "LootMgr.h"
|
|
||||||
#include "Map.h"
|
#include "Map.h"
|
||||||
#include "ModelIgnoreFlags.h"
|
#include "ModelIgnoreFlags.h"
|
||||||
#include "MotionMaster.h"
|
#include "MotionMaster.h"
|
||||||
@ -34,7 +30,6 @@
|
|||||||
#include "PlayerbotTextMgr.h"
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "Position.h"
|
#include "Position.h"
|
||||||
#include "QuestDef.h"
|
|
||||||
#include "Random.h"
|
#include "Random.h"
|
||||||
#include "RandomPlayerbotMgr.h"
|
#include "RandomPlayerbotMgr.h"
|
||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
@ -53,218 +48,7 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
EmitDebugMove("MoveFar", "empty-dest", 0.0f, 0.0f, 0.0f);
|
EmitDebugMove("MoveFar", "empty-dest", 0.0f, 0.0f, 0.0f);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return MoveTo2(dest);
|
||||||
UpdateMovementState();
|
|
||||||
|
|
||||||
if (!IsMovingAllowed())
|
|
||||||
{
|
|
||||||
EmitDebugMove("MoveFar", "cant-move",
|
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resume a transport ride if we're still on the same boat as last tick.
|
|
||||||
if (WaitForTransport())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
WorldPosition botPos(bot);
|
|
||||||
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
|
||||||
|
|
||||||
// Short-stop: at destination — stop and clear the cached path.
|
|
||||||
{
|
|
||||||
float const totalDistance = botPos.distance(dest);
|
|
||||||
if (totalDistance < sPlayerbotAIConfig.targetPosRecalcDistance)
|
|
||||||
{
|
|
||||||
if (!lastMove.lastPath.empty() &&
|
|
||||||
lastMove.lastPath.getBack().distance(dest) <= totalDistance)
|
|
||||||
lastMove.clear();
|
|
||||||
bot->StopMoving();
|
|
||||||
EmitDebugMove("MoveFar", "arrived",
|
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Per-tick re-resolve: rebuild the TravelPath from the bot's current
|
|
||||||
// position every tick (10% reuse short-circuits via the cached
|
|
||||||
// lastPath). Recovers naturally from knockback, off-route drift,
|
|
||||||
// destination changes, and blocked waypoints.
|
|
||||||
TravelPath path = ResolveMovePath(botPos, dest, lastMove);
|
|
||||||
lastMove.setPath(path);
|
|
||||||
|
|
||||||
if (path.empty())
|
|
||||||
{
|
|
||||||
EmitDebugMove("MoveFar", "no-path",
|
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim leading waypoints behind the bot, bridge with mmap probe if
|
|
||||||
// the new head requires it. May empty the path (collapsed) — let
|
|
||||||
// the next tick rebuild from a fresh start.
|
|
||||||
path.makeShortCut(botPos, sPlayerbotAIConfig.reactDistance, bot);
|
|
||||||
if (path.empty())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Special head segment (portal / area-trigger / transport / flight)?
|
|
||||||
// UpcommingSpecialMovement cuts the path so the head is the special;
|
|
||||||
// HandleSpecialMovement dispatches the matching action.
|
|
||||||
bool const onTransport = bot->GetTransport() != nullptr;
|
|
||||||
if (path.UpcommingSpecialMovement(botPos,
|
|
||||||
sPlayerbotAIConfig.reactDistance,
|
|
||||||
onTransport))
|
|
||||||
{
|
|
||||||
if (HandleSpecialMovement(path))
|
|
||||||
return true;
|
|
||||||
// Special handler declined (e.g. AREA_TRIGGER with entry → caller
|
|
||||||
// dispatches the walk into the trigger volume). Fall through.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transport guard: bot is on a transport but no special movement
|
|
||||||
// applies this tick — don't dispatch a walk spline (would fight the
|
|
||||||
// transport's own movement).
|
|
||||||
if (onTransport)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// ClipPath — truncate at first hostile creature in range / non-walkable
|
|
||||||
// hop / drifted past reactDistance / > 125 sqDist jump.
|
|
||||||
path.ClipPath(botAI, bot, false);
|
|
||||||
if (path.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Telemetry: show the path's actual tail coords vs bot + dest so we
|
|
||||||
// can see whether the resolved path is heading toward the right
|
|
||||||
// place. dest-distance == 0 means tail IS the dest (good); large
|
|
||||||
// dest-distance means graph picked a far-off endNode.
|
|
||||||
if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
|
||||||
{
|
|
||||||
WorldPosition tail = path.getBack();
|
|
||||||
float const tailToDest = tail.distance(dest);
|
|
||||||
float const botToTail = bot->GetExactDist(tail.GetPositionX(),
|
|
||||||
tail.GetPositionY(),
|
|
||||||
tail.GetPositionZ());
|
|
||||||
std::ostringstream tlog;
|
|
||||||
tlog << "[PATH] tail=(" << std::fixed << std::setprecision(1)
|
|
||||||
<< tail.GetPositionX() << "," << tail.GetPositionY() << "," << tail.GetPositionZ()
|
|
||||||
<< ") botToTail=" << botToTail << "y tailToDest=" << tailToDest << "y";
|
|
||||||
botAI->TellMasterNoFacing(tlog);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk dispatch.
|
|
||||||
std::vector<WorldPosition> const& pts = path.getPointPath();
|
|
||||||
Movement::PointsArray points;
|
|
||||||
points.reserve(pts.size());
|
|
||||||
for (auto const& wp : pts)
|
|
||||||
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
|
||||||
|
|
||||||
if (points.size() < 2)
|
|
||||||
{
|
|
||||||
// Single-point fallback path (cmangos pattern: ResolveMovePath
|
|
||||||
// emits a single dest point if nothing else worked). Hand it
|
|
||||||
// to the engine's MovePoint via MoveTo.
|
|
||||||
EmitDebugMove("MoveFar", "single-point",
|
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
|
||||||
return MoveTo(dest.GetMapId(), dest.GetPositionX(),
|
|
||||||
dest.GetPositionY(), dest.GetPositionZ(),
|
|
||||||
false, false, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bot->IsMounted() && !bot->IsInCombat() &&
|
|
||||||
bot->IsOutdoors() && bot->IsAlive())
|
|
||||||
botAI->DoSpecificAction("check mount state", Event(), true);
|
|
||||||
|
|
||||||
return DispatchPathPoints(dest, points, "walk");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest,
|
|
||||||
Movement::PointsArray& points,
|
|
||||||
char const* label)
|
|
||||||
{
|
|
||||||
if (points.size() < 2)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// MoveFarTo runs makeShortCut + setPath upstream now, so no need
|
|
||||||
// for the local prefix-trim or lastMove.setPath here.
|
|
||||||
|
|
||||||
for (auto& pt : points)
|
|
||||||
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
|
|
||||||
|
|
||||||
// ClipPath now runs at MoveFarTo level on the TravelPath before the
|
|
||||||
// points array is built. No per-dispatch clip here.
|
|
||||||
|
|
||||||
if (points.size() < 2)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
G3D::Vector3 const& last = points.back();
|
|
||||||
|
|
||||||
float totalDist = 0.f;
|
|
||||||
for (size_t i = 1; i < points.size(); ++i)
|
|
||||||
totalDist += (points[i] - points[i - 1]).length();
|
|
||||||
|
|
||||||
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
|
||||||
|
|
||||||
// Skip cosmetic walking for random bots with no nearby player —
|
|
||||||
// teleport to the path tail and schedule a cooldown instead.
|
|
||||||
if (sRandomPlayerbotMgr.IsRandomBot(bot))
|
|
||||||
{
|
|
||||||
WorldPosition tail(dest.GetMapId(), last.x, last.y, last.z);
|
|
||||||
time_t now = time(nullptr);
|
|
||||||
if (totalDist > sPlayerbotAIConfig.reactDistance &&
|
|
||||||
lastMove.nextTeleport <= now &&
|
|
||||||
!botAI->HasPlayerNearby(&tail))
|
|
||||||
{
|
|
||||||
float speed = std::max(bot->GetSpeed(MOVE_RUN), 0.1f);
|
|
||||||
lastMove.nextTeleport = now + (time_t)(totalDist / speed);
|
|
||||||
|
|
||||||
EmitDebugMove("MoveFar", "teleport",
|
|
||||||
tail.GetPositionX(), tail.GetPositionY(), tail.GetPositionZ());
|
|
||||||
|
|
||||||
WorldPosition botPos(bot);
|
|
||||||
return bot->TeleportTo(dest.GetMapId(),
|
|
||||||
tail.GetPositionX(), tail.GetPositionY(),
|
|
||||||
tail.GetPositionZ(),
|
|
||||||
botPos.getAngleTo(tail));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match master's walk pace when they're walking and within 5y.
|
|
||||||
ForcedMovement moveMode = FORCED_MOVEMENT_RUN;
|
|
||||||
if (Player* master = botAI->GetMaster())
|
|
||||||
{
|
|
||||||
if (bot->IsFriendlyTo(master) && master->IsWalking() &&
|
|
||||||
bot->GetExactDist2d(master) < 5.0f)
|
|
||||||
{
|
|
||||||
moveMode = FORCED_MOVEMENT_WALK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear emote/sit/cast so the spline can begin cleanly.
|
|
||||||
bot->ClearEmoteState();
|
|
||||||
if (!bot->IsStandState())
|
|
||||||
bot->SetStandState(UNIT_STAND_STATE_STAND);
|
|
||||||
if (bot->IsNonMeleeSpellCast(true))
|
|
||||||
bot->InterruptNonMeleeSpells(true);
|
|
||||||
|
|
||||||
bot->GetMotionMaster()->Clear();
|
|
||||||
bot->GetMotionMaster()->MoveSplinePath(&points, moveMode);
|
|
||||||
|
|
||||||
EmitDebugMove("MoveFar", label, last.x, last.y, last.z);
|
|
||||||
|
|
||||||
// WaitForReach: leave ~10y headroom on long paths.
|
|
||||||
float waitDist = totalDist > sPlayerbotAIConfig.reactDistance
|
|
||||||
? std::max(totalDist - 10.0f, 0.0f) : totalDist;
|
|
||||||
UnitMoveType const speedType = (moveMode == FORCED_MOVEMENT_WALK) ? MOVE_WALK : MOVE_RUN;
|
|
||||||
float speed = std::max(bot->GetSpeed(speedType), 0.1f);
|
|
||||||
float duration = 1000.0f * (waitDist / speed) + sPlayerbotAIConfig.reactDelay;
|
|
||||||
duration = std::min(duration, (float)sPlayerbotAIConfig.maxWaitForMove);
|
|
||||||
if (duration < 0.0f)
|
|
||||||
duration = 0.0f;
|
|
||||||
|
|
||||||
lastMove.Set(bot->GetMapId(), last.x, last.y, last.z,
|
|
||||||
bot->GetOrientation(), (uint32)duration,
|
|
||||||
MovementPriority::MOVEMENT_NORMAL);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NewRpgBaseAction::MoveWorldObjectTo(ObjectGuid guid, float distance)
|
bool NewRpgBaseAction::MoveWorldObjectTo(ObjectGuid guid, float distance)
|
||||||
|
|||||||
@ -82,15 +82,6 @@ protected:
|
|||||||
bool RandomChangeStatus(std::vector<NewRpgStatus> candidateStatus);
|
bool RandomChangeStatus(std::vector<NewRpgStatus> candidateStatus);
|
||||||
bool CheckRpgStatusAvailable(NewRpgStatus status);
|
bool CheckRpgStatusAvailable(NewRpgStatus status);
|
||||||
|
|
||||||
private:
|
|
||||||
// Centralized dispatch helper. Applies underwater fixup, ClipPath
|
|
||||||
// (truncate at first hostile in attack range with LOS, level+5 cap),
|
|
||||||
// inactive-bot teleport (with self-bot carve-out), masterWalking
|
|
||||||
// mode, pre-dispatch state cleanup, then dispatches via
|
|
||||||
// MoveSplinePath and schedules via WaitForReach formula.
|
|
||||||
bool DispatchPathPoints(WorldPosition const& dest,
|
|
||||||
Movement::PointsArray& points,
|
|
||||||
char const* label);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -274,6 +274,27 @@ bool WorldPosition::isUnderWater()
|
|||||||
: false;
|
: false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool WorldPosition::setAtWaterSurface()
|
||||||
|
{
|
||||||
|
if (!isInWater() && !isUnderWater())
|
||||||
|
return false;
|
||||||
|
Map* map = getMap();
|
||||||
|
if (!map)
|
||||||
|
return false;
|
||||||
|
// Returns the water level when liquid is present; falls back to
|
||||||
|
// ground level otherwise. Our isInWater/isUnderWater preconditions
|
||||||
|
// ensure liquid exists, so the +0.5y nudge lands the point on top
|
||||||
|
// of the surface (matches the reference's surface snap).
|
||||||
|
float const level = map->GetWaterOrGroundLevel(PHASEMASK_NORMAL,
|
||||||
|
GetPositionX(),
|
||||||
|
GetPositionY(),
|
||||||
|
GetPositionZ());
|
||||||
|
if (level <= INVALID_HEIGHT)
|
||||||
|
return false;
|
||||||
|
setZ(level + 0.5f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool WorldPosition::IsValid()
|
bool WorldPosition::IsValid()
|
||||||
{
|
{
|
||||||
return !(GetMapId() == MAPID_INVALID && GetPositionX() == 0 && GetPositionY() == 0 && GetPositionZ() == 0);
|
return !(GetMapId() == MAPID_INVALID && GetPositionX() == 0 && GetPositionY() == 0 && GetPositionZ() == 0);
|
||||||
@ -723,6 +744,13 @@ std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos
|
|||||||
// fire — apply the same bot cost biases here so generated paths
|
// fire — apply the same bot cost biases here so generated paths
|
||||||
// match what bots prefer at runtime (STEEP/water are reachable
|
// match what bots prefer at runtime (STEEP/water are reachable
|
||||||
// but not preferred).
|
// but not preferred).
|
||||||
|
//
|
||||||
|
// Reference also applies setAreaCost(12, 5) + setAreaCost(13, 20)
|
||||||
|
// here. Not ported: reference and AC use different mmap generators
|
||||||
|
// and Detour area-id assignments diverge — raw IDs 12/13 are
|
||||||
|
// unlikely to match any polys on AC's navmesh and could no-op or
|
||||||
|
// bias something unintended. If we ever regenerate mmaps to match
|
||||||
|
// the reference dataset, revisit.
|
||||||
path.SetNavTerrainCost(NAV_GROUND_STEEP, 5.0f);
|
path.SetNavTerrainCost(NAV_GROUND_STEEP, 5.0f);
|
||||||
path.SetNavTerrainCost(NAV_WATER, 10.0f);
|
path.SetNavTerrainCost(NAV_WATER, 10.0f);
|
||||||
auto result = getPathStepFrom(startPos, path);
|
auto result = getPathStepFrom(startPos, path);
|
||||||
@ -1943,93 +1971,6 @@ void TravelMgr::LoadQuestTravelTable()
|
|||||||
units.push_back(t_unit);
|
units.push_back(t_unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// 0 1 2 3 4 5 6 7 8
|
|
||||||
std::string const query = "SELECT 0,guid,id,map,position_x,position_y,position_z,orientation, (SELECT COUNT(*) FROM
|
|
||||||
creature k WHERE c.id1 = k.id1) FROM creature c UNION ALL SELECT
|
|
||||||
1,guid,id,map,position_x,position_y,position_z,orientation, (SELECT COUNT(*) FROM gameobject h WHERE h.id = g.id)
|
|
||||||
FROM gameobject g";
|
|
||||||
|
|
||||||
QueryResult result = WorldDatabase.Query(query.c_str());
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
Field* fields = result->Fetch();
|
|
||||||
|
|
||||||
t_unit.type = fields[0].Get<uint32>();
|
|
||||||
t_unit.guid = fields[1].Get<uint32>();
|
|
||||||
t_unit.entry = fields[2].Get<uint32>();
|
|
||||||
t_unit.map = fields[3].Get<uint32>();
|
|
||||||
t_unit.x = fields[4].Get<float>();
|
|
||||||
t_unit.y = fields[5].Get<float>();
|
|
||||||
t_unit.z = fields[6].Get<float>();
|
|
||||||
t_unit.o = fields[7].Get<float>();
|
|
||||||
t_unit.c = uint32(fields[8].Get<uint64>());
|
|
||||||
|
|
||||||
units.push_back(t_unit);
|
|
||||||
|
|
||||||
} while (result->NextRow());
|
|
||||||
|
|
||||||
LOG_INFO("playerbots", ">> Loaded {} units locations.", units.size());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_ERROR("playerbots", ">> Error loading units locations.");
|
|
||||||
}
|
|
||||||
|
|
||||||
query = "SELECT 0, 0, id, quest FROM creature_queststarter UNION ALL SELECT 0, 1, id, quest FROM creature_questender
|
|
||||||
UNION ALL SELECT 1, 0, id, quest FROM gameobject_queststarter UNION ALL SELECT 1, 1, id, quest FROM
|
|
||||||
gameobject_questender"; result = WorldDatabase.Query(query.c_str());
|
|
||||||
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
Field* fields = result->Fetch();
|
|
||||||
|
|
||||||
t_rel.type = fields[0].Get<uint32>();
|
|
||||||
t_rel.role = fields[1].Get<uint32>();
|
|
||||||
t_rel.entry = fields[2].Get<uint32>();
|
|
||||||
t_rel.questId = fields[3].Get<uint32>();
|
|
||||||
|
|
||||||
relations.push_back(t_rel);
|
|
||||||
|
|
||||||
} while (result->NextRow());
|
|
||||||
|
|
||||||
LOG_INFO("playerbots", ">> Loaded {} relations.", relations.size());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_ERROR("playerbots", ">> Error loading relations.");
|
|
||||||
}
|
|
||||||
|
|
||||||
query = "SELECT 0, ct.entry, item FROM creature_template ct JOIN creature_loot_template clt ON (ct.lootid =
|
|
||||||
clt.entry) UNION ALL SELECT 0, entry, item FROM npc_vendor UNION ALL SELECT 1, gt.entry, item FROM
|
|
||||||
gameobject_template gt JOIN gameobject_loot_template glt ON (gt.TYPE = 3 AND gt.DATA1 = glt.entry)"; result =
|
|
||||||
WorldDatabase.Query(query.c_str());
|
|
||||||
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
Field* fields = result->Fetch();
|
|
||||||
|
|
||||||
t_loot.type = fields[0].Get<uint32>();
|
|
||||||
t_loot.entry = fields[1].Get<uint32>();
|
|
||||||
t_loot.item = fields[2].Get<uint32>();
|
|
||||||
|
|
||||||
loots.push_back(t_loot);
|
|
||||||
|
|
||||||
} while (result->NextRow());
|
|
||||||
|
|
||||||
LOG_INFO("playerbots", ">> Loaded {} loot lists.", loots.size());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_ERROR("playerbots", ">> Error loading loot lists.");
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
LOG_INFO("playerbots", "Loading quest data.");
|
LOG_INFO("playerbots", "Loading quest data.");
|
||||||
|
|
||||||
@ -2115,164 +2056,6 @@ void TravelMgr::LoadQuestTravelTable()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
if (loadQuestData && false)
|
|
||||||
{
|
|
||||||
for (auto& questId : questIds)
|
|
||||||
{
|
|
||||||
Quest* quest = questMap.find(questId)->second;
|
|
||||||
|
|
||||||
QuestContainer* container = new QuestContainer;
|
|
||||||
QuestTravelDestination* loc = nullptr;
|
|
||||||
WorldPosition point;
|
|
||||||
|
|
||||||
bool hasError = false;
|
|
||||||
|
|
||||||
//Relations
|
|
||||||
for (auto& r : relations)
|
|
||||||
{
|
|
||||||
if (questId != r.questId)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int32 entry = r.type == 0 ? r.entry : r.entry * -1;
|
|
||||||
|
|
||||||
loc = new QuestRelationTravelDestination(r.questId, entry, r.role, sPlayerbotAIConfig.tooCloseDistance,
|
|
||||||
sPlayerbotAIConfig.sightDistance); loc->setExpireDelay(5 * 60 * 1000); loc->setMaxVisitors(15, 0);
|
|
||||||
|
|
||||||
for (auto& u : units)
|
|
||||||
{
|
|
||||||
if (r.type != u.type || r.entry != u.entry)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int32 guid = u.type == 0 ? u.guid : u.guid * -1;
|
|
||||||
|
|
||||||
point = WorldPosition(u.map, u.x, u.y, u.z, u.o);
|
|
||||||
loc->addPoint(&point);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loc->getPoints(0).empty())
|
|
||||||
{
|
|
||||||
logQuestError(1, quest, r.role, entry);
|
|
||||||
delete loc;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r.role == 0)
|
|
||||||
{
|
|
||||||
container->questGivers.push_back(loc);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
container->questTakers.push_back(loc);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//Mobs
|
|
||||||
for (uint32 i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
if (quest->RequiredNpcOrGoCount[i] == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
uint32 reqEntry = quest->RequiredNpcOrGo[i];
|
|
||||||
|
|
||||||
loc = new QuestObjectiveTravelDestination(questId, reqEntry, i, sPlayerbotAIConfig.tooCloseDistance,
|
|
||||||
sPlayerbotAIConfig.sightDistance); loc->setExpireDelay(1 * 60 * 1000); loc->setMaxVisitors(100, 1);
|
|
||||||
|
|
||||||
for (auto& u : units)
|
|
||||||
{
|
|
||||||
int32 entry = u.type == 0 ? u.entry : u.entry * -1;
|
|
||||||
|
|
||||||
if (entry != reqEntry)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int32 guid = u.type == 0 ? u.guid : u.guid * -1;
|
|
||||||
|
|
||||||
point = WorldPosition(u.map, u.x, u.y, u.z, u.o);
|
|
||||||
loc->addPoint(&point);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loc->getPoints(0).empty())
|
|
||||||
{
|
|
||||||
logQuestError(2, quest, i, reqEntry);
|
|
||||||
|
|
||||||
delete loc;
|
|
||||||
hasError = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
container->questObjectives.push_back(loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Loot
|
|
||||||
for (uint32 i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
if (quest->RequiredItemCount[i] == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(quest->RequiredItemId[i]);
|
|
||||||
if (!proto)
|
|
||||||
{
|
|
||||||
logQuestError(3, quest, i, 0, quest->RequiredItemId[i]);
|
|
||||||
hasError = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32 foundLoot = 0;
|
|
||||||
|
|
||||||
for (auto& l : loots)
|
|
||||||
{
|
|
||||||
if (l.item != quest->RequiredItemId[i])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int32 entry = l.type == 0 ? l.entry : l.entry * -1;
|
|
||||||
|
|
||||||
loc = new QuestObjectiveTravelDestination(questId, entry, i, sPlayerbotAIConfig.tooCloseDistance,
|
|
||||||
sPlayerbotAIConfig.sightDistance, l.item); loc->setExpireDelay(1 * 60 * 1000); loc->setMaxVisitors(100, 1);
|
|
||||||
|
|
||||||
for (auto& u : units)
|
|
||||||
{
|
|
||||||
if (l.type != u.type || l.entry != u.entry)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int32 guid = u.type == 0 ? u.guid : u.guid * -1;
|
|
||||||
|
|
||||||
point = WorldPosition(u.map, u.x, u.y, u.z, u.o);
|
|
||||||
loc->addPoint(&point);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loc->getPoints(0).empty())
|
|
||||||
{
|
|
||||||
logQuestError(4, quest, i, entry, quest->RequiredItemId[i]);
|
|
||||||
delete loc;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
container->questObjectives.push_back(loc);
|
|
||||||
|
|
||||||
foundLoot++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundLoot == 0)
|
|
||||||
{
|
|
||||||
hasError = true;
|
|
||||||
logQuestError(5, quest, i, 0, quest->RequiredItemId[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (container->questTakers.empty())
|
|
||||||
logQuestError(7, quest);
|
|
||||||
|
|
||||||
if (!container->questGivers.empty() || !container->questTakers.empty() || hasError)
|
|
||||||
{
|
|
||||||
quests.insert(std::make_pair(questId, container));
|
|
||||||
|
|
||||||
for (auto loc : container->questGivers)
|
|
||||||
questGivers.push_back(loc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO("playerbots", ">> Loaded {} quest details.", questIds.size());
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
WorldPosition point;
|
WorldPosition point;
|
||||||
|
|
||||||
@ -2407,531 +2190,7 @@ void TravelMgr::LoadQuestTravelTable()
|
|||||||
|
|
||||||
// Node loading/generation is handled by TravelNodeMap::Init() called from TravelMgr::Init().
|
// Node loading/generation is handled by TravelNodeMap::Init() called from TravelMgr::Init().
|
||||||
|
|
||||||
/*
|
|
||||||
bool fullNavPointReload = false;
|
|
||||||
bool storeNavPointReload = true;
|
|
||||||
|
|
||||||
if (!fullNavPointReload && true)
|
|
||||||
TravelNodeStore::loadNodes();
|
|
||||||
|
|
||||||
//TravelNodeMap::instance().loadNodeStore();
|
|
||||||
|
|
||||||
for (auto node : TravelNodeMap::instance().getNodes())
|
|
||||||
{
|
|
||||||
node->setLinked(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool reloadNavigationPoints = false || fullNavPointReload || storeNavPointReload;
|
|
||||||
|
|
||||||
if (reloadNavigationPoints)
|
|
||||||
{
|
|
||||||
LOG_INFO("playerbots", "Loading navigation points");
|
|
||||||
|
|
||||||
//Npc nodes
|
|
||||||
|
|
||||||
WorldPosition pos;
|
|
||||||
|
|
||||||
for (auto& u : units)
|
|
||||||
{
|
|
||||||
if (u.type != 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(u.entry);
|
|
||||||
if (!cInfo)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::vector<uint32> allowedNpcFlags;
|
|
||||||
|
|
||||||
allowedNpcFlags.push_back(UNIT_NPC_FLAG_INNKEEPER);
|
|
||||||
allowedNpcFlags.push_back(UNIT_NPC_FLAG_FLIGHTMASTER);
|
|
||||||
//allowedNpcFlags.push_back(UNIT_NPC_FLAG_QUESTGIVER);
|
|
||||||
|
|
||||||
for (std::vector<uint32>::iterator i = allowedNpcFlags.begin(); i != allowedNpcFlags.end(); ++i)
|
|
||||||
{
|
|
||||||
if ((cInfo->npcflag & *i) != 0)
|
|
||||||
{
|
|
||||||
pos = WorldPosition(u.map, u.x, u.y, u.z, u.o);
|
|
||||||
|
|
||||||
std::string const nodeName = pos.getAreaName(false);
|
|
||||||
if ((cInfo->npcflag & UNIT_NPC_FLAG_INNKEEPER) != 0)
|
|
||||||
nodeName += " innkeeper";
|
|
||||||
else
|
|
||||||
nodeName += " flightMaster";
|
|
||||||
|
|
||||||
TravelNodeMap::instance().addNode(&pos, nodeName, true, true);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Build flight paths
|
|
||||||
for (uint32 i = 0; i < sTaxiPathStore.GetNumRows(); ++i)
|
|
||||||
{
|
|
||||||
TaxiPathEntry const* taxiPath = sTaxiPathStore.LookupEntry(i);
|
|
||||||
|
|
||||||
if (!taxiPath)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
TaxiNodesEntry const* startTaxiNode = sTaxiNodesStore.LookupEntry(taxiPath->from);
|
|
||||||
if (!startTaxiNode)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
TaxiNodesEntry const* endTaxiNode = sTaxiNodesStore.LookupEntry(taxiPath->to);
|
|
||||||
if (!endTaxiNode)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
TaxiPathNodeList const& nodes = sTaxiPathNodesByPath[taxiPath->ID];
|
|
||||||
if (nodes.empty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
WorldPosition startPos(startTaxiNode->map_id, startTaxiNode->x, startTaxiNode->y, startTaxiNode->z);
|
|
||||||
WorldPosition endPos(endTaxiNode->map_id, endTaxiNode->x, endTaxiNode->y, endTaxiNode->z);
|
|
||||||
|
|
||||||
TravelNode* startNode = TravelNodeMap::instance().getNode(&startPos, nullptr, 15.0f);
|
|
||||||
TravelNode* endNode = TravelNodeMap::instance().getNode(&endPos, nullptr, 15.0f);
|
|
||||||
|
|
||||||
if (!startNode || !endNode)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::vector<WorldPosition> ppath;
|
|
||||||
|
|
||||||
for (auto& n : nodes)
|
|
||||||
ppath.push_back(WorldPosition(n->mapid, n->x, n->y, n->z, 0.0));
|
|
||||||
|
|
||||||
float totalTime = startPos.getPathLength(ppath) / (450 * 8.0f);
|
|
||||||
|
|
||||||
TravelNodePath travelPath(0.1f, totalTime, (uint8) TravelNodePathType::flightPath, i, true);
|
|
||||||
travelPath.setPath(ppath);
|
|
||||||
|
|
||||||
startNode->setPathTo(endNode, travelPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Unique bosses
|
|
||||||
for (auto& u : units)
|
|
||||||
{
|
|
||||||
if (u.type != 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(u.entry);
|
|
||||||
if (!cInfo)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
pos = WorldPosition(u.map, u.x, u.y, u.z, u.o);
|
|
||||||
|
|
||||||
if (cInfo->rank == 3 || (cInfo->rank == 1 && !pos.isOverworld() && u.c == 1))
|
|
||||||
{
|
|
||||||
std::string const nodeName = cInfo->Name;
|
|
||||||
TravelNodeMap::instance().addNode(&pos, nodeName, true, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<uint8, std::string> startNames;
|
|
||||||
startNames[RACE_HUMAN] = "Human";
|
|
||||||
startNames[RACE_ORC] = "Orc and Troll";
|
|
||||||
startNames[RACE_DWARF] = "Dwarf and Gnome";
|
|
||||||
startNames[RACE_NIGHTELF] = "Night Elf";
|
|
||||||
startNames[RACE_UNDEAD_PLAYER] = "Undead";
|
|
||||||
startNames[RACE_TAUREN] = "Tauren";
|
|
||||||
startNames[RACE_GNOME] = "Dwarf and Gnome";
|
|
||||||
startNames[RACE_TROLL] = "Orc and Troll";
|
|
||||||
startNames[RACE_DRAENEI] = "Draenei";
|
|
||||||
startNames[RACE_BLOODELF] = "Blood Elf";
|
|
||||||
|
|
||||||
for (uint32 i = 0; i < MAX_RACES; i++)
|
|
||||||
{
|
|
||||||
for (uint32 j = 0; j < MAX_CLASSES; j++)
|
|
||||||
{
|
|
||||||
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(i, j);
|
|
||||||
if (!info)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
pos = WorldPosition(info->mapId, info->positionX, info->positionY, info->positionZ, info->orientation);
|
|
||||||
|
|
||||||
std::string const nodeName = startNames[i] + " start";
|
|
||||||
TravelNodeMap::instance().addNode(&pos, nodeName, true, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Transports
|
|
||||||
GameObjectTemplateContainer const* goTemplates = sObjectMgr->GetGameObjectTemplates();
|
|
||||||
for (auto const& iter : *goTemplates)
|
|
||||||
{
|
|
||||||
GameObjectTemplate const* data = &iter.second;
|
|
||||||
if (data && (data->type == GAMEOBJECT_TYPE_TRANSPORT || data->type == GAMEOBJECT_TYPE_MO_TRANSPORT))
|
|
||||||
{
|
|
||||||
TransportAnimation const* animation = sTransportMgr->GetTransportAnimInfo(iter.first);
|
|
||||||
|
|
||||||
uint32 pathId = data->moTransport.taxiPathId;
|
|
||||||
float moveSpeed = data->moTransport.moveSpeed;
|
|
||||||
if (pathId >= sTaxiPathNodesByPath.size())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId];
|
|
||||||
|
|
||||||
std::vector<WorldPosition> ppath;
|
|
||||||
TravelNode* prevNode = nullptr;
|
|
||||||
|
|
||||||
//Elevators/Trams
|
|
||||||
if (path.empty())
|
|
||||||
{
|
|
||||||
if (animation)
|
|
||||||
{
|
|
||||||
TransportPathContainer aPath = animation->Path;
|
|
||||||
float timeStart;
|
|
||||||
|
|
||||||
for (auto& u : units)
|
|
||||||
{
|
|
||||||
if (u.type != 1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (u.entry != iter.first)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
prevNode = nullptr;
|
|
||||||
WorldPosition lPos = WorldPosition(u.map, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
for (auto& p : aPath)
|
|
||||||
{
|
|
||||||
float dx = cos(u.o) * p.second->X - sin(u.o) * p.second->Y;
|
|
||||||
float dy = sin(u.o) * p.second->X + cos(u.o) * p.second->Y;
|
|
||||||
WorldPosition pos = WorldPosition(u.map, u.x + dx, u.y + dy, u.z + p.second->Z, u.o);
|
|
||||||
|
|
||||||
if (prevNode)
|
|
||||||
{
|
|
||||||
ppath.push_back(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos.distance(&lPos) == 0)
|
|
||||||
{
|
|
||||||
TravelNode* node = TravelNodeMap::instance().addNode(&pos, data->name, true, true, true,
|
|
||||||
iter.first);
|
|
||||||
|
|
||||||
if (!prevNode)
|
|
||||||
{
|
|
||||||
ppath.push_back(pos);
|
|
||||||
timeStart = p.second->TimeSeg;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
float totalTime = (p.second->TimeSeg - timeStart) / 1000.0f;
|
|
||||||
TravelNodePath travelPath(0.1f, totalTime, (uint8)
|
|
||||||
TravelNodePathType::transport, entry, true); node->setPathTo(prevNode, travelPath); ppath.clear();
|
|
||||||
ppath.push_back(pos);
|
|
||||||
timeStart = p.second->TimeSeg;
|
|
||||||
}
|
|
||||||
|
|
||||||
prevNode = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
lPos = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevNode)
|
|
||||||
{
|
|
||||||
for (auto& p : aPath)
|
|
||||||
{
|
|
||||||
float dx = cos(u.o) * p.second->X - sin(u.o) * p.second->Y;
|
|
||||||
float dy = sin(u.o) * p.second->X + cos(u.o) * p.second->Y;
|
|
||||||
WorldPosition pos = WorldPosition(u.map, u.x + dx, u.y + dy, u.z + p.second->Z,
|
|
||||||
u.o);
|
|
||||||
|
|
||||||
ppath.push_back(pos);
|
|
||||||
|
|
||||||
if (pos.distance(&lPos) == 0)
|
|
||||||
{
|
|
||||||
TravelNode* node = TravelNodeMap::instance().addNode(&pos, data->name, true, true, true,
|
|
||||||
iter.first); if (node != prevNode)
|
|
||||||
{
|
|
||||||
float totalTime = (p.second->TimeSeg - timeStart) / 1000.0f;
|
|
||||||
TravelNodePath travelPath(0.1f, totalTime, (uint8)
|
|
||||||
TravelNodePathType::transport, entry, true); travelPath.setPath(ppath); node->setPathTo(prevNode, travelPath);
|
|
||||||
ppath.clear();
|
|
||||||
ppath.push_back(pos);
|
|
||||||
timeStart = p.second->TimeSeg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lPos = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ppath.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else //Boats/Zepelins
|
|
||||||
{
|
|
||||||
//Loop over the path and connect stop locations.
|
|
||||||
for (auto& p : path)
|
|
||||||
{
|
|
||||||
WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);
|
|
||||||
|
|
||||||
//if (data->displayId == 3015)
|
|
||||||
// pos.setZ(pos.getZ() + 6.0f);
|
|
||||||
//else if (data->displayId == 3031)
|
|
||||||
// pos.setZ(pos.getZ() - 17.0f);
|
|
||||||
|
|
||||||
if (prevNode)
|
|
||||||
{
|
|
||||||
ppath.push_back(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p->delay > 0)
|
|
||||||
{
|
|
||||||
TravelNode* node = TravelNodeMap::instance().addNode(&pos, data->name, true, true, true, iter.first);
|
|
||||||
|
|
||||||
if (!prevNode)
|
|
||||||
{
|
|
||||||
ppath.push_back(pos);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TravelNodePath travelPath(0.1f, 0.0, (uint8) TravelNodePathType::transport, entry,
|
|
||||||
true); travelPath.setPathAndCost(ppath, moveSpeed); node->setPathTo(prevNode, travelPath); ppath.clear();
|
|
||||||
ppath.push_back(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
prevNode = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevNode)
|
|
||||||
{
|
|
||||||
//Continue from start until first stop and connect to end.
|
|
||||||
for (auto& p : path)
|
|
||||||
{
|
|
||||||
WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);
|
|
||||||
|
|
||||||
//if (data->displayId == 3015)
|
|
||||||
// pos.setZ(pos.getZ() + 6.0f);
|
|
||||||
//else if (data->displayId == 3031)
|
|
||||||
// pos.setZ(pos.getZ() - 17.0f);
|
|
||||||
|
|
||||||
ppath.push_back(pos);
|
|
||||||
|
|
||||||
if (p->delay > 0)
|
|
||||||
{
|
|
||||||
TravelNode* node = TravelNodeMap::instance().getNode(&pos, nullptr, 5.0f);
|
|
||||||
if (node != prevNode)
|
|
||||||
{
|
|
||||||
TravelNodePath travelPath(0.1f, 0.0, (uint8) TravelNodePathType::transport, entry,
|
|
||||||
true); travelPath.setPathAndCost(ppath, moveSpeed); node->setPathTo(prevNode, travelPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ppath.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Zone means
|
|
||||||
for (auto& loc : exploreLocs)
|
|
||||||
{
|
|
||||||
std::vector<WorldPosition*> points;
|
|
||||||
|
|
||||||
for (auto p : loc.second->getPoints(true))
|
|
||||||
if (!p->isUnderWater())
|
|
||||||
points.push_back(p);
|
|
||||||
|
|
||||||
if (points.empty())
|
|
||||||
points = loc.second->getPoints(true);
|
|
||||||
|
|
||||||
WorldPosition pos = WorldPosition(points, WP_MEAN_CENTROID);
|
|
||||||
|
|
||||||
TravelNode* node = TravelNodeMap::instance().addNode(&pos, pos.getAreaName(), true, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO("playerbots", ">> Loaded {} navigation points.", TravelNodeMap::instance().getNodes().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
TravelNodeMap::instance().calcMapOffset();
|
|
||||||
loadMapTransfers();
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
bool preloadNodePaths = false || fullNavPointReload || storeNavPointReload; //Calculate paths using
|
|
||||||
PathGenerator. bool preloadReLinkFullyLinked = false || fullNavPointReload || storeNavPointReload; //Retry
|
|
||||||
nodes that are fully linked. bool preloadUnlinkedPaths = false || fullNavPointReload; //Try to connect points
|
|
||||||
currently unlinked. bool preloadWorldPaths = true; //Try to load paths in overworld. bool
|
|
||||||
preloadInstancePaths = true; //Try to load paths in instances. bool preloadSubPrint = false; //Print output
|
|
||||||
every 2%.
|
|
||||||
|
|
||||||
if (preloadNodePaths)
|
|
||||||
{
|
|
||||||
std::unordered_map<uint32, Map*> instances;
|
|
||||||
|
|
||||||
//PathGenerator
|
|
||||||
std::vector<WorldPosition> ppath;
|
|
||||||
|
|
||||||
uint32 cur = 0, max = TravelNodeMap::instance().getNodes().size();
|
|
||||||
|
|
||||||
for (auto& startNode : TravelNodeMap::instance().getNodes())
|
|
||||||
{
|
|
||||||
if (!preloadReLinkFullyLinked && startNode->isLinked())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (auto& endNode : TravelNodeMap::instance().getNodes())
|
|
||||||
{
|
|
||||||
if (startNode == endNode)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (startNode->getPosition()->isOverworld() && !preloadWorldPaths)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!startNode->getPosition()->isOverworld() && !preloadInstancePaths)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (startNode->hasCompletePathTo(endNode))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!preloadUnlinkedPaths && !startNode->hasLinkTo(endNode))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (startNode->getMapId() != endNode->getMapId())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
//if (preloadUnlinkedPaths && !startNode->hasLinkTo(endNode) && startNode->isUselessLink(endNode))
|
|
||||||
// continue;
|
|
||||||
|
|
||||||
startNode->BuildPath(endNode, nullptr, false);
|
|
||||||
|
|
||||||
//if (startNode->hasLinkTo(endNode) && !startNode->getPathTo(endNode)->getComplete())
|
|
||||||
//startNode->removeLinkTo(endNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
startNode->setLinked(true);
|
|
||||||
|
|
||||||
cur++;
|
|
||||||
|
|
||||||
if (preloadSubPrint && (cur * 50) / max > ((cur - 1) * 50) / max)
|
|
||||||
{
|
|
||||||
TravelNodeMap::instance().printMap();
|
|
||||||
TravelNodeMap::instance().printNodeStore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!preloadSubPrint)
|
|
||||||
{
|
|
||||||
TravelNodeMap::instance().printNodeStore();
|
|
||||||
TravelNodeMap::instance().printMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO("playerbots", ">> Loaded paths for {} nodes.", TravelNodeMap::instance().getNodes().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool removeLowLinkNodes = false || fullNavPointReload || storeNavPointReload;
|
|
||||||
|
|
||||||
if (removeLowLinkNodes)
|
|
||||||
{
|
|
||||||
std::vector<TravelNode*> goodNodes;
|
|
||||||
std::vector<TravelNode*> remNodes;
|
|
||||||
for (auto& node : TravelNodeMap::instance().getNodes())
|
|
||||||
{
|
|
||||||
if (!node->getPosition()->isOverworld())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (std::find(goodNodes.begin(), goodNodes.end(), node) != goodNodes.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (std::find(remNodes.begin(), remNodes.end(), node) != remNodes.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::vector<TravelNode*> nodes = node->getNodeMap(true);
|
|
||||||
|
|
||||||
if (nodes.size() < 5)
|
|
||||||
remNodes.insert(remNodes.end(), nodes.begin(), nodes.end());
|
|
||||||
else
|
|
||||||
goodNodes.insert(goodNodes.end(), nodes.begin(), nodes.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& node : remNodes)
|
|
||||||
TravelNodeMap::instance().removeNode(node);
|
|
||||||
|
|
||||||
LOG_INFO("playerbots", ">> Checked {} nodes.", TravelNodeMap::instance().getNodes().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool cleanUpNodeLinks = false || fullNavPointReload || storeNavPointReload;
|
|
||||||
bool cleanUpSubPrint = false; //Print output every 2%.
|
|
||||||
|
|
||||||
if (cleanUpNodeLinks)
|
|
||||||
{
|
|
||||||
//Routes
|
|
||||||
uint32 cur = 0;
|
|
||||||
uint32 max = TravelNodeMap::instance().getNodes().size();
|
|
||||||
|
|
||||||
//Clean up node links
|
|
||||||
for (auto& startNode : TravelNodeMap::instance().getNodes())
|
|
||||||
{
|
|
||||||
startNode->cropUselessLinks();
|
|
||||||
|
|
||||||
cur++;
|
|
||||||
if (cleanUpSubPrint && (cur * 10) / max > ((cur - 1) * 10) / max)
|
|
||||||
{
|
|
||||||
TravelNodeMap::instance().printMap();
|
|
||||||
TravelNodeMap::instance().printNodeStore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO("playerbots", ">> Cleaned paths for {} nodes.", TravelNodeMap::instance().getNodes().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool reCalculateCost = false || fullNavPointReload || storeNavPointReload;
|
|
||||||
bool forceReCalculate = false;
|
|
||||||
|
|
||||||
if (reCalculateCost)
|
|
||||||
{
|
|
||||||
for (auto& startNode : TravelNodeMap::instance().getNodes())
|
|
||||||
{
|
|
||||||
for (auto& path : *startNode->getLinks())
|
|
||||||
{
|
|
||||||
TravelNodePath* nodePath = path.second;
|
|
||||||
|
|
||||||
if (path.second->getPathType() != TravelNodePathType::walk)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (nodePath->getCalculated() && !forceReCalculate)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
nodePath->calculateCost();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO("playerbots", ">> Calculated pathcost for {} nodes.", TravelNodeMap::instance().getNodes().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mirrorMissingPaths = true || fullNavPointReload || storeNavPointReload;
|
|
||||||
|
|
||||||
if (mirrorMissingPaths)
|
|
||||||
{
|
|
||||||
for (auto& startNode : TravelNodeMap::instance().getNodes())
|
|
||||||
{
|
|
||||||
for (auto& path : *startNode->getLinks())
|
|
||||||
{
|
|
||||||
TravelNode* endNode = path.first;
|
|
||||||
|
|
||||||
if (endNode->hasLinkTo(startNode))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (path.second->getPathType() != TravelNodePathType::walk)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
TravelNodePath nodePath = *path.second;
|
|
||||||
|
|
||||||
std::vector<WorldPosition> pPath = nodePath.GetPath();
|
|
||||||
std::reverse(pPath.begin(), pPath.end());
|
|
||||||
|
|
||||||
nodePath.setPath(pPath);
|
|
||||||
|
|
||||||
endNode->setPathTo(startNode, nodePath, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO("playerbots", ">> Reversed missing paths for {} nodes.", TravelNodeMap::instance().getNodes().size());
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
TravelNodeMap::instance().printMap();
|
TravelNodeMap::instance().printMap();
|
||||||
TravelNodeMap::instance().printNodeStore();
|
TravelNodeMap::instance().printNodeStore();
|
||||||
@ -3686,65 +2945,6 @@ void TravelMgr::LoadQuestTravelTable()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
sPlayerbotAIConfig.openLog(7, "w");
|
|
||||||
|
|
||||||
//Zone area map REMOVE!
|
|
||||||
uint32 k = 0;
|
|
||||||
for (auto& node : TravelNodeMap::instance().getNodes())
|
|
||||||
{
|
|
||||||
WorldPosition* pos = node->getPosition();
|
|
||||||
//map area
|
|
||||||
for (uint32 x = 0; x < 2000; x++)
|
|
||||||
{
|
|
||||||
for (uint32 y = 0; y < 2000; y++)
|
|
||||||
{
|
|
||||||
if (!pos->getMap())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
float nx = pos->GetPositionX() + (x * 5) - 5000.0f;
|
|
||||||
float ny = pos->GetPositionY() + (y * 5) - 5000.0f;
|
|
||||||
float nz = pos->GetPositionZ() + 100.0f;
|
|
||||||
|
|
||||||
//pos->getMap()->GetHitPosition(nx, ny, nz + 200.0f, nx, ny, nz, -0.5f);
|
|
||||||
|
|
||||||
if (!pos->getMap()->GetHeightInRange(nx, ny, nz, 5000.0f)) // GetHeight can fail
|
|
||||||
continue;
|
|
||||||
|
|
||||||
WorldPosition npos = WorldPosition(pos->GetMapId(), nx, ny, nz, 0.0);
|
|
||||||
uint32 area = path.getArea(npos.GetMapId(), npos.GetPositionX(), npos.GetPositionY(),
|
|
||||||
npos.GetPositionZ());
|
|
||||||
|
|
||||||
std::ostringstream out;
|
|
||||||
out << std::fixed << area << "," << npos.getDisplayX() << "," << npos.getDisplayY();
|
|
||||||
sPlayerbotAIConfig.log(7, out.str().c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
k++;
|
|
||||||
|
|
||||||
if (k > 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Explore map output (REMOVE!)
|
|
||||||
|
|
||||||
sPlayerbotAIConfig.openLog(5, "w");
|
|
||||||
for (auto i : exploreLocs)
|
|
||||||
{
|
|
||||||
for (auto j : i.second->getPoints())
|
|
||||||
{
|
|
||||||
std::ostringstream out;
|
|
||||||
std::string const name = i.second->getTitle();
|
|
||||||
name.erase(remove(name.begin(), name.end(), '\"'), name.end());
|
|
||||||
out << std::fixed << std::setprecision(2) << name.c_str() << "," << i.first << "," << j->getDisplayX() <<
|
|
||||||
"," << j->getDisplayY() << "," << j->GetPositionX() << "," << j->GetPositionY() << "," << j->GetPositionZ();
|
|
||||||
sPlayerbotAIConfig.log(5,
|
|
||||||
out.str().c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 TravelMgr::getDialogStatus(Player* pPlayer, int32 questgiver, Quest const* pQuest)
|
uint32 TravelMgr::getDialogStatus(Player* pPlayer, int32 questgiver, Quest const* pQuest)
|
||||||
|
|||||||
@ -138,6 +138,9 @@ public:
|
|||||||
bool isOverworld();
|
bool isOverworld();
|
||||||
bool isInWater();
|
bool isInWater();
|
||||||
bool isUnderWater();
|
bool isUnderWater();
|
||||||
|
// Snap Z to the water surface (level + 0.5y). Returns false if the
|
||||||
|
// point isn't in/under water or the water level can't be sampled.
|
||||||
|
bool setAtWaterSurface();
|
||||||
bool IsValid();
|
bool IsValid();
|
||||||
|
|
||||||
WorldPosition relPoint(WorldPosition* center);
|
WorldPosition relPoint(WorldPosition* center);
|
||||||
|
|||||||
@ -425,11 +425,6 @@ bool TravelNode::isUselessLink(TravelNode* farNode)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TravelNode::cropUselessLink(TravelNode* farNode)
|
|
||||||
{
|
|
||||||
if (isUselessLink(farNode))
|
|
||||||
removeLinkTo(farNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TravelNode::cropUselessLinks()
|
bool TravelNode::cropUselessLinks()
|
||||||
{
|
{
|
||||||
@ -475,135 +470,8 @@ bool TravelNode::cropUselessLinks()
|
|||||||
|
|
||||||
return hasRemoved;
|
return hasRemoved;
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
//std::vector<std::pair<TravelNode*, TravelNode*>> toRemove;
|
|
||||||
for (auto& firstLink : getLinks())
|
|
||||||
{
|
|
||||||
|
|
||||||
TravelNode* firstNode = firstLink.first;
|
|
||||||
float firstLength = firstLink.second.getDistance();
|
|
||||||
for (auto& secondLink : getLinks())
|
|
||||||
{
|
|
||||||
TravelNode* secondNode = secondLink.first;
|
|
||||||
float secondLength = secondLink.second.getDistance();
|
|
||||||
|
|
||||||
if (firstNode == secondNode)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (std::find(toRemove.begin(), toRemove.end(), [firstNode, secondNode](std::pair<TravelNode*, TravelNode*>
|
|
||||||
pair) {return pair.first == firstNode || pair.first == secondNode;}) != toRemove.end()) continue;
|
|
||||||
|
|
||||||
if (firstNode->hasLinkTo(secondNode))
|
|
||||||
{
|
|
||||||
//Is it quicker to go past first node to reach second node instead of going directly?
|
|
||||||
if (firstLength + firstNode->linkLengthTo(secondNode) < secondLength * 1.1)
|
|
||||||
{
|
|
||||||
if (secondNode->hasLinkTo(this) && !firstNode->hasLinkTo(this))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
toRemove.push_back(make_pair(this, secondNode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TravelNodeRoute route = TravelNodeMap::instance().GetNodeRoute(firstNode, secondNode, nullptr);
|
|
||||||
|
|
||||||
if (route.isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (route.hasNode(this))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
//Is it quicker to go past first (and multiple) nodes to reach the second node instead of going
|
|
||||||
directly? if (firstLength + route.getLength() < secondLength * 1.1)
|
|
||||||
{
|
|
||||||
if (secondNode->hasLinkTo(this) && !firstNode->hasLinkTo(this))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
toRemove.push_back(make_pair(this, secondNode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Reverse cleanup. This is needed when we add a node in an existing map.
|
|
||||||
if (firstNode->hasLinkTo(this))
|
|
||||||
{
|
|
||||||
firstLength = firstNode->getPathTo(this)->getDistance();
|
|
||||||
|
|
||||||
for (auto& secondLink : firstNode->getLinks())
|
|
||||||
{
|
|
||||||
TravelNode* secondNode = secondLink.first;
|
|
||||||
float secondLength = secondLink.second.getDistance();
|
|
||||||
|
|
||||||
if (this == secondNode)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (std::find(toRemove.begin(), toRemove.end(), [firstNode, secondNode](std::pair<TravelNode*,
|
|
||||||
TravelNode*> pair) {return pair.first == firstNode || pair.first == secondNode; }) != toRemove.end()) continue;
|
|
||||||
|
|
||||||
if (firstNode->hasLinkTo(secondNode))
|
|
||||||
{
|
|
||||||
//Is it quicker to go past first node to reach second node instead of going directly?
|
|
||||||
if (firstLength + firstNode->linkLengthTo(secondNode) < secondLength * 1.1)
|
|
||||||
{
|
|
||||||
if (secondNode->hasLinkTo(this) && !firstNode->hasLinkTo(this))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
toRemove.push_back(make_pair(this, secondNode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TravelNodeRoute route = TravelNodeMap::instance().GetNodeRoute(firstNode, secondNode, nullptr);
|
|
||||||
|
|
||||||
if (route.isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (route.hasNode(this))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
//Is it quicker to go past first (and multiple) nodes to reach the second node instead of going
|
|
||||||
directly? if (firstLength + route.getLength() < secondLength * 1.1)
|
|
||||||
{
|
|
||||||
if (secondNode->hasLinkTo(this) && !firstNode->hasLinkTo(this))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
toRemove.push_back(make_pair(this, secondNode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& nodePair : toRemove)
|
|
||||||
nodePair.first->unlinkNode(nodePair.second, false);
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TravelNode::isEqual(TravelNode* compareNode)
|
|
||||||
{
|
|
||||||
if (!hasLinkTo(compareNode))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!compareNode->hasLinkTo(this))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (auto& node : TravelNodeMap::instance().getNodes())
|
|
||||||
{
|
|
||||||
if (node == this || node == compareNode)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (node->hasLinkTo(this) != node->hasLinkTo(compareNode))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (hasLinkTo(node) != compareNode->hasLinkTo(node))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TravelNode::print([[maybe_unused]] bool printFailed)
|
void TravelNode::print([[maybe_unused]] bool printFailed)
|
||||||
{
|
{
|
||||||
@ -889,6 +757,11 @@ bool TravelPath::UpcommingSpecialMovement(WorldPosition startPos,
|
|||||||
{
|
{
|
||||||
if (startP->entry)
|
if (startP->entry)
|
||||||
{
|
{
|
||||||
|
// Reference also checks an AreaTriggerEntry DBC store
|
||||||
|
// (sAreaTriggerStore). AC doesn't expose a separate DBC
|
||||||
|
// store for area triggers — sObjectMgr->GetAreaTrigger is
|
||||||
|
// the loaded view of the same data, so it's the only
|
||||||
|
// existence check we need on this side.
|
||||||
AreaTrigger const* at = sObjectMgr->GetAreaTrigger(startP->entry);
|
AreaTrigger const* at = sObjectMgr->GetAreaTrigger(startP->entry);
|
||||||
if (!at)
|
if (!at)
|
||||||
return false;
|
return false;
|
||||||
@ -912,13 +785,6 @@ bool TravelPath::UpcommingSpecialMovement(WorldPosition startPos,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Teleport spell (hearthstone et al.): fire on the next-step marker.
|
|
||||||
if (nextP->type == PathNodeType::NODE_TELEPORT)
|
|
||||||
{
|
|
||||||
cutTo(*nextP, false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flight path: interact with flight master when in range.
|
// Flight path: interact with flight master when in range.
|
||||||
if (startP->type == PathNodeType::NODE_FLIGHTPATH &&
|
if (startP->type == PathNodeType::NODE_FLIGHTPATH &&
|
||||||
startPos.distance(startP->point) < INTERACTION_DISTANCE)
|
startPos.distance(startP->point) < INTERACTION_DISTANCE)
|
||||||
@ -927,12 +793,12 @@ bool TravelPath::UpcommingSpecialMovement(WorldPosition startPos,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transport boarding/disembark. We don't expose a teleport-vs-walk
|
// Board-and-ride mode (transportSkipRide == false). Cut to dock if
|
||||||
// toggle yet, so always take the walk-on-board path: cut to dock if
|
|
||||||
// off-transport, traverse to disembark if on-transport.
|
// off-transport, traverse to disembark if on-transport.
|
||||||
if (startP->type == PathNodeType::NODE_TRANSPORT)
|
if (!sPlayerbotAIConfig.transportSkipRide &&
|
||||||
|
startP->type == PathNodeType::NODE_TRANSPORT)
|
||||||
{
|
{
|
||||||
uint32 entry = nextP->entry;
|
uint32 const entry = nextP->entry;
|
||||||
|
|
||||||
if (!onTransport)
|
if (!onTransport)
|
||||||
{
|
{
|
||||||
@ -954,6 +820,24 @@ bool TravelPath::UpcommingSpecialMovement(WorldPosition startPos,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip-ride mode (transportSkipRide == true): bot is approaching a
|
||||||
|
// transport node — walk forward to find the first non-transport node
|
||||||
|
// (the disembark side), cut to prevP (last transport node) so
|
||||||
|
// HandleSpecialMovement teleports the bot across directly.
|
||||||
|
if (sPlayerbotAIConfig.transportSkipRide &&
|
||||||
|
nextP->type == PathNodeType::NODE_TRANSPORT)
|
||||||
|
{
|
||||||
|
for (auto p = std::next(startP); p != fullPath.end(); ++p)
|
||||||
|
{
|
||||||
|
if (p->type != PathNodeType::NODE_TRANSPORT)
|
||||||
|
{
|
||||||
|
cutTo(*prevP, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
prevP = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -997,7 +881,13 @@ void TravelPath::ClipPath(PlayerbotAI* ai, Unit* mover, bool ignoreEnemyTargets)
|
|||||||
float const range = cre->GetAttackDistance(mover);
|
float const range = cre->GetAttackDistance(mover);
|
||||||
if (WorldPosition(unit).sqDistance(p->point) > range * range)
|
if (WorldPosition(unit).sqDistance(p->point) > range * range)
|
||||||
continue;
|
continue;
|
||||||
if (!unit->IsHostileTo(mover) || !unit->IsWithinLOSInMap(mover))
|
// Reference uses CanAttackOnSight (faction + sanctuary +
|
||||||
|
// feign + phased + passive flags). AC equivalent is
|
||||||
|
// CanCreatureAttack with skipDistCheck=true (range already
|
||||||
|
// confirmed above). IsHostileTo alone over-clipped at
|
||||||
|
// neutral mobs that wouldn't actually aggro.
|
||||||
|
if (!cre->CanCreatureAttack(mover, true) ||
|
||||||
|
!unit->IsWithinLOSInMap(mover))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
endP = p;
|
endP = p;
|
||||||
@ -1029,6 +919,22 @@ void TravelPath::ClipPath(PlayerbotAI* ai, Unit* mover, bool ignoreEnemyTargets)
|
|||||||
fullPath.erase(std::next(endP), fullPath.end());
|
fullPath.erase(std::next(endP), fullPath.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TravelPath::surfaceSnapWaypoints(WorldPosition endPos)
|
||||||
|
{
|
||||||
|
if (fullPath.empty())
|
||||||
|
return;
|
||||||
|
// Same map + dest is on land. If dest is itself underwater the bot
|
||||||
|
// wants to dive; leave waypoints alone.
|
||||||
|
if (fullPath.front().point.GetMapId() != endPos.GetMapId() ||
|
||||||
|
endPos.isUnderWater())
|
||||||
|
return;
|
||||||
|
for (auto& p : fullPath)
|
||||||
|
{
|
||||||
|
if (p.point.isUnderWater())
|
||||||
|
p.point.setAtWaterSurface();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool TravelPath::makeShortCut(WorldPosition startPos, float maxDist, Unit* bot)
|
bool TravelPath::makeShortCut(WorldPosition startPos, float maxDist, Unit* bot)
|
||||||
{
|
{
|
||||||
if (GetPath().empty())
|
if (GetPath().empty())
|
||||||
@ -1221,14 +1127,6 @@ TravelPath TravelNodeRoute::BuildPath(std::vector<WorldPosition> pathToStart, st
|
|||||||
// Full taxi waypoint route; same reasoning as transport.
|
// Full taxi waypoint route; same reasoning as transport.
|
||||||
travelPath.addPath(nodePath->GetPath(), PathNodeType::NODE_FLIGHTPATH, nodePath->getPathObject());
|
travelPath.addPath(nodePath->GetPath(), PathNodeType::NODE_FLIGHTPATH, nodePath->getPathObject());
|
||||||
}
|
}
|
||||||
else if (nodePath->getPathType() == TravelNodePathType::teleportSpell)
|
|
||||||
{
|
|
||||||
// Hearthstone or spell-cast teleport edge: emit a paired
|
|
||||||
// NODE_TELEPORT (entry = exit) so HandleSpecialMovement can
|
|
||||||
// dispatch the cast when the head reaches the entry point.
|
|
||||||
travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_TELEPORT, nodePath->getPathObject());
|
|
||||||
travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_TELEPORT, nodePath->getPathObject());
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::vector<WorldPosition> path = nodePath->GetPath();
|
std::vector<WorldPosition> path = nodePath->GetPath();
|
||||||
@ -1417,8 +1315,6 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal,
|
|||||||
|
|
||||||
std::vector<TravelNodeStub*> open, closed;
|
std::vector<TravelNodeStub*> open, closed;
|
||||||
|
|
||||||
std::vector<TravelNode*> portNodes; // synthetic teleport/portal edges
|
|
||||||
|
|
||||||
if (bot)
|
if (bot)
|
||||||
{
|
{
|
||||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||||
@ -1455,97 +1351,20 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal,
|
|||||||
PAI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::travel));
|
PAI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::travel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hearthstone (item 6948 / spell 8690): inject a synthetic
|
|
||||||
// teleport edge from start to the node nearest the bot's
|
|
||||||
// home bind, so A* can pick hearthing over walking.
|
|
||||||
if (bot->IsAlive() && bot->HasItemCount(6948, 1))
|
|
||||||
{
|
|
||||||
WorldPosition homePos = AI_VALUE(WorldPosition, "home bind");
|
|
||||||
std::vector<WorldPosition> dummy;
|
|
||||||
TravelNode* homeNode = sTravelNodeMap.getNode(homePos, dummy, nullptr, 50.0f);
|
|
||||||
if (homeNode && homeNode != start)
|
|
||||||
{
|
|
||||||
PortalNode* portNode = new PortalNode(start);
|
|
||||||
portNode->SetPortal(start, homeNode, 8690);
|
|
||||||
|
|
||||||
TravelNodeStub* hsStub = &m_stubs.insert(std::make_pair(
|
|
||||||
static_cast<TravelNode*>(portNode), TravelNodeStub(portNode))).first->second;
|
|
||||||
|
|
||||||
// Cost: max(2, (10 - deathCount) * MINUTE) — matches
|
|
||||||
// reference exactly, including the uint32 underflow at
|
|
||||||
// deathCount > 10 (which makes hearthstone prohibitive
|
|
||||||
// for very-dead bots — apparently intentional).
|
|
||||||
hsStub->costFromStart = std::max<uint32>(2,
|
|
||||||
(10 - AI_VALUE(uint32, "death count")) * MINUTE);
|
|
||||||
hsStub->heuristic = hsStub->dataNode->fDist(goal) / botSpeed;
|
|
||||||
hsStub->totalCost = hsStub->costFromStart + hsStub->heuristic;
|
|
||||||
|
|
||||||
open.push_back(hsStub);
|
|
||||||
hsStub->open = true;
|
|
||||||
portNodes.push_back(portNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mage teleport spells: 3561 Stormwind, 3562 Ironforge, 3563 Undercity,
|
|
||||||
// 3565 Darnassus, 3566 Thunder Bluff, 3567 Orgrimmar, 18960 Moonglade.
|
|
||||||
// Inject one synthetic teleport edge per known + ready spell.
|
|
||||||
static const uint32 teleSpells[] = {3561, 3562, 3563, 3565, 3566, 3567, 18960};
|
|
||||||
for (uint32 spellId : teleSpells)
|
|
||||||
{
|
|
||||||
if (!bot->IsAlive() || bot->IsInCombat())
|
|
||||||
break;
|
|
||||||
if (!bot->HasSpell(spellId))
|
|
||||||
continue;
|
|
||||||
if (bot->HasSpellCooldown(spellId))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
SpellTargetPosition const* stp =
|
|
||||||
sSpellMgr->GetSpellTargetPosition(spellId, EFFECT_0);
|
|
||||||
if (!stp)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
WorldPosition telePos(stp->target_mapId, stp->target_X,
|
|
||||||
stp->target_Y, stp->target_Z, 0.0f);
|
|
||||||
std::vector<WorldPosition> dummy;
|
|
||||||
TravelNode* destNode = sTravelNodeMap.getNode(telePos, dummy, nullptr, 10.0f);
|
|
||||||
if (!destNode || destNode == start)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
PortalNode* portNode = new PortalNode(start);
|
|
||||||
portNode->SetPortal(start, destNode, spellId);
|
|
||||||
|
|
||||||
TravelNodeStub* tsStub = &m_stubs.insert(std::make_pair(
|
|
||||||
static_cast<TravelNode*>(portNode), TravelNodeStub(portNode))).first->second;
|
|
||||||
|
|
||||||
tsStub->costFromStart = MINUTE; // cheaper than ~1-min walk
|
|
||||||
tsStub->heuristic = tsStub->dataNode->fDist(goal) / botSpeed;
|
|
||||||
tsStub->totalCost = tsStub->costFromStart + tsStub->heuristic;
|
|
||||||
|
|
||||||
open.push_back(tsStub);
|
|
||||||
tsStub->open = true;
|
|
||||||
portNodes.push_back(portNode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
startStub->currentGold = bot->GetMoney();
|
startStub->currentGold = bot->GetMoney();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open.empty() && !start->hasRouteTo(goal))
|
if (!start->hasRouteTo(goal))
|
||||||
{
|
|
||||||
for (auto* p : portNodes)
|
|
||||||
delete p;
|
|
||||||
return TravelNodeRoute();
|
return TravelNodeRoute();
|
||||||
}
|
|
||||||
|
|
||||||
// Min-heap: smallest f at front
|
// Min-heap: smallest f at front
|
||||||
auto heapComp = [](TravelNodeStub* i, TravelNodeStub* j) { return i->totalCost > j->totalCost; };
|
auto heapComp = [](TravelNodeStub* i, TravelNodeStub* j) { return i->totalCost > j->totalCost; };
|
||||||
|
|
||||||
open.push_back(startStub);
|
open.push_back(startStub);
|
||||||
startStub->open = true;
|
startStub->open = true;
|
||||||
// Heapify all of open in one pass — covers both startStub and any
|
std::push_heap(open.begin(), open.end(), heapComp);
|
||||||
// PortalNode stubs injected above.
|
|
||||||
std::make_heap(open.begin(), open.end(), heapComp);
|
|
||||||
|
|
||||||
while (!open.empty())
|
while (!open.empty())
|
||||||
{
|
{
|
||||||
@ -1571,12 +1390,7 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal,
|
|||||||
}
|
}
|
||||||
|
|
||||||
reverse(path.begin(), path.end());
|
reverse(path.begin(), path.end());
|
||||||
|
return TravelNodeRoute(path);
|
||||||
// Successful route: hand off ownership of any synthetic
|
|
||||||
// PortalNodes injected at the head. Caller (GetFullPath)
|
|
||||||
// is expected to call cleanTempNodes() when done with the
|
|
||||||
// route — see the call site for the lifecycle.
|
|
||||||
return TravelNodeRoute(path, portNodes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto const& link : *currentNode->dataNode->getLinks()) // for each successor n' of n
|
for (auto const& link : *currentNode->dataNode->getLinks()) // for each successor n' of n
|
||||||
@ -1615,9 +1429,6 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A* exhausted open without reaching goal. Clean up synthetic nodes.
|
|
||||||
for (auto* p : portNodes)
|
|
||||||
delete p;
|
|
||||||
return TravelNodeRoute();
|
return TravelNodeRoute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1697,7 +1508,7 @@ TravelNodeRoute TravelNodeMap::FindRouteNearestNodes(WorldPosition startPos, Wor
|
|||||||
return TravelNodeRoute();
|
return TravelNodeRoute();
|
||||||
}
|
}
|
||||||
|
|
||||||
TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos, [[maybe_unused]] uint32 botZoneId,
|
TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos,
|
||||||
WorldPosition destination, Unit* bot)
|
WorldPosition destination, Unit* bot)
|
||||||
{
|
{
|
||||||
TravelPath path;
|
TravelPath path;
|
||||||
@ -1705,14 +1516,14 @@ TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos, [[maybe_unused]] uin
|
|||||||
// Probe-first short-circuit (matches reference exactly): if a 40-step
|
// Probe-first short-circuit (matches reference exactly): if a 40-step
|
||||||
// mmap probe from bot to destination reaches within spellDistance of
|
// mmap probe from bot to destination reaches within spellDistance of
|
||||||
// dest, use the probe directly and skip graph routing. Otherwise
|
// dest, use the probe directly and skip graph routing. Otherwise
|
||||||
// fall through to the graph A* below — the failed probe waypoints
|
// the probe waypoints are kept as `beginPath` and fed into per-
|
||||||
// would ideally feed into getRoute as startPath (reference does
|
// candidate startPath cropping below.
|
||||||
// this; we don't yet — TODO).
|
std::vector<WorldPosition> beginPath;
|
||||||
if (botPos.GetMapId() == destination.GetMapId())
|
if (botPos.GetMapId() == destination.GetMapId())
|
||||||
{
|
{
|
||||||
std::vector<WorldPosition> probe = destination.getPathFromPath({botPos}, bot, 40);
|
beginPath = destination.getPathFromPath({botPos}, bot, 40);
|
||||||
if (destination.isPathTo(probe, sPlayerbotAIConfig.spellDistance))
|
if (destination.isPathTo(beginPath, sPlayerbotAIConfig.spellDistance))
|
||||||
return TravelPath(probe);
|
return TravelPath(beginPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_lock<std::shared_timed_mutex> guard(m_nMapMtx);
|
std::shared_lock<std::shared_timed_mutex> guard(m_nMapMtx);
|
||||||
@ -1810,15 +1621,21 @@ TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos, [[maybe_unused]] uin
|
|||||||
if (transportEntry)
|
if (transportEntry)
|
||||||
{
|
{
|
||||||
path = route.BuildPath({botPos}, endProbe, bot);
|
path = route.BuildPath({botPos}, endProbe, bot);
|
||||||
route.cleanTempNodes();
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate bot -> startNode is pathable within maxStartDistance.
|
// Validate bot -> startNode is pathable within maxStartDistance.
|
||||||
|
// Reference reuses the (failed) probe waypoints first via
|
||||||
|
// cropPathTo, falling back to a fresh getPathTo only if the
|
||||||
|
// probe can't be cropped to reach startNode. This saves
|
||||||
|
// re-running mmap when the probe already covers part of
|
||||||
|
// the journey to startNode.
|
||||||
float const maxStartDistance = s->isTransport() ? 20.0f : 1.0f;
|
float const maxStartDistance = s->isTransport() ? 20.0f : 1.0f;
|
||||||
std::vector<WorldPosition> pathToStart;
|
std::vector<WorldPosition> pathToStart = beginPath;
|
||||||
bool startPathOk = false;
|
bool startPathOk = !pathToStart.empty() &&
|
||||||
if (bot && botPos.GetMapId() == startNodePos.GetMapId())
|
startNodePos.cropPathTo(pathToStart, maxStartDistance);
|
||||||
|
|
||||||
|
if (!startPathOk && bot && botPos.GetMapId() == startNodePos.GetMapId())
|
||||||
{
|
{
|
||||||
pathToStart = botPos.getPathTo(startNodePos, bot);
|
pathToStart = botPos.getPathTo(startNodePos, bot);
|
||||||
startPathOk = startNodePos.isPathTo(pathToStart, maxStartDistance);
|
startPathOk = startNodePos.isPathTo(pathToStart, maxStartDistance);
|
||||||
@ -1827,139 +1644,21 @@ TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos, [[maybe_unused]] uin
|
|||||||
if (!startPathOk)
|
if (!startPathOk)
|
||||||
{
|
{
|
||||||
badStartNodes.push_back(s);
|
badStartNodes.push_back(s);
|
||||||
route.cleanTempNodes();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both ends validated — build and return.
|
// Both ends validated — build and return. Save the
|
||||||
|
// successful pathToStart back as beginPath so subsequent
|
||||||
|
// ResolveMovePath cycles can reuse it.
|
||||||
|
beginPath = pathToStart;
|
||||||
path = route.BuildPath(pathToStart, endProbe, bot);
|
path = route.BuildPath(pathToStart, endProbe, bot);
|
||||||
route.cleanTempNodes();
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No graph route found. Last-resort hearthstone fallback (reference
|
|
||||||
// also does this): if bot has hearthstone item and is alive, treat
|
|
||||||
// the bot's current position as a one-off node and try routing from
|
|
||||||
// it to each endCandidate via the hearthstone PortalNode edge.
|
|
||||||
if (Player* player = dynamic_cast<Player*>(bot))
|
|
||||||
{
|
|
||||||
if (player->IsAlive() && player->HasItemCount(6948, 1))
|
|
||||||
{
|
|
||||||
TravelNode* botNode = new TravelNode(botPos, "Bot Pos", false);
|
|
||||||
botNode->setPoint(botPos);
|
|
||||||
|
|
||||||
for (TravelNode* e : endCandidates)
|
|
||||||
{
|
|
||||||
if (!e || std::find(badEndNodes.begin(), badEndNodes.end(), e) != badEndNodes.end())
|
|
||||||
continue;
|
|
||||||
TravelNodeRoute route = GetNodeRoute(botNode, e, player);
|
|
||||||
if (route.isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Build the end-side path again for this candidate.
|
|
||||||
WorldPosition endNodePos = *e->getPosition();
|
|
||||||
std::vector<WorldPosition> endProbe;
|
|
||||||
if (endNodePos.GetMapId() == destination.GetMapId())
|
|
||||||
{
|
|
||||||
Unit* pathBot = (bot && bot->GetMapId() == destination.GetMapId()) ? bot : nullptr;
|
|
||||||
endProbe = endNodePos.getPathTo(destination, pathBot);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
endProbe = {endNodePos, destination};
|
|
||||||
|
|
||||||
route.addTempNodes({botNode}); // transfer ownership of botNode
|
|
||||||
path = route.BuildPath({botPos}, endProbe, bot);
|
|
||||||
route.cleanTempNodes();
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
delete botNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path; // empty
|
return path; // empty
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TravelNodeMap::cropUselessNode(TravelNode* startNode)
|
|
||||||
{
|
|
||||||
if (!startNode->isLinked() || startNode->isImportant())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
std::vector<TravelNode*> ignore = {startNode};
|
|
||||||
|
|
||||||
for (auto& node : getNodes(*startNode->getPosition(), 5000.f))
|
|
||||||
{
|
|
||||||
if (startNode == node)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (node->getNodeMap(true).size() > node->getNodeMap(true, ignore).size())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeNode(startNode);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TravelNode* TravelNodeMap::addZoneLinkNode(TravelNode* startNode)
|
|
||||||
{
|
|
||||||
for (auto& path : *startNode->getPaths())
|
|
||||||
{
|
|
||||||
//TravelNode* endNode = path.first; //not used, line marked for removal.
|
|
||||||
|
|
||||||
std::string zoneName = startNode->getPosition()->getAreaName(true, true);
|
|
||||||
for (auto& pos : path.second.GetPath())
|
|
||||||
{
|
|
||||||
std::string const newZoneName = pos.getAreaName(true, true);
|
|
||||||
if (zoneName != newZoneName)
|
|
||||||
{
|
|
||||||
if (!getNode(pos, nullptr, 100.0f))
|
|
||||||
{
|
|
||||||
std::string const nodeName = zoneName + " to " + newZoneName;
|
|
||||||
return TravelNodeMap::instance().addNode(pos, nodeName, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
zoneName = newZoneName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
TravelNode* TravelNodeMap::addRandomExtNode(TravelNode* startNode)
|
|
||||||
{
|
|
||||||
std::unordered_map<TravelNode*, TravelNodePath> paths = *startNode->getPaths();
|
|
||||||
|
|
||||||
if (paths.empty())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
for (uint32 i = 0; i < 20; i++)
|
|
||||||
{
|
|
||||||
auto random_it = std::next(std::begin(paths), urand(0, paths.size() - 1));
|
|
||||||
|
|
||||||
TravelNode* endNode = random_it->first;
|
|
||||||
std::vector<WorldPosition> path = random_it->second.GetPath();
|
|
||||||
|
|
||||||
if (path.empty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Prefer to skip complete links
|
|
||||||
if (endNode->hasLinkTo(startNode) && startNode->hasLinkTo(endNode) && !urand(0, 20))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Prefer to skip no links
|
|
||||||
if (!startNode->hasLinkTo(endNode) && !urand(0, 20))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
WorldPosition point = path[urand(0, path.size() - 1)];
|
|
||||||
|
|
||||||
if (!getNode(point, nullptr, 100.0f))
|
|
||||||
return TravelNodeMap::instance().addNode(point, startNode->getName(), false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TravelNodeMap::generateNpcNodes()
|
void TravelNodeMap::generateNpcNodes()
|
||||||
{
|
{
|
||||||
@ -2421,7 +2120,6 @@ void TravelNodeMap::generateAll()
|
|||||||
hasToSave = true;
|
hasToSave = true;
|
||||||
saveNodeStore();
|
saveNodeStore();
|
||||||
|
|
||||||
BuildZoneIndex();
|
|
||||||
PrecomputeReachability();
|
PrecomputeReachability();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2446,7 +2144,6 @@ void TravelNodeMap::Init()
|
|||||||
saveNodeStore();
|
saveNodeStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildZoneIndex();
|
|
||||||
PrecomputeReachability();
|
PrecomputeReachability();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2515,16 +2212,6 @@ void TravelNodeMap::printNodeStore()
|
|||||||
out << "," << (node->isTransport() ? "true" : "false") << "," << node->getTransportId();
|
out << "," << (node->isTransport() ? "true" : "false") << "," << node->getTransportId();
|
||||||
out << "});";
|
out << "});";
|
||||||
|
|
||||||
/*
|
|
||||||
out << std::fixed << std::setprecision(2) << " nodes[" << i << "] =
|
|
||||||
TravelNodeMap::instance().addNode(&WorldPosition(" << node->GetMapId() << "," << node->getX() << "f," << node->getY()
|
|
||||||
<< "f," << node->getZ() << "f,"<< node->getO() <<"f), \""
|
|
||||||
<< name << "\", " << (node->isImportant() ? "true" : "false") << ", true";
|
|
||||||
if (node->isTransport())
|
|
||||||
out << "," << (node->isTransport() ? "true" : "false") << "," << node->getTransportId();
|
|
||||||
|
|
||||||
out << ");";
|
|
||||||
*/
|
|
||||||
sPlayerbotAIConfig.log(nodeStore, out.str().c_str());
|
sPlayerbotAIConfig.log(nodeStore, out.str().c_str());
|
||||||
|
|
||||||
saveNodes.insert(std::make_pair(node, i));
|
saveNodes.insert(std::make_pair(node, i));
|
||||||
@ -3056,87 +2743,6 @@ std::vector<uint32> TravelNodeMap::BuildPath(uint32 fromNode, uint32 toNode,
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TravelNodeMap::BuildZoneIndex()
|
|
||||||
{
|
|
||||||
m_zoneIndex.clear();
|
|
||||||
m_mapIndex.clear();
|
|
||||||
|
|
||||||
for (auto* node : nodes)
|
|
||||||
{
|
|
||||||
if (!node)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
WorldPosition* pos = node->getPosition();
|
|
||||||
uint32 mapId = pos->GetMapId();
|
|
||||||
|
|
||||||
m_mapIndex[mapId].push_back(node);
|
|
||||||
|
|
||||||
uint32 zoneId = sMapMgr->GetZoneId(PHASEMASK_NORMAL, *pos);
|
|
||||||
if (zoneId)
|
|
||||||
m_zoneIndex[zoneId].push_back(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TravelNode* TravelNodeMap::GetNearestNodeInZone(WorldPosition pos, uint32 zoneId)
|
|
||||||
{
|
|
||||||
auto it = m_zoneIndex.find(zoneId);
|
|
||||||
if (it == m_zoneIndex.end() || it->second.empty())
|
|
||||||
return GetNearestNodeOnMap(pos); // Fallback to map-wide
|
|
||||||
|
|
||||||
TravelNode* bestNode = nullptr;
|
|
||||||
float bestDist = FLT_MAX;
|
|
||||||
|
|
||||||
for (auto* node : it->second)
|
|
||||||
{
|
|
||||||
if (!node || node->GetMapId() != pos.GetMapId())
|
|
||||||
continue;
|
|
||||||
float dist = node->fDist(pos);
|
|
||||||
if (dist < bestDist)
|
|
||||||
{
|
|
||||||
bestDist = dist;
|
|
||||||
bestNode = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bestNode)
|
|
||||||
return GetNearestNodeOnMap(pos);
|
|
||||||
|
|
||||||
return bestNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<TravelNode*> const& TravelNodeMap::GetNodesInZone(uint32 zoneId) const
|
|
||||||
{
|
|
||||||
static std::vector<TravelNode*> const empty;
|
|
||||||
auto it = m_zoneIndex.find(zoneId);
|
|
||||||
if (it == m_zoneIndex.end())
|
|
||||||
return empty;
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
TravelNode* TravelNodeMap::GetNearestNodeOnMap(WorldPosition pos)
|
|
||||||
{
|
|
||||||
auto it = m_mapIndex.find(pos.GetMapId());
|
|
||||||
if (it == m_mapIndex.end() || it->second.empty())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
TravelNode* bestNode = nullptr;
|
|
||||||
float bestDist = FLT_MAX;
|
|
||||||
|
|
||||||
for (auto* node : it->second)
|
|
||||||
{
|
|
||||||
if (!node)
|
|
||||||
continue;
|
|
||||||
float d = node->fDist(pos);
|
|
||||||
if (d < bestDist)
|
|
||||||
{
|
|
||||||
bestDist = d;
|
|
||||||
bestNode = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bestNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TravelNodeMap::PrecomputeReachability()
|
void TravelNodeMap::PrecomputeReachability()
|
||||||
{
|
{
|
||||||
// Find connected components via BFS
|
// Find connected components via BFS
|
||||||
|
|||||||
@ -86,8 +86,6 @@
|
|||||||
// (saveNodeStore) and by the debug dump command.
|
// (saveNodeStore) and by the debug dump command.
|
||||||
//
|
//
|
||||||
|
|
||||||
constexpr float MAX_PATHFINDING_DISTANCE = 296.0f;
|
|
||||||
|
|
||||||
enum class TravelNodePathType : uint8
|
enum class TravelNodePathType : uint8
|
||||||
{
|
{
|
||||||
none = 0,
|
none = 0,
|
||||||
@ -95,10 +93,7 @@ enum class TravelNodePathType : uint8
|
|||||||
areaTrigger = 2,
|
areaTrigger = 2,
|
||||||
transport = 3,
|
transport = 3,
|
||||||
flightPath = 4,
|
flightPath = 4,
|
||||||
// Teleport-spell edges (hearthstone, mage portals). Generated at A*
|
// teleportSpell = 5 // maybe someday
|
||||||
// search start via PortalNode injection; consumed by
|
|
||||||
// HandleSpecialMovement's NODE_TELEPORT case.
|
|
||||||
teleportSpell = 5,
|
|
||||||
staticPortal = 6
|
staticPortal = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -358,11 +353,8 @@ public:
|
|||||||
}
|
}
|
||||||
void removeLinkTo(TravelNode* node, bool removePaths = false);
|
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.
|
// Removes links to other nodes that can also be reached by passing another node.
|
||||||
bool isUselessLink(TravelNode* farNode);
|
bool isUselessLink(TravelNode* farNode);
|
||||||
void cropUselessLink(TravelNode* farNode);
|
|
||||||
bool cropUselessLinks();
|
bool cropUselessLinks();
|
||||||
|
|
||||||
// Returns all nodes that can be reached from this node.
|
// Returns all nodes that can be reached from this node.
|
||||||
@ -410,26 +402,6 @@ protected:
|
|||||||
// uint32 transportId = 0;
|
// uint32 transportId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Synthetic A* node injected at search start to represent a teleport-spell
|
|
||||||
// (hearthstone, mage portal, etc.) as an alternative travel edge. Owned
|
|
||||||
// by GetNodeRoute caller; deleted after the route is built.
|
|
||||||
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
|
// Route step type
|
||||||
enum class PathNodeType : uint8
|
enum class PathNodeType : uint8
|
||||||
{
|
{
|
||||||
@ -439,10 +411,7 @@ enum class PathNodeType : uint8
|
|||||||
NODE_AREA_TRIGGER = 3,
|
NODE_AREA_TRIGGER = 3,
|
||||||
NODE_TRANSPORT = 4,
|
NODE_TRANSPORT = 4,
|
||||||
NODE_FLIGHTPATH = 5,
|
NODE_FLIGHTPATH = 5,
|
||||||
// Teleport-spell endpoint (hearthstone, mage portal). Emitted by
|
// value 6 reserved (was NODE_TELEPORT — removed with teleportSpell)
|
||||||
// TravelNodeRoute::BuildPath when traversing a teleportSpell-type
|
|
||||||
// edge; consumed by HandleSpecialMovement.
|
|
||||||
NODE_TELEPORT = 6,
|
|
||||||
NODE_STATIC_PORTAL = 7
|
NODE_STATIC_PORTAL = 7
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -517,6 +486,14 @@ public:
|
|||||||
|
|
||||||
bool makeShortCut(WorldPosition startPos, float maxDist, Unit* bot = nullptr);
|
bool makeShortCut(WorldPosition startPos, float maxDist, Unit* bot = nullptr);
|
||||||
|
|
||||||
|
// For each waypoint that's in/under water, snap its Z to the water
|
||||||
|
// surface. No-op when destination is itself underwater (caller wants
|
||||||
|
// the bot to dive) or path's front map differs from dest map.
|
||||||
|
// Mirrors the reference's underwater→surface snap so bots swim
|
||||||
|
// along the top of shallow water on land-bound paths instead of
|
||||||
|
// diving and air-walking the seafloor.
|
||||||
|
void surfaceSnapWaypoints(WorldPosition endPos);
|
||||||
|
|
||||||
// Trim the path up to (and optionally including) the given point.
|
// Trim the path up to (and optionally including) the given point.
|
||||||
// Returns true if the point was found. Used by upcoming special-
|
// Returns true if the point was found. Used by upcoming special-
|
||||||
// movement detection to advance the path past a portal/transport/
|
// movement detection to advance the path past a portal/transport/
|
||||||
@ -575,14 +552,6 @@ public:
|
|||||||
{
|
{
|
||||||
nodes = nodes1;
|
nodes = nodes1;
|
||||||
}
|
}
|
||||||
TravelNodeRoute(std::vector<TravelNode*> nodes1,
|
|
||||||
std::vector<TravelNode*> const& tempNodes_)
|
|
||||||
{
|
|
||||||
nodes = nodes1;
|
|
||||||
if (!tempNodes_.empty())
|
|
||||||
addTempNodes(tempNodes_);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isEmpty() { return nodes.empty(); }
|
bool isEmpty() { return nodes.empty(); }
|
||||||
|
|
||||||
bool hasNode(TravelNode* node)
|
bool hasNode(TravelNode* node)
|
||||||
@ -593,19 +562,6 @@ public:
|
|||||||
|
|
||||||
std::vector<TravelNode*> getNodes() { return nodes; }
|
std::vector<TravelNode*> getNodes() { return nodes; }
|
||||||
|
|
||||||
// Take ownership of synthetic A* nodes (PortalNode etc.). Must call
|
|
||||||
// cleanTempNodes() to delete them when the route is no longer needed.
|
|
||||||
void addTempNodes(std::vector<TravelNode*> const& tempNodes_)
|
|
||||||
{
|
|
||||||
tempNodes.insert(tempNodes.end(), tempNodes_.begin(), tempNodes_.end());
|
|
||||||
}
|
|
||||||
void cleanTempNodes()
|
|
||||||
{
|
|
||||||
for (auto* n : tempNodes)
|
|
||||||
delete n;
|
|
||||||
tempNodes.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
TravelPath BuildPath(
|
TravelPath BuildPath(
|
||||||
std::vector<WorldPosition> pathToStart = {},
|
std::vector<WorldPosition> pathToStart = {},
|
||||||
std::vector<WorldPosition> pathToEnd = {},
|
std::vector<WorldPosition> pathToEnd = {},
|
||||||
@ -619,7 +575,6 @@ private:
|
|||||||
return std::find(nodes.begin(), nodes.end(), node);
|
return std::find(nodes.begin(), nodes.end(), node);
|
||||||
}
|
}
|
||||||
std::vector<TravelNode*> nodes;
|
std::vector<TravelNode*> nodes;
|
||||||
std::vector<TravelNode*> tempNodes; // owned synthetic nodes (PortalNode etc.)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// A node container to aid A* calculations with nodes.
|
// A node container to aid A* calculations with nodes.
|
||||||
@ -746,9 +701,6 @@ public:
|
|||||||
void saveNodeStore();
|
void saveNodeStore();
|
||||||
void LoadNodeStore();
|
void LoadNodeStore();
|
||||||
|
|
||||||
bool cropUselessNode(TravelNode* startNode);
|
|
||||||
TravelNode* addZoneLinkNode(TravelNode* startNode);
|
|
||||||
TravelNode* addRandomExtNode(TravelNode* startNode);
|
|
||||||
|
|
||||||
void calcMapOffset();
|
void calcMapOffset();
|
||||||
WorldPosition getMapOffset(uint32 mapId);
|
WorldPosition getMapOffset(uint32 mapId);
|
||||||
@ -757,20 +709,12 @@ public:
|
|||||||
void InitTaxiGraph();
|
void InitTaxiGraph();
|
||||||
std::vector<uint32> FindTaxiPath(uint32 fromNode, uint32 toNode);
|
std::vector<uint32> FindTaxiPath(uint32 fromNode, uint32 toNode);
|
||||||
|
|
||||||
void BuildZoneIndex();
|
|
||||||
void PrecomputeReachability();
|
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;
|
|
||||||
|
|
||||||
// Resolve a full TravelPath from botPos to destination. Returns an
|
// Resolve a full TravelPath from botPos to destination. Returns an
|
||||||
// empty TravelPath if no graph route + mmap stitch is reachable;
|
// empty TravelPath if no graph route + mmap stitch is reachable;
|
||||||
// the caller is then expected to fall back to a single-point path.
|
// the caller is then expected to fall back to a single-point path.
|
||||||
TravelPath GetFullPath(WorldPosition botPos, uint32 botZoneId,
|
TravelPath GetFullPath(WorldPosition botPos,
|
||||||
WorldPosition destination, Unit* bot = nullptr);
|
WorldPosition destination, Unit* bot = nullptr);
|
||||||
|
|
||||||
// Resolve A* route between two world positions (returns node vector)
|
// Resolve A* route between two world positions (returns node vector)
|
||||||
@ -807,9 +751,6 @@ private:
|
|||||||
|
|
||||||
std::vector<TravelNode*> nodes;
|
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;
|
std::vector<std::pair<uint32, WorldPosition>> mapOffsets;
|
||||||
|
|
||||||
bool hasToSave = false;
|
bool hasToSave = false;
|
||||||
|
|||||||
@ -88,6 +88,7 @@ bool PlayerbotAIConfig::Initialize()
|
|||||||
|
|
||||||
farDistance = sConfigMgr->GetOption<float>("AiPlayerbot.FarDistance", 20.0f);
|
farDistance = sConfigMgr->GetOption<float>("AiPlayerbot.FarDistance", 20.0f);
|
||||||
sightDistance = sConfigMgr->GetOption<float>("AiPlayerbot.SightDistance", 100.0f);
|
sightDistance = sConfigMgr->GetOption<float>("AiPlayerbot.SightDistance", 100.0f);
|
||||||
|
transportSkipRide = sConfigMgr->GetOption<bool>("AiPlayerbot.TransportSkipRide", false);
|
||||||
spellDistance = sConfigMgr->GetOption<float>("AiPlayerbot.SpellDistance", 28.5f);
|
spellDistance = sConfigMgr->GetOption<float>("AiPlayerbot.SpellDistance", 28.5f);
|
||||||
shootDistance = sConfigMgr->GetOption<float>("AiPlayerbot.ShootDistance", 5.0f);
|
shootDistance = sConfigMgr->GetOption<float>("AiPlayerbot.ShootDistance", 5.0f);
|
||||||
healDistance = sConfigMgr->GetOption<float>("AiPlayerbot.HealDistance", 38.5f);
|
healDistance = sConfigMgr->GetOption<float>("AiPlayerbot.HealDistance", 38.5f);
|
||||||
|
|||||||
@ -93,6 +93,12 @@ public:
|
|||||||
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
|
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
|
||||||
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, expireActionTime,
|
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, expireActionTime,
|
||||||
dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay;
|
dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay;
|
||||||
|
// Transport handling:
|
||||||
|
// false (default) = teleport-board, ride the transport, teleport-disembark
|
||||||
|
// true = skip the ride entirely (teleport directly across)
|
||||||
|
// AC has no transport-surface mmap so an in-deck walking mode can't be
|
||||||
|
// faithfully implemented — the on-board phase always teleport-snaps.
|
||||||
|
bool transportSkipRide;
|
||||||
bool dynamicReactDelay;
|
bool dynamicReactDelay;
|
||||||
float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, shootDistance, fleeDistance,
|
float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, shootDistance, fleeDistance,
|
||||||
tooCloseDistance, meleeDistance, followDistance, whisperDistance, contactDistance, aoeRadius, rpgDistance,
|
tooCloseDistance, meleeDistance, followDistance, whisperDistance, contactDistance, aoeRadius, rpgDistance,
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
#include "BattleGroundTactics.h"
|
#include "BattleGroundTactics.h"
|
||||||
#include "Chat.h"
|
#include "Chat.h"
|
||||||
#include "GuildTaskMgr.h"
|
#include "GuildTaskMgr.h"
|
||||||
|
#include "MapMgr.h"
|
||||||
#include "PerfMonitor.h"
|
#include "PerfMonitor.h"
|
||||||
#include "PlayerbotMgr.h"
|
#include "PlayerbotMgr.h"
|
||||||
#include "RandomPlayerbotMgr.h"
|
#include "RandomPlayerbotMgr.h"
|
||||||
@ -186,7 +187,24 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32 zoneId = player->GetZoneId();
|
uint32 zoneId = player->GetZoneId();
|
||||||
std::vector<TravelNode*> const& nodes = sTravelNodeMap.GetNodesInZone(zoneId);
|
uint32 const phaseMask = player->GetPhaseMask();
|
||||||
|
uint32 const mapId = player->GetMapId();
|
||||||
|
std::vector<TravelNode*> nodes;
|
||||||
|
for (TravelNode* n : sTravelNodeMap.getNodes())
|
||||||
|
{
|
||||||
|
if (!n)
|
||||||
|
continue;
|
||||||
|
WorldPosition* pos = n->getPosition();
|
||||||
|
if (!pos || pos->GetMapId() != mapId)
|
||||||
|
continue;
|
||||||
|
uint32 const nodeZone = sMapMgr->GetZoneId(phaseMask, mapId,
|
||||||
|
pos->GetPositionX(),
|
||||||
|
pos->GetPositionY(),
|
||||||
|
pos->GetPositionZ());
|
||||||
|
if (nodeZone != zoneId)
|
||||||
|
continue;
|
||||||
|
nodes.push_back(n);
|
||||||
|
}
|
||||||
if (nodes.empty())
|
if (nodes.empty())
|
||||||
{
|
{
|
||||||
handler->PSendSysMessage("No travel nodes registered in zone {} (is the travel node system loaded?)", zoneId);
|
handler->PSendSysMessage("No travel nodes registered in zone {} (is the travel node system loaded?)", zoneId);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user