mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
Compare commits
8 Commits
d51f9b8fb8
...
9d3f47b19f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d3f47b19f | ||
|
|
c64ac89924 | ||
|
|
753248510a | ||
|
|
3710c35a41 | ||
|
|
5c73cde20d | ||
|
|
cc3ac5fd7a | ||
|
|
823b08d86c | ||
|
|
f8150ffb86 |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -330,7 +330,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool generatePath = !bot->IsFlying() && !bot->isSwimming();
|
bool generatePath = !bot->IsFlying() && !bot->isSwimming() && !bot->IsInWater();
|
||||||
bool disableMoveSplinePath =
|
bool disableMoveSplinePath =
|
||||||
sPlayerbotAIConfig.disableMoveSplinePath >= 2 ||
|
sPlayerbotAIConfig.disableMoveSplinePath >= 2 ||
|
||||||
(sPlayerbotAIConfig.disableMoveSplinePath == 1 && bot->InBattleground());
|
(sPlayerbotAIConfig.disableMoveSplinePath == 1 && bot->InBattleground());
|
||||||
@ -3308,7 +3308,7 @@ bool MovementAction::GetTravelPlan(TravelPlan& plan, WorldPosition destination)
|
|||||||
destination.GetPositionX(), destination.GetPositionY(), destination.GetPositionZ(),
|
destination.GetPositionX(), destination.GetPositionY(), destination.GetPositionZ(),
|
||||||
destination.GetMapId(), botPos.fDist(destination));
|
destination.GetMapId(), botPos.fDist(destination));
|
||||||
|
|
||||||
return sTravelNodeMap.GetFullPath(plan, botPos, bot->GetZoneId(), destination);
|
return sTravelNodeMap.GetFullPath(plan, botPos, bot->GetZoneId(), destination, bot);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MovementAction::ExecuteTravelPlan(TravelPlan& state)
|
bool MovementAction::ExecuteTravelPlan(TravelPlan& state)
|
||||||
|
|||||||
@ -9,23 +9,6 @@
|
|||||||
|
|
||||||
LastMovement::LastMovement() { clear(); }
|
LastMovement::LastMovement() { clear(); }
|
||||||
|
|
||||||
LastMovement::LastMovement(LastMovement& other)
|
|
||||||
: taxiNodes(other.taxiNodes),
|
|
||||||
taxiMaster(other.taxiMaster),
|
|
||||||
lastFollow(other.lastFollow),
|
|
||||||
lastAreaTrigger(other.lastAreaTrigger),
|
|
||||||
lastMoveToX(other.lastMoveToX),
|
|
||||||
lastMoveToY(other.lastMoveToY),
|
|
||||||
lastMoveToZ(other.lastMoveToZ),
|
|
||||||
lastMoveToOri(other.lastMoveToOri),
|
|
||||||
lastFlee(other.lastFlee)
|
|
||||||
{
|
|
||||||
lastMoveShort = other.lastMoveShort;
|
|
||||||
nextTeleport = other.nextTeleport;
|
|
||||||
lastPath = other.lastPath;
|
|
||||||
priority = other.priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LastMovement::clear()
|
void LastMovement::clear()
|
||||||
{
|
{
|
||||||
lastMoveShort = WorldPosition();
|
lastMoveShort = WorldPosition();
|
||||||
|
|||||||
@ -27,20 +27,9 @@ class LastMovement
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LastMovement();
|
LastMovement();
|
||||||
LastMovement(LastMovement& other);
|
|
||||||
|
|
||||||
LastMovement& operator=(LastMovement const& other)
|
LastMovement(LastMovement const&) = default;
|
||||||
{
|
LastMovement& operator=(LastMovement const&) = default;
|
||||||
taxiNodes = other.taxiNodes;
|
|
||||||
taxiMaster = other.taxiMaster;
|
|
||||||
lastFollow = other.lastFollow;
|
|
||||||
lastAreaTrigger = other.lastAreaTrigger;
|
|
||||||
lastMoveShort = other.lastMoveShort;
|
|
||||||
lastPath = other.lastPath;
|
|
||||||
nextTeleport = other.nextTeleport;
|
|
||||||
priority = other.priority;
|
|
||||||
return *this;
|
|
||||||
};
|
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
@ -66,7 +55,6 @@ public:
|
|||||||
MovementPriority priority;
|
MovementPriority priority;
|
||||||
TravelPath lastPath;
|
TravelPath lastPath;
|
||||||
time_t nextTeleport;
|
time_t nextTeleport;
|
||||||
std::future<TravelPath> future;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class LastMovementValue : public ManualSetValue<LastMovement&>
|
class LastMovementValue : public ManualSetValue<LastMovement&>
|
||||||
@ -75,7 +63,7 @@ public:
|
|||||||
LastMovementValue(PlayerbotAI* botAI) : ManualSetValue<LastMovement&>(botAI, data) {}
|
LastMovementValue(PlayerbotAI* botAI) : ManualSetValue<LastMovement&>(botAI, data) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LastMovement data = LastMovement();
|
LastMovement data{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class StayTimeValue : public ManualSetValue<time_t>
|
class StayTimeValue : public ManualSetValue<time_t>
|
||||||
|
|||||||
@ -52,32 +52,23 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
|
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Let previously committed movement finish before recomputing.
|
// Already-at-dest short-stop. Below targetPosRecalcDistance
|
||||||
//
|
// (default 0.1y) the move is effectively done. Stop any spline
|
||||||
// MoveTo internally caps its stored delay at maxWaitForMove
|
// still running and clear the cached path if it points to here
|
||||||
// (default 5s), but a long path (200+ yd routed around a
|
// — otherwise the bot keeps gliding past dest. Mirrors cmangos
|
||||||
// mountain) takes 30+ seconds to walk. After 5s
|
// MovementActions.cpp:1095-1106.
|
||||||
// IsWaitingForLastMove returns false and MoveFarTo re-enters.
|
|
||||||
// Without this gate, DoMovePoint would call mm->Clear() and
|
|
||||||
// reissue MovePoint from the new bot position — and from a new
|
|
||||||
// position mmap's partial-path endpoint often differs, so the
|
|
||||||
// bot gets clobbered mid-walk and ends up oscillating around an
|
|
||||||
// unreachable destination.
|
|
||||||
//
|
|
||||||
// If the bot is still actively walking toward its last
|
|
||||||
// committed point on the same map, just let the current spline
|
|
||||||
// finish.
|
|
||||||
{
|
{
|
||||||
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
float const totalDistance = bot->GetExactDist(dest);
|
||||||
if (bot->isMoving() && lastMove.lastMoveToMapId == bot->GetMapId())
|
if (totalDistance < sPlayerbotAIConfig.targetPosRecalcDistance)
|
||||||
{
|
{
|
||||||
float remaining = bot->GetExactDist(lastMove.lastMoveToX, lastMove.lastMoveToY, lastMove.lastMoveToZ);
|
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
||||||
if (remaining > 10.0f)
|
if (!lastMove.lastPath.empty() &&
|
||||||
|
lastMove.lastPath.getBack().distance(dest) <= totalDistance)
|
||||||
{
|
{
|
||||||
EmitDebugMove("MoveFar", "spline-plan",
|
lastMove.clear();
|
||||||
lastMove.lastMoveToX, lastMove.lastMoveToY, lastMove.lastMoveToZ);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
bot->StopMoving();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,24 +110,7 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
points.reserve(pts.size());
|
points.reserve(pts.size());
|
||||||
for (auto const& wp : pts)
|
for (auto const& wp : pts)
|
||||||
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
||||||
for (auto& pt : points)
|
return DispatchPathPoints(dest, points, "reuse");
|
||||||
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
|
|
||||||
bot->GetMotionMaster()->Clear();
|
|
||||||
bot->GetMotionMaster()->MoveSplinePath(&points, FORCED_MOVEMENT_RUN);
|
|
||||||
|
|
||||||
G3D::Vector3 const& last = points.back();
|
|
||||||
float totalChainDist = 0.f;
|
|
||||||
for (size_t i = 1; i < points.size(); ++i)
|
|
||||||
totalChainDist += (points[i] - points[i - 1]).length();
|
|
||||||
float speed = std::max(bot->GetSpeed(MOVE_RUN), 0.1f);
|
|
||||||
uint32 expectedMs = static_cast<uint32>((totalChainDist / speed) * IN_MILLISECONDS);
|
|
||||||
uint32 cappedMs = std::min(expectedMs, (uint32)sPlayerbotAIConfig.maxWaitForMove);
|
|
||||||
lastMove.Set(bot->GetMapId(), last.x, last.y, last.z,
|
|
||||||
bot->GetOrientation(), cappedMs, MovementPriority::MOVEMENT_NORMAL);
|
|
||||||
|
|
||||||
EmitDebugMove("MoveFar", "reuse",
|
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Path was cleared or collapsed — fall through to fresh planning.
|
// Path was cleared or collapsed — fall through to fresh planning.
|
||||||
@ -145,71 +119,62 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float disToDest = bot->GetDistance(dest);
|
float const dis = bot->GetExactDist(dest);
|
||||||
float dis = bot->GetExactDist(dest);
|
|
||||||
|
|
||||||
// Decision tree (cmangos ResolveMovePath order — travel nodes first):
|
// Mirrors cmangos ResolveMovePath: pick travel-node graph for
|
||||||
|
// long-distance / cross-map moves; mmap probe for short paths.
|
||||||
//
|
//
|
||||||
// 1. Active node plan? Ride it.
|
// 1. needsLongPath = cross-map OR dis > sightDistance
|
||||||
//
|
// 2. If needsLongPath && travel nodes enabled → graph plan
|
||||||
// 2. Long-distance move (>= nodeFirstDis) and travel nodes
|
// 3. Else 40-step chained mmap probe
|
||||||
// enabled: try the node graph FIRST. The graph holds
|
// 4. Regression guard against the new probe — ride cached path
|
||||||
// curated waypoints that avoid known bad terrain.
|
// if it ends at least as close to dest
|
||||||
//
|
// 5. Dispatch probe (no reach gate — partial probes still make
|
||||||
// 3. If no node plan returned: run the 40-step chained mmap
|
// progress and the next tick replans)
|
||||||
// probe and dispatch its waypoint chain.
|
// 6. Empty probe → cmangos addPoint(endPosition) fallback:
|
||||||
//
|
// single-waypoint MoveTo so PathGenerator resolves it.
|
||||||
// 4. Empty / non-progressing probe: fall back to single-
|
|
||||||
// waypoint spline at dest.
|
|
||||||
bool tryNodes = (dis >= nodeFirstDis && sPlayerbotAIConfig.enableTravelNodes);
|
|
||||||
|
|
||||||
// If a node plan is already active, ride it — but only if its
|
bool const needsLongPath = (bot->GetMapId() != dest.GetMapId()) ||
|
||||||
// destination still matches the requested dest. Otherwise the
|
(dis > sPlayerbotAIConfig.sightDistance);
|
||||||
// old plan (e.g. built toward a quest objective POI) would keep
|
|
||||||
// driving the bot after the caller switched targets (e.g. to a
|
// needsLongPath branch — graph routing only. cmangos's getFullPath
|
||||||
// turn-in NPC). cmangos's ResolveMovePath dodges this by being
|
// already attempts a 40-step mmap probe internally before falling
|
||||||
// stateless; we have a long-lived plan flag, so check explicitly.
|
// back to A* node routing, so we don't need a second probe here.
|
||||||
if (tryNodes && botAI->rpgInfo.HasActiveTravelPlan())
|
// If the graph yields no plan, fall straight to the single-point
|
||||||
|
// fallback (cmangos addPoint(endPosition)). BG gate per
|
||||||
|
// MovementActions.cpp:705.
|
||||||
|
if (needsLongPath)
|
||||||
{
|
{
|
||||||
if (botAI->rpgInfo.travelPlan.destination.distance(dest) > 10.0f)
|
if (sPlayerbotAIConfig.enableTravelNodes && !bot->InBattleground())
|
||||||
botAI->rpgInfo.ClearTravel();
|
|
||||||
else
|
|
||||||
return UpdateTravelPlan();
|
|
||||||
}
|
|
||||||
|
|
||||||
// PRIORITY: try the travel-node graph FIRST when the move is
|
|
||||||
// long enough to need it.
|
|
||||||
if (tryNodes)
|
|
||||||
{
|
|
||||||
StartTravelPlan(dest);
|
|
||||||
if (botAI->rpgInfo.HasActiveTravelPlan())
|
|
||||||
{
|
{
|
||||||
LOG_INFO("playerbots", "[MoveFar] {} nodetravel | dis={:.0f}",
|
StartTravelPlan(dest);
|
||||||
bot->GetName(), dis);
|
if (botAI->rpgInfo.HasActiveTravelPlan())
|
||||||
EmitDebugMove("MoveFar", "travelplan",
|
{
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
LOG_INFO("playerbots", "[MoveFar] {} nodetravel | dis={:.0f}",
|
||||||
return UpdateTravelPlan();
|
bot->GetName(), dis);
|
||||||
|
EmitDebugMove("MoveFar", "travelplan",
|
||||||
|
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||||
|
return UpdateTravelPlan();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Graph returned no plan — fall through to mmap probe.
|
|
||||||
}
|
// Long-path fallback. Cross-map can't be served by a same-map
|
||||||
else if (botAI->rpgInfo.HasActiveTravelPlan())
|
// straight-line spline — bail rather than splining toward
|
||||||
{
|
// unreachable coords.
|
||||||
// Move dropped below node-first threshold — drop any leftover plan.
|
if (bot->GetMapId() != dest.GetMapId())
|
||||||
botAI->rpgInfo.ClearTravel();
|
return false;
|
||||||
|
|
||||||
|
Movement::PointsArray fallback;
|
||||||
|
fallback.emplace_back(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||||
|
fallback.emplace_back(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||||
|
return DispatchPathPoints(dest, fallback, "spline-long");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 40-step chained mmap probe — fallback when the node graph
|
// Short-path branch — 40-step chained mmap probe.
|
||||||
// returned no plan (or for short moves below nodeFirstDis).
|
|
||||||
WorldPosition botPos(bot);
|
WorldPosition botPos(bot);
|
||||||
std::vector<WorldPosition> probe = botPos.getPathTo(dest, bot);
|
std::vector<WorldPosition> probe = botPos.getPathTo(dest, bot);
|
||||||
|
|
||||||
// Regression guard (cmangos ResolveMovePath parity): if a cached
|
// Regression guard.
|
||||||
// lastPath ends at least as close to dest as the new probe's
|
|
||||||
// endpoint, prefer the cached path. The 10% reuse block above
|
|
||||||
// already returned early when cached was within 10% of dest;
|
|
||||||
// this catches "cached is far (>10%) but still better than the
|
|
||||||
// probe" — typically when the probe got blocked by geometry and
|
|
||||||
// ended much farther from dest than where cached had reached.
|
|
||||||
{
|
{
|
||||||
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
||||||
if (!lastMove.lastPath.empty() && !probe.empty() && probe.size() >= 2)
|
if (!lastMove.lastPath.empty() && !probe.empty() && probe.size() >= 2)
|
||||||
@ -234,24 +199,7 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
points.reserve(pts.size());
|
points.reserve(pts.size());
|
||||||
for (auto const& wp : pts)
|
for (auto const& wp : pts)
|
||||||
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
||||||
for (auto& pt : points)
|
return DispatchPathPoints(dest, points, "regress-keep");
|
||||||
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
|
|
||||||
bot->GetMotionMaster()->Clear();
|
|
||||||
bot->GetMotionMaster()->MoveSplinePath(&points, FORCED_MOVEMENT_RUN);
|
|
||||||
|
|
||||||
G3D::Vector3 const& last = points.back();
|
|
||||||
float totalChainDist = 0.f;
|
|
||||||
for (size_t i = 1; i < points.size(); ++i)
|
|
||||||
totalChainDist += (points[i] - points[i - 1]).length();
|
|
||||||
float speed = std::max(bot->GetSpeed(MOVE_RUN), 0.1f);
|
|
||||||
uint32 expectedMs = static_cast<uint32>((totalChainDist / speed) * IN_MILLISECONDS);
|
|
||||||
uint32 cappedMs = std::min(expectedMs, (uint32)sPlayerbotAIConfig.maxWaitForMove);
|
|
||||||
lastMove.Set(bot->GetMapId(), last.x, last.y, last.z,
|
|
||||||
bot->GetOrientation(), cappedMs, MovementPriority::MOVEMENT_NORMAL);
|
|
||||||
|
|
||||||
EmitDebugMove("MoveFar", "regress-keep",
|
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,109 +207,248 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the chained probe's full waypoint chain via MoveSplinePath.
|
|
||||||
// Handing the full waypoint vector to the motion master removes
|
|
||||||
// its discretion to introduce a straight-line shortcut between
|
|
||||||
// intermediate points.
|
|
||||||
if (!probe.empty() && probe.size() >= 2)
|
if (!probe.empty() && probe.size() >= 2)
|
||||||
{
|
{
|
||||||
WorldPosition stepDest = probe.back();
|
Movement::PointsArray points;
|
||||||
float endDistToDest = dest.GetExactDist(stepDest.GetPositionX(),
|
points.reserve(probe.size());
|
||||||
stepDest.GetPositionY(), stepDest.GetPositionZ());
|
for (auto const& wp : probe)
|
||||||
if (endDistToDest + 5.0f < disToDest)
|
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
||||||
|
|
||||||
|
if (points.size() >= 2)
|
||||||
{
|
{
|
||||||
// Convert WorldPosition probe to G3D::Vector3 array.
|
LOG_INFO("playerbots", "[MoveFar] {} mmap-path | dis={:.0f} | wp={}",
|
||||||
Movement::PointsArray points;
|
bot->GetName(), dis, (uint32)points.size());
|
||||||
points.reserve(probe.size());
|
|
||||||
for (auto const& wp : probe)
|
|
||||||
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
|
||||||
|
|
||||||
// Per-waypoint Z-snap to current ground.
|
if (!bot->IsMounted() && !bot->IsInCombat() && bot->IsOutdoors() && bot->IsAlive())
|
||||||
|
botAI->DoSpecificAction("check mount state", Event(), true);
|
||||||
|
|
||||||
|
return DispatchPathPoints(dest, points, "mmap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty-probe fallback — cmangos addPoint(endPosition) + DispatchMovement.
|
||||||
|
// Build a 2-point straight-line path (bot → dest) and route it through
|
||||||
|
// DispatchPathPoints so it gets the same clip / underwater fixup /
|
||||||
|
// setPath / WaitForReach / teleport / mode-flip treatment as the
|
||||||
|
// chained-mmap branches. Trade-off vs the old MoveTo path: lose the
|
||||||
|
// engine's MovePoint(generatePath=true) local PathGenerator resolution.
|
||||||
|
// For the empty-probe case the long mmap probe already failed, so the
|
||||||
|
// engine's local resolution would likely fail too.
|
||||||
|
Movement::PointsArray fallback;
|
||||||
|
fallback.emplace_back(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||||
|
fallback.emplace_back(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||||
|
return DispatchPathPoints(dest, fallback, "spline");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest,
|
||||||
|
Movement::PointsArray& points,
|
||||||
|
char const* label)
|
||||||
|
{
|
||||||
|
if (points.size() < 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Save the planner's path BEFORE clip/fixup mutations, so the
|
||||||
|
// next-tick reuse/regress branches see the original intent (not
|
||||||
|
// a clip-truncated tail). Mirrors cmangos MovementActions.cpp:1117 +
|
||||||
|
// 1147 — both saves happen before ClipPath at :1150 and the
|
||||||
|
// underwater fixup loop at :1170.
|
||||||
|
{
|
||||||
|
LastMovement& lm = AI_VALUE(LastMovement&, "last movement");
|
||||||
|
std::vector<WorldPosition> wpts;
|
||||||
|
wpts.reserve(points.size());
|
||||||
|
for (auto const& pt : points)
|
||||||
|
wpts.emplace_back(dest.GetMapId(), pt.x, pt.y, pt.z);
|
||||||
|
lm.setPath(TravelPath(wpts));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push submerged waypoints to the water surface unless dest is itself underwater.
|
||||||
|
if (Map* map = bot->GetMap())
|
||||||
|
{
|
||||||
|
WorldPosition destWp = dest;
|
||||||
|
if (!destWp.isUnderWater())
|
||||||
|
{
|
||||||
for (auto& pt : points)
|
for (auto& pt : points)
|
||||||
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
|
|
||||||
|
|
||||||
if (points.size() >= 2)
|
|
||||||
{
|
{
|
||||||
LOG_INFO("playerbots", "[MoveFar] {} mmap-path | dis={:.0f} | endDist={:.0f} | wp={}",
|
WorldPosition wp(dest.GetMapId(), pt.x, pt.y, pt.z);
|
||||||
bot->GetName(), dis, endDistToDest, (uint32)points.size());
|
if (wp.isUnderWater())
|
||||||
EmitDebugMove("MoveFar", "mmap",
|
{
|
||||||
points.back().x, points.back().y, points.back().z);
|
float surface = map->GetWaterLevel(pt.x, pt.y);
|
||||||
|
if (surface != INVALID_HEIGHT && surface > pt.z)
|
||||||
// Mount up if outdoors and not in combat.
|
pt.z = surface;
|
||||||
if (!bot->IsMounted() && !bot->IsInCombat() && bot->IsOutdoors() && bot->IsAlive())
|
}
|
||||||
botAI->DoSpecificAction("check mount state", Event(), true);
|
|
||||||
|
|
||||||
// Bulk dispatch: hand the full waypoint chain to the
|
|
||||||
// motion master via MoveSplinePath. Motion master plays
|
|
||||||
// every point in sequence — no per-tick re-dispatching.
|
|
||||||
bot->GetMotionMaster()->Clear();
|
|
||||||
bot->GetMotionMaster()->MoveSplinePath(&points, FORCED_MOVEMENT_RUN);
|
|
||||||
|
|
||||||
// Update LastMovement to the chain endpoint so spline-
|
|
||||||
// active early-exit at the top of MoveFarTo silences
|
|
||||||
// recompute attempts during the walk.
|
|
||||||
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();
|
|
||||||
float speed = std::max(bot->GetSpeed(MOVE_RUN), 0.1f);
|
|
||||||
uint32 expectedMs = static_cast<uint32>((totalDist / speed) * IN_MILLISECONDS);
|
|
||||||
uint32 cappedMs = std::min(expectedMs, (uint32)sPlayerbotAIConfig.maxWaitForMove);
|
|
||||||
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
|
||||||
lastMove.Set(bot->GetMapId(), last.x, last.y, last.z,
|
|
||||||
bot->GetOrientation(), cappedMs, MovementPriority::MOVEMENT_NORMAL);
|
|
||||||
|
|
||||||
// Cache full chain for downstream consumers
|
|
||||||
// (LastLongMoveValue) and the lastPath reuse check.
|
|
||||||
std::vector<WorldPosition> wpts;
|
|
||||||
wpts.reserve(points.size());
|
|
||||||
for (auto const& pt : points)
|
|
||||||
wpts.emplace_back(bot->GetMapId(), pt.x, pt.y, pt.z);
|
|
||||||
lastMove.setPath(TravelPath(wpts));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Probe failed or didn't progress — emit visibility whisper so
|
for (auto& pt : points)
|
||||||
// the user can see WHY mmap didn't dispatch.
|
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
|
||||||
|
|
||||||
|
// ClipPath — truncate the path at the first hostile creature within
|
||||||
|
// its own attack range, so the bot stops walking into pulls instead
|
||||||
|
// of marching past mobs and aggroing them all at once. Skipped while
|
||||||
|
// already in combat or dead. Mirrors cmangos TravelNode.cpp:1118-1203
|
||||||
|
// in simplified form (no transport, no hazard list).
|
||||||
|
if (botAI->GetState() != BOT_STATE_COMBAT && bot->IsAlive())
|
||||||
{
|
{
|
||||||
bool const probeProgressed = !probe.empty() && probe.size() >= 2 &&
|
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
|
||||||
(dest.GetExactDist(probe.back().GetPositionX(),
|
if (!targets.empty())
|
||||||
probe.back().GetPositionY(), probe.back().GetPositionZ()) + 5.0f < disToDest);
|
|
||||||
if (!probeProgressed)
|
|
||||||
{
|
{
|
||||||
char const* reason = (probe.empty() || probe.size() < 2) ? "mmap-empty" : "mmap-noprogress";
|
size_t clipAt = points.size();
|
||||||
EmitDebugMove("MoveFar", reason,
|
for (size_t i = 0; i < points.size() && clipAt == points.size(); ++i)
|
||||||
dest.GetPositionX(), dest.GetPositionY(),
|
{
|
||||||
dest.GetPositionZ());
|
for (ObjectGuid const& guid : targets)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(guid);
|
||||||
|
if (!unit || !unit->IsAlive())
|
||||||
|
continue;
|
||||||
|
Creature* cre = unit->ToCreature();
|
||||||
|
if (!cre)
|
||||||
|
continue;
|
||||||
|
if (unit->GetLevel() > bot->GetLevel() + 5)
|
||||||
|
continue;
|
||||||
|
float range = cre->GetAttackDistance(bot);
|
||||||
|
float dx = unit->GetPositionX() - points[i].x;
|
||||||
|
float dy = unit->GetPositionY() - points[i].y;
|
||||||
|
float dz = unit->GetPositionZ() - points[i].z;
|
||||||
|
if (dx * dx + dy * dy + dz * dz > range * range)
|
||||||
|
continue;
|
||||||
|
// LOS guard — mobs behind walls don't actually
|
||||||
|
// pull, so clipping there over-truncates. Mirrors
|
||||||
|
// cmangos TravelNode.cpp:1155.
|
||||||
|
if (!unit->IsWithinLOSInMap(bot))
|
||||||
|
continue;
|
||||||
|
clipAt = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Truncate after the first hostile waypoint. cmangos's
|
||||||
|
// analog at TravelNode.cpp:1158 sets endP=path.begin() if
|
||||||
|
// i=0 (mob right at start) and erases everything after,
|
||||||
|
// collapsing to a 1-point path. Our caller then sees
|
||||||
|
// points.size() < 2 and bails out — the bot just stops.
|
||||||
|
if (clipAt < points.size() && clipAt + 1 < points.size())
|
||||||
|
points.erase(points.begin() + clipAt + 1, points.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty / non-progressing path falls back to dispatching the
|
// After clip, points may have collapsed to one element (mob right
|
||||||
// destination as a single waypoint. Spline only when target is
|
// at start). Bail rather than dispatching a degenerate spline.
|
||||||
// line-of-sight: dispatching a straight line through walls
|
if (points.size() < 2)
|
||||||
// produces visible clipping/glitching. If LOS is blocked we
|
return false;
|
||||||
// refuse and let UnstuckAction (5/10 min) catch the stuck.
|
|
||||||
bool const inLOS = bot->IsWithinLOS(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
G3D::Vector3 const& last = points.back();
|
||||||
LOG_INFO("playerbots", "[MoveFar] {} spline | dis={:.0f} | probe.empty={} | LOS={}",
|
|
||||||
bot->GetName(), dis,
|
float totalDist = 0.f;
|
||||||
probe.empty() ? "y" : "n",
|
for (size_t i = 1; i < points.size(); ++i)
|
||||||
inLOS ? "y" : "n");
|
totalDist += (points[i] - points[i - 1]).length();
|
||||||
if (!inLOS)
|
|
||||||
|
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
||||||
|
|
||||||
|
// Item 2 — inactive-bot teleport. When the path is longer than
|
||||||
|
// reactDistance and there's no real player around to witness, jump
|
||||||
|
// to the path tail and schedule a cooldown. Skips cosmetic walking
|
||||||
|
// for unobserved random bots. Player-owned (self) bots are excluded
|
||||||
|
// so testing/observed sessions always see the real walk.
|
||||||
|
if (sRandomPlayerbotMgr.IsRandomBot(bot))
|
||||||
{
|
{
|
||||||
EmitDebugMove("MoveFar", "spline-blocked",
|
WorldPosition tail(dest.GetMapId(), last.x, last.y, last.z);
|
||||||
dest.GetPositionX(), dest.GetPositionY(),
|
time_t now = time(nullptr);
|
||||||
dest.GetPositionZ());
|
if (totalDist > sPlayerbotAIConfig.reactDistance &&
|
||||||
return false; // Refuse to dispatch a straight line through geometry.
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
EmitDebugMove("MoveFar", "spline",
|
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
// masterWalking — match the master's walk pace when they're nearby
|
||||||
// Same exact_waypoint=false rationale as the mmap branch — terrain-
|
// and walking. Lets a follower bot trail at walk speed instead of
|
||||||
// following spline, not a straight diagonal.
|
// sprinting past. No-op for masterless RPG bots. Mirrors cmangos
|
||||||
return MoveTo(dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
|
// MovementActions.cpp:1212-1221. (Flying-mount mode flip deferred —
|
||||||
false, false, false, false);
|
// requires takeoff/landing infrastructure we haven't ported.)
|
||||||
|
ForcedMovement moveMode = FORCED_MOVEMENT_RUN;
|
||||||
|
if (sPlayerbotAIConfig.walkDistance > 0.0f)
|
||||||
|
{
|
||||||
|
if (Player* master = botAI->GetMaster())
|
||||||
|
{
|
||||||
|
if (bot->IsFriendlyTo(master) && master->IsWalking() &&
|
||||||
|
bot->GetExactDist2d(master) < sPlayerbotAIConfig.walkDistance)
|
||||||
|
{
|
||||||
|
moveMode = FORCED_MOVEMENT_WALK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug-move beacon — when the `debug move` strategy is active in
|
||||||
|
// non-combat, summon a visible creature at every waypoint (white
|
||||||
|
// glow, red glow on the tail). Lets the operator visually verify
|
||||||
|
// ClipPath truncation, underwater fixup, masterWalking pace, etc.
|
||||||
|
// Mirrors cmangos MovementActions.cpp:1152-1161.
|
||||||
|
if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < points.size(); ++i)
|
||||||
|
{
|
||||||
|
G3D::Vector3 const& p = points[i];
|
||||||
|
if (Creature* wp = bot->SummonCreature(2334, p.x, p.y, p.z, 0,
|
||||||
|
TEMPSUMMON_TIMED_DESPAWN, 10000))
|
||||||
|
{
|
||||||
|
bot->AddAura(246, wp);
|
||||||
|
if (i + 1 == points.size())
|
||||||
|
bot->AddAura(1130, wp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-dispatch state cleanup. Mirrors cmangos MovementActions.cpp:1186-1194:
|
||||||
|
// - Clear any looping emote so the bot doesn't run-while-waving
|
||||||
|
// - Stand up unconditionally (eating/sitting clients ignore moves)
|
||||||
|
// - Interrupt any non-melee cast so the spline can begin
|
||||||
|
bot->ClearEmoteState();
|
||||||
|
if (!bot->IsStandState())
|
||||||
|
bot->SetStandState(UNIT_STAND_STATE_STAND);
|
||||||
|
if (bot->IsNonMeleeSpellCast(true))
|
||||||
|
bot->InterruptNonMeleeSpells(true);
|
||||||
|
|
||||||
|
// Item 7 — DispatchMovement two-step. cmangos calls
|
||||||
|
// mm.MovePoint(last) followed by mm.MovePath(points). In AC,
|
||||||
|
// MotionMaster::Mutate at MOTION_SLOT_ACTIVE replaces (not queues)
|
||||||
|
// the previous generator, so both calls in sequence collapse to
|
||||||
|
// whichever ran last. We skip the redundant MovePoint and dispatch
|
||||||
|
// the smooth waypoint path directly.
|
||||||
|
bot->GetMotionMaster()->Clear();
|
||||||
|
bot->GetMotionMaster()->MoveSplinePath(&points, moveMode);
|
||||||
|
|
||||||
|
EmitDebugMove("MoveFar", label, last.x, last.y, last.z);
|
||||||
|
|
||||||
|
// Item 6 — WaitForReach scheduling.
|
||||||
|
// waitDist = (totalDist > reactDistance) ? totalDist - 10 : totalDist
|
||||||
|
// duration = 1000 * (waitDist / runSpeed) + reactDelay
|
||||||
|
// capped at maxWaitForMove. The −10y leaves a small buffer so the
|
||||||
|
// AI tick can intervene before the path strictly finishes.
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NewRpgBaseAction::StartTravelPlan(WorldPosition dest)
|
void NewRpgBaseAction::StartTravelPlan(WorldPosition dest)
|
||||||
|
|||||||
@ -76,19 +76,20 @@ protected:
|
|||||||
bool RandomChangeStatus(std::vector<NewRpgStatus> candidateStatus);
|
bool RandomChangeStatus(std::vector<NewRpgStatus> candidateStatus);
|
||||||
bool CheckRpgStatusAvailable(NewRpgStatus status);
|
bool CheckRpgStatusAvailable(NewRpgStatus status);
|
||||||
|
|
||||||
protected:
|
|
||||||
/* FOR MOVE FAR */
|
|
||||||
// Distance at which MoveFarTo considers the travel-node graph as
|
|
||||||
// a routing option. Below this, the move is short enough that
|
|
||||||
// mmap handles it directly. Above this, mmap is *still probed
|
|
||||||
// first* via the 40-step chained pathfinder; the node graph
|
|
||||||
// only takes over if mmap can't get within spellDistance of
|
|
||||||
// the destination.
|
|
||||||
const float nodeFirstDis = 75.0f;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void StartTravelPlan(WorldPosition dest);
|
void StartTravelPlan(WorldPosition dest);
|
||||||
bool UpdateTravelPlan();
|
bool UpdateTravelPlan();
|
||||||
|
|
||||||
|
// Dispatches a chained mmap path. Applies cmangos-parity tweaks:
|
||||||
|
// underwater fixup (push submerged waypoints to the surface unless
|
||||||
|
// dest is itself underwater), inactive-bot teleport (jump to the
|
||||||
|
// tail when no players are nearby and the path is longer than
|
||||||
|
// reactDistance), and the WaitForReach formula
|
||||||
|
// (1000 * dist/speed + reactDelay, capped at maxWaitForMove).
|
||||||
|
// Returns true if dispatched or teleported.
|
||||||
|
bool DispatchPathPoints(WorldPosition const& dest,
|
||||||
|
Movement::PointsArray& points,
|
||||||
|
char const* label);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -50,7 +50,7 @@ void NewRpgInfo::ChangeToTravelFlight(uint32 flightMasterEntry, WorldPosition fl
|
|||||||
|
|
||||||
void NewRpgInfo::ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId)
|
void NewRpgInfo::ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId)
|
||||||
{
|
{
|
||||||
startT = getMSTime();
|
Reset();
|
||||||
OutdoorPvP pvp;
|
OutdoorPvP pvp;
|
||||||
pvp.capturePointSpawnId = capturePointSpawnId;
|
pvp.capturePointSpawnId = capturePointSpawnId;
|
||||||
data = pvp;
|
data = pvp;
|
||||||
|
|||||||
@ -721,6 +721,22 @@ std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos
|
|||||||
map->EnsureGridCreated(Acore::ComputeGridCoord(GetPositionX(), GetPositionY()));
|
map->EnsureGridCreated(Acore::ComputeGridCoord(GetPositionX(), GetPositionY()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PathGenerator path(pathUnit);
|
||||||
|
path.AddExcludeFlag(NAV_GROUND_STEEP);
|
||||||
|
auto result = getPathStepFrom(startPos, path);
|
||||||
|
|
||||||
|
if (tempCreature)
|
||||||
|
delete tempCreature;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pathfinder-reuse overload — caller owns the PathGenerator and any
|
||||||
|
// per-call configuration (filters, area costs). Mirrors cmangos
|
||||||
|
// WorldPosition.cpp:958 which threads one PathFinder through the whole
|
||||||
|
// 40-step chain instead of constructing a new one per step.
|
||||||
|
std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos, PathGenerator& path)
|
||||||
|
{
|
||||||
// Explicit-start overload (PathGenerator.h:67). Without this,
|
// Explicit-start overload (PathGenerator.h:67). Without this,
|
||||||
// CalculatePath(destX,destY,destZ) defaults to the unit's
|
// CalculatePath(destX,destY,destZ) defaults to the unit's
|
||||||
// current position as start — which means every iteration of
|
// current position as start — which means every iteration of
|
||||||
@ -729,16 +745,12 @@ std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos
|
|||||||
// never advances. With explicit start, each step extends from
|
// never advances. With explicit start, each step extends from
|
||||||
// the previous step's endpoint, giving the 40-attempt walker
|
// the previous step's endpoint, giving the 40-attempt walker
|
||||||
// its intended multi-tile reach.
|
// its intended multi-tile reach.
|
||||||
PathGenerator path(pathUnit);
|
|
||||||
path.CalculatePath(startPos.GetPositionX(), startPos.GetPositionY(), startPos.GetPositionZ(),
|
path.CalculatePath(startPos.GetPositionX(), startPos.GetPositionY(), startPos.GetPositionZ(),
|
||||||
GetPositionX(), GetPositionY(), GetPositionZ(), false);
|
GetPositionX(), GetPositionY(), GetPositionZ(), false);
|
||||||
|
|
||||||
Movement::PointsArray points = path.GetPath();
|
Movement::PointsArray points = path.GetPath();
|
||||||
PathType type = path.GetPathType();
|
PathType type = path.GetPathType();
|
||||||
|
|
||||||
if (tempCreature)
|
|
||||||
delete tempCreature;
|
|
||||||
|
|
||||||
// PathType is a bitmask. Two things to handle:
|
// PathType is a bitmask. Two things to handle:
|
||||||
//
|
//
|
||||||
// 1. AC's PathGenerator can return INCOMPLETE | FARFROMPOLY_END
|
// 1. AC's PathGenerator can return INCOMPLETE | FARFROMPOLY_END
|
||||||
@ -754,11 +766,50 @@ std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos
|
|||||||
// To match cmangos's intent (never silently dispatch a
|
// To match cmangos's intent (never silently dispatch a
|
||||||
// geometry-ignoring shortcut), reject any path with the
|
// geometry-ignoring shortcut), reject any path with the
|
||||||
// NOT_USING_PATH bit set.
|
// NOT_USING_PATH bit set.
|
||||||
if ((type & (PATHFIND_NORMAL | PATHFIND_INCOMPLETE))
|
if (!(type & (PATHFIND_NORMAL | PATHFIND_INCOMPLETE)) ||
|
||||||
&& !(type & PATHFIND_NOT_USING_PATH))
|
(type & PATHFIND_NOT_USING_PATH))
|
||||||
return fromPointsArray(points);
|
return {};
|
||||||
|
|
||||||
return {};
|
std::vector<WorldPosition> retvec = fromPointsArray(points);
|
||||||
|
|
||||||
|
// Underwater path-extension. Mirrors cmangos WorldPosition.cpp:997-1014.
|
||||||
|
// When PATHFIND_INCOMPLETE ends within 50y of dest and both endpoints
|
||||||
|
// are underwater with LOS between them, extend by one 5y step (or
|
||||||
|
// straight to dest if <5y). Lets bots traverse navmesh-poor water
|
||||||
|
// volumes the same way real swimmers do.
|
||||||
|
if (type & PATHFIND_INCOMPLETE)
|
||||||
|
{
|
||||||
|
WorldPosition end = *this;
|
||||||
|
WorldPosition lastPoint = retvec.back();
|
||||||
|
float dist = lastPoint.distance(&end);
|
||||||
|
|
||||||
|
if (dist < 50.0f && lastPoint.isUnderWater() && end.isUnderWater())
|
||||||
|
{
|
||||||
|
Map* m = end.getMap();
|
||||||
|
bool inLos = m && m->isInLineOfSight(
|
||||||
|
lastPoint.GetPositionX(), lastPoint.GetPositionY(), lastPoint.GetPositionZ() + 1.0f,
|
||||||
|
end.GetPositionX(), end.GetPositionY(), end.GetPositionZ() + 1.0f,
|
||||||
|
PHASEMASK_NORMAL, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing);
|
||||||
|
if (inLos)
|
||||||
|
{
|
||||||
|
if (dist < 5.0f)
|
||||||
|
retvec.push_back(end);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float dx = end.GetPositionX() - lastPoint.GetPositionX();
|
||||||
|
float dy = end.GetPositionY() - lastPoint.GetPositionY();
|
||||||
|
float dz = end.GetPositionZ() - lastPoint.GetPositionZ();
|
||||||
|
float scale = 5.0f / dist;
|
||||||
|
retvec.emplace_back(end.GetMapId(),
|
||||||
|
lastPoint.GetPositionX() + dx * scale,
|
||||||
|
lastPoint.GetPositionY() + dy * scale,
|
||||||
|
lastPoint.GetPositionZ() + dz * scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retvec;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WorldPosition::cropPathTo(std::vector<WorldPosition>& path, float maxDistance)
|
bool WorldPosition::cropPathTo(std::vector<WorldPosition>& path, float maxDistance)
|
||||||
@ -794,11 +845,50 @@ std::vector<WorldPosition> WorldPosition::getPathFromPath(std::vector<WorldPosit
|
|||||||
|
|
||||||
std::vector<WorldPosition> subPath, fullPath = startPath;
|
std::vector<WorldPosition> subPath, fullPath = startPath;
|
||||||
|
|
||||||
|
// Construct ONE PathGenerator and thread it through every step.
|
||||||
|
// Mirrors cmangos WorldPosition.cpp:1091-1096. Avoids the per-step
|
||||||
|
// allocation and lets Detour reuse internal scratch.
|
||||||
|
Unit* pathUnit = bot;
|
||||||
|
Creature* tempCreature = nullptr;
|
||||||
|
if (!pathUnit)
|
||||||
|
{
|
||||||
|
Map* map = sMapMgr->FindBaseMap(GetMapId());
|
||||||
|
if (!map)
|
||||||
|
return fullPath;
|
||||||
|
|
||||||
|
tempCreature = new Creature();
|
||||||
|
if (!tempCreature->Create(map->GenerateLowGuid<HighGuid::Unit>(), map,
|
||||||
|
PHASEMASK_NORMAL, 1 /*entry*/, 0,
|
||||||
|
currentPos.GetPositionX(), currentPos.GetPositionY(),
|
||||||
|
currentPos.GetPositionZ(), 0))
|
||||||
|
{
|
||||||
|
delete tempCreature;
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
pathUnit = tempCreature;
|
||||||
|
map->EnsureGridCreated(Acore::ComputeGridCoord(currentPos.GetPositionX(), currentPos.GetPositionY()));
|
||||||
|
map->EnsureGridCreated(Acore::ComputeGridCoord(GetPositionX(), GetPositionY()));
|
||||||
|
}
|
||||||
|
|
||||||
|
PathGenerator path(pathUnit);
|
||||||
|
path.AddExcludeFlag(NAV_GROUND_STEEP);
|
||||||
|
|
||||||
// Limit the pathfinding attempts
|
// Limit the pathfinding attempts
|
||||||
for (uint32 i = 0; i < maxAttempt; i++)
|
for (uint32 i = 0; i < maxAttempt; i++)
|
||||||
{
|
{
|
||||||
|
// Drop cached poly state from the previous step. AC's
|
||||||
|
// BuildPolyPath has a subpath-prefix optimization
|
||||||
|
// (PathGenerator.cpp:285-389) that keeps ~80% of the prior
|
||||||
|
// call's poly chain when the new startPoly is anywhere in
|
||||||
|
// it, then computes a suffix from there. For a chained
|
||||||
|
// walker this implicitly snaps the start to the previous
|
||||||
|
// corridor instead of using the explicit startPos we pass
|
||||||
|
// in, bending the route. Reset wipes the cache so each step
|
||||||
|
// is a fresh A*.
|
||||||
|
path.Clear();
|
||||||
|
|
||||||
// Try to pathfind to this position.
|
// Try to pathfind to this position.
|
||||||
subPath = getPathStepFrom(currentPos, bot);
|
subPath = getPathStepFrom(currentPos, path);
|
||||||
|
|
||||||
// If we could not find a path return what we have now.
|
// If we could not find a path return what we have now.
|
||||||
if (subPath.empty() || currentPos.distance(&subPath.back()) < sPlayerbotAIConfig.targetPosRecalcDistance)
|
if (subPath.empty() || currentPos.distance(&subPath.back()) < sPlayerbotAIConfig.targetPosRecalcDistance)
|
||||||
@ -815,6 +905,9 @@ std::vector<WorldPosition> WorldPosition::getPathFromPath(std::vector<WorldPosit
|
|||||||
currentPos = subPath.back();
|
currentPos = subPath.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tempCreature)
|
||||||
|
delete tempCreature;
|
||||||
|
|
||||||
return fullPath;
|
return fullPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
class Creature;
|
class Creature;
|
||||||
class GuidPosition;
|
class GuidPosition;
|
||||||
class ObjectGuid;
|
class ObjectGuid;
|
||||||
|
class PathGenerator;
|
||||||
class Quest;
|
class Quest;
|
||||||
class Player;
|
class Player;
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
@ -283,6 +284,7 @@ public:
|
|||||||
|
|
||||||
// Pathfinding
|
// Pathfinding
|
||||||
std::vector<WorldPosition> getPathStepFrom(WorldPosition startPos, Unit* bot);
|
std::vector<WorldPosition> getPathStepFrom(WorldPosition startPos, Unit* bot);
|
||||||
|
std::vector<WorldPosition> getPathStepFrom(WorldPosition startPos, PathGenerator& pathfinder);
|
||||||
std::vector<WorldPosition> getPathFromPath(std::vector<WorldPosition> startPath, Unit* bot, uint8 maxAttempt = 40);
|
std::vector<WorldPosition> getPathFromPath(std::vector<WorldPosition> startPath, Unit* bot, uint8 maxAttempt = 40);
|
||||||
|
|
||||||
std::vector<WorldPosition> getPathFrom(WorldPosition startPos, Unit* bot)
|
std::vector<WorldPosition> getPathFrom(WorldPosition startPos, Unit* bot)
|
||||||
|
|||||||
@ -1285,18 +1285,31 @@ TravelNodeRoute TravelNodeMap::FindRouteNearestNodes(WorldPosition startPos, Wor
|
|||||||
|
|
||||||
bool TravelNodeMap::GetFullPath(TravelPlan& plan,
|
bool TravelNodeMap::GetFullPath(TravelPlan& plan,
|
||||||
WorldPosition botPos, uint32 botZoneId,
|
WorldPosition botPos, uint32 botZoneId,
|
||||||
WorldPosition destination)
|
WorldPosition destination, Unit* bot)
|
||||||
{
|
{
|
||||||
plan.Reset();
|
plan.Reset();
|
||||||
plan.destination = destination;
|
plan.destination = destination;
|
||||||
|
|
||||||
// Short distance — direct walk, no nodes needed
|
// mmap probe first — mirrors cmangos getFullPath (TravelNode.cpp:1887-1895).
|
||||||
if (botPos.fDist(destination) < MAX_PATHFINDING_DISTANCE &&
|
// 40-step chained probe from bot; if it gets within spellDistance of dest
|
||||||
botPos.GetMapId() == destination.GetMapId())
|
// we skip the graph entirely (a short walk is always better than a node
|
||||||
|
// hop). When the probe falls short, fall through to graph routing.
|
||||||
|
if (botPos.GetMapId() == destination.GetMapId())
|
||||||
{
|
{
|
||||||
plan.steps.addPoint(botPos, PathNodeType::NODE_PREPATH);
|
std::vector<WorldPosition> probe = destination.getPathFromPath({botPos}, bot, 40);
|
||||||
plan.steps.addPoint(destination, PathNodeType::NODE_PATH);
|
// Guard the degenerate case: getPathFromPath always returns at
|
||||||
return true;
|
// least {botPos}, and isPathTo(probe, spellDistance) succeeds
|
||||||
|
// when the bot is already within spellDistance of dest.
|
||||||
|
// Without this guard we'd emit a 1-element plan [botPos as
|
||||||
|
// PREPATH] and ExecuteTravelPlan would silently no-op every
|
||||||
|
// tick.
|
||||||
|
if (probe.size() >= 2 && destination.isPathTo(probe, sPlayerbotAIConfig.spellDistance))
|
||||||
|
{
|
||||||
|
plan.steps.addPoint(botPos, PathNodeType::NODE_PREPATH);
|
||||||
|
for (size_t i = 1; i < probe.size(); ++i)
|
||||||
|
plan.steps.addPoint(probe[i], PathNodeType::NODE_PATH);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_lock<std::shared_timed_mutex> guard(m_nMapMtx);
|
std::shared_lock<std::shared_timed_mutex> guard(m_nMapMtx);
|
||||||
|
|||||||
@ -725,7 +725,7 @@ public:
|
|||||||
std::vector<TravelNode*> const& GetNodesInZone(uint32 zoneId) const;
|
std::vector<TravelNode*> const& GetNodesInZone(uint32 zoneId) const;
|
||||||
|
|
||||||
bool GetFullPath(TravelPlan& plan, WorldPosition botPos,
|
bool GetFullPath(TravelPlan& plan, WorldPosition botPos,
|
||||||
uint32 botZoneId, WorldPosition destination);
|
uint32 botZoneId, 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)
|
||||||
std::vector<TravelNode*> ResolveRoute(WorldPosition startPos,
|
std::vector<TravelNode*> ResolveRoute(WorldPosition startPos,
|
||||||
|
|||||||
@ -98,6 +98,7 @@ bool PlayerbotAIConfig::Initialize()
|
|||||||
tooCloseDistance = sConfigMgr->GetOption<float>("AiPlayerbot.TooCloseDistance", 5.0f);
|
tooCloseDistance = sConfigMgr->GetOption<float>("AiPlayerbot.TooCloseDistance", 5.0f);
|
||||||
meleeDistance = sConfigMgr->GetOption<float>("AiPlayerbot.MeleeDistance", 0.75f);
|
meleeDistance = sConfigMgr->GetOption<float>("AiPlayerbot.MeleeDistance", 0.75f);
|
||||||
followDistance = sConfigMgr->GetOption<float>("AiPlayerbot.FollowDistance", 1.5f);
|
followDistance = sConfigMgr->GetOption<float>("AiPlayerbot.FollowDistance", 1.5f);
|
||||||
|
walkDistance = sConfigMgr->GetOption<float>("AiPlayerbot.WalkDistance", 5.0f);
|
||||||
whisperDistance = sConfigMgr->GetOption<float>("AiPlayerbot.WhisperDistance", 6000.0f);
|
whisperDistance = sConfigMgr->GetOption<float>("AiPlayerbot.WhisperDistance", 6000.0f);
|
||||||
contactDistance = sConfigMgr->GetOption<float>("AiPlayerbot.ContactDistance", 0.45f);
|
contactDistance = sConfigMgr->GetOption<float>("AiPlayerbot.ContactDistance", 0.45f);
|
||||||
aoeRadius = sConfigMgr->GetOption<float>("AiPlayerbot.AoeRadius", 10.0f);
|
aoeRadius = sConfigMgr->GetOption<float>("AiPlayerbot.AoeRadius", 10.0f);
|
||||||
|
|||||||
@ -89,7 +89,7 @@ public:
|
|||||||
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,
|
||||||
targetPosRecalcDistance, farDistance, healDistance, aggroDistance;
|
targetPosRecalcDistance, farDistance, healDistance, aggroDistance, walkDistance;
|
||||||
uint32 criticalHealth, lowHealth, mediumHealth, almostFullHealth;
|
uint32 criticalHealth, lowHealth, mediumHealth, almostFullHealth;
|
||||||
uint32 lowMana, mediumMana, highMana;
|
uint32 lowMana, mediumMana, highMana;
|
||||||
bool autoSaveMana;
|
bool autoSaveMana;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user