mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-21 07:59:25 +02:00
Compare commits
No commits in common. "9d3f47b19f1531e969ddee0b3f1f9893b67ff793" and "d51f9b8fb8ac338b0fd91dc34a2422169e9176c4" have entirely different histories.
9d3f47b19f
...
d51f9b8fb8
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() && !bot->IsInWater();
|
bool generatePath = !bot->IsFlying() && !bot->isSwimming();
|
||||||
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, bot);
|
return sTravelNodeMap.GetFullPath(plan, botPos, bot->GetZoneId(), destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MovementAction::ExecuteTravelPlan(TravelPlan& state)
|
bool MovementAction::ExecuteTravelPlan(TravelPlan& state)
|
||||||
|
|||||||
@ -9,6 +9,23 @@
|
|||||||
|
|
||||||
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,9 +27,20 @@ class LastMovement
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LastMovement();
|
LastMovement();
|
||||||
|
LastMovement(LastMovement& other);
|
||||||
|
|
||||||
LastMovement(LastMovement const&) = default;
|
LastMovement& operator=(LastMovement const& other)
|
||||||
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();
|
||||||
|
|
||||||
@ -55,6 +66,7 @@ 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&>
|
||||||
@ -63,7 +75,7 @@ public:
|
|||||||
LastMovementValue(PlayerbotAI* botAI) : ManualSetValue<LastMovement&>(botAI, data) {}
|
LastMovementValue(PlayerbotAI* botAI) : ManualSetValue<LastMovement&>(botAI, data) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LastMovement data{};
|
LastMovement data = LastMovement();
|
||||||
};
|
};
|
||||||
|
|
||||||
class StayTimeValue : public ManualSetValue<time_t>
|
class StayTimeValue : public ManualSetValue<time_t>
|
||||||
|
|||||||
@ -52,23 +52,32 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
|
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Already-at-dest short-stop. Below targetPosRecalcDistance
|
// Let previously committed movement finish before recomputing.
|
||||||
// (default 0.1y) the move is effectively done. Stop any spline
|
//
|
||||||
// still running and clear the cached path if it points to here
|
// MoveTo internally caps its stored delay at maxWaitForMove
|
||||||
// — otherwise the bot keeps gliding past dest. Mirrors cmangos
|
// (default 5s), but a long path (200+ yd routed around a
|
||||||
// MovementActions.cpp:1095-1106.
|
// mountain) takes 30+ seconds to walk. After 5s
|
||||||
|
// 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.
|
||||||
{
|
{
|
||||||
float const totalDistance = bot->GetExactDist(dest);
|
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
||||||
if (totalDistance < sPlayerbotAIConfig.targetPosRecalcDistance)
|
if (bot->isMoving() && lastMove.lastMoveToMapId == bot->GetMapId())
|
||||||
{
|
{
|
||||||
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
float remaining = bot->GetExactDist(lastMove.lastMoveToX, lastMove.lastMoveToY, lastMove.lastMoveToZ);
|
||||||
if (!lastMove.lastPath.empty() &&
|
if (remaining > 10.0f)
|
||||||
lastMove.lastPath.getBack().distance(dest) <= totalDistance)
|
|
||||||
{
|
{
|
||||||
lastMove.clear();
|
EmitDebugMove("MoveFar", "spline-plan",
|
||||||
|
lastMove.lastMoveToX, lastMove.lastMoveToY, lastMove.lastMoveToZ);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
bot->StopMoving();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +119,24 @@ 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());
|
||||||
return DispatchPathPoints(dest, points, "reuse");
|
for (auto& pt : points)
|
||||||
|
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.
|
||||||
@ -119,62 +145,71 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float const dis = bot->GetExactDist(dest);
|
float disToDest = bot->GetDistance(dest);
|
||||||
|
float dis = bot->GetExactDist(dest);
|
||||||
|
|
||||||
// Mirrors cmangos ResolveMovePath: pick travel-node graph for
|
// Decision tree (cmangos ResolveMovePath order — travel nodes first):
|
||||||
// long-distance / cross-map moves; mmap probe for short paths.
|
|
||||||
//
|
//
|
||||||
// 1. needsLongPath = cross-map OR dis > sightDistance
|
// 1. Active node plan? Ride it.
|
||||||
// 2. If needsLongPath && travel nodes enabled → graph plan
|
//
|
||||||
// 3. Else 40-step chained mmap probe
|
// 2. Long-distance move (>= nodeFirstDis) and travel nodes
|
||||||
// 4. Regression guard against the new probe — ride cached path
|
// enabled: try the node graph FIRST. The graph holds
|
||||||
// if it ends at least as close to dest
|
// curated waypoints that avoid known bad terrain.
|
||||||
// 5. Dispatch probe (no reach gate — partial probes still make
|
//
|
||||||
// progress and the next tick replans)
|
// 3. If no node plan returned: run the 40-step chained mmap
|
||||||
// 6. Empty probe → cmangos addPoint(endPosition) fallback:
|
// probe and dispatch its waypoint chain.
|
||||||
// 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);
|
||||||
|
|
||||||
bool const needsLongPath = (bot->GetMapId() != dest.GetMapId()) ||
|
// If a node plan is already active, ride it — but only if its
|
||||||
(dis > sPlayerbotAIConfig.sightDistance);
|
// destination still matches the requested dest. Otherwise the
|
||||||
|
// old plan (e.g. built toward a quest objective POI) would keep
|
||||||
// needsLongPath branch — graph routing only. cmangos's getFullPath
|
// driving the bot after the caller switched targets (e.g. to a
|
||||||
// already attempts a 40-step mmap probe internally before falling
|
// turn-in NPC). cmangos's ResolveMovePath dodges this by being
|
||||||
// back to A* node routing, so we don't need a second probe here.
|
// stateless; we have a long-lived plan flag, so check explicitly.
|
||||||
// If the graph yields no plan, fall straight to the single-point
|
if (tryNodes && botAI->rpgInfo.HasActiveTravelPlan())
|
||||||
// fallback (cmangos addPoint(endPosition)). BG gate per
|
|
||||||
// MovementActions.cpp:705.
|
|
||||||
if (needsLongPath)
|
|
||||||
{
|
{
|
||||||
if (sPlayerbotAIConfig.enableTravelNodes && !bot->InBattleground())
|
if (botAI->rpgInfo.travelPlan.destination.distance(dest) > 10.0f)
|
||||||
{
|
botAI->rpgInfo.ClearTravel();
|
||||||
StartTravelPlan(dest);
|
else
|
||||||
if (botAI->rpgInfo.HasActiveTravelPlan())
|
return UpdateTravelPlan();
|
||||||
{
|
|
||||||
LOG_INFO("playerbots", "[MoveFar] {} nodetravel | dis={:.0f}",
|
|
||||||
bot->GetName(), dis);
|
|
||||||
EmitDebugMove("MoveFar", "travelplan",
|
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
|
||||||
return UpdateTravelPlan();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Long-path fallback. Cross-map can't be served by a same-map
|
|
||||||
// straight-line spline — bail rather than splining toward
|
|
||||||
// unreachable coords.
|
|
||||||
if (bot->GetMapId() != dest.GetMapId())
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short-path branch — 40-step chained mmap probe.
|
// 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}",
|
||||||
|
bot->GetName(), dis);
|
||||||
|
EmitDebugMove("MoveFar", "travelplan",
|
||||||
|
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||||
|
return UpdateTravelPlan();
|
||||||
|
}
|
||||||
|
// Graph returned no plan — fall through to mmap probe.
|
||||||
|
}
|
||||||
|
else if (botAI->rpgInfo.HasActiveTravelPlan())
|
||||||
|
{
|
||||||
|
// Move dropped below node-first threshold — drop any leftover plan.
|
||||||
|
botAI->rpgInfo.ClearTravel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 40-step chained mmap probe — fallback when the node graph
|
||||||
|
// 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.
|
// Regression guard (cmangos ResolveMovePath parity): if a cached
|
||||||
|
// 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)
|
||||||
@ -199,7 +234,24 @@ 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());
|
||||||
return DispatchPathPoints(dest, points, "regress-keep");
|
for (auto& pt : points)
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,248 +259,109 @@ 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)
|
||||||
{
|
{
|
||||||
Movement::PointsArray points;
|
WorldPosition stepDest = probe.back();
|
||||||
points.reserve(probe.size());
|
float endDistToDest = dest.GetExactDist(stepDest.GetPositionX(),
|
||||||
for (auto const& wp : probe)
|
stepDest.GetPositionY(), stepDest.GetPositionZ());
|
||||||
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
if (endDistToDest + 5.0f < disToDest)
|
||||||
|
|
||||||
if (points.size() >= 2)
|
|
||||||
{
|
{
|
||||||
LOG_INFO("playerbots", "[MoveFar] {} mmap-path | dis={:.0f} | wp={}",
|
// Convert WorldPosition probe to G3D::Vector3 array.
|
||||||
bot->GetName(), dis, (uint32)points.size());
|
Movement::PointsArray points;
|
||||||
|
points.reserve(probe.size());
|
||||||
|
for (auto const& wp : probe)
|
||||||
|
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
||||||
|
|
||||||
if (!bot->IsMounted() && !bot->IsInCombat() && bot->IsOutdoors() && bot->IsAlive())
|
// Per-waypoint Z-snap to current ground.
|
||||||
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)
|
||||||
{
|
{
|
||||||
WorldPosition wp(dest.GetMapId(), pt.x, pt.y, pt.z);
|
LOG_INFO("playerbots", "[MoveFar] {} mmap-path | dis={:.0f} | endDist={:.0f} | wp={}",
|
||||||
if (wp.isUnderWater())
|
bot->GetName(), dis, endDistToDest, (uint32)points.size());
|
||||||
{
|
EmitDebugMove("MoveFar", "mmap",
|
||||||
float surface = map->GetWaterLevel(pt.x, pt.y);
|
points.back().x, points.back().y, points.back().z);
|
||||||
if (surface != INVALID_HEIGHT && surface > pt.z)
|
|
||||||
pt.z = surface;
|
// Mount up if outdoors and not in combat.
|
||||||
}
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& pt : points)
|
// Probe failed or didn't progress — emit visibility whisper so
|
||||||
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
|
// the user can see WHY mmap didn't dispatch.
|
||||||
|
|
||||||
// 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())
|
|
||||||
{
|
{
|
||||||
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
|
bool const probeProgressed = !probe.empty() && probe.size() >= 2 &&
|
||||||
if (!targets.empty())
|
(dest.GetExactDist(probe.back().GetPositionX(),
|
||||||
|
probe.back().GetPositionY(), probe.back().GetPositionZ()) + 5.0f < disToDest);
|
||||||
|
if (!probeProgressed)
|
||||||
{
|
{
|
||||||
size_t clipAt = points.size();
|
char const* reason = (probe.empty() || probe.size() < 2) ? "mmap-empty" : "mmap-noprogress";
|
||||||
for (size_t i = 0; i < points.size() && clipAt == points.size(); ++i)
|
EmitDebugMove("MoveFar", reason,
|
||||||
{
|
dest.GetPositionX(), dest.GetPositionY(),
|
||||||
for (ObjectGuid const& guid : targets)
|
dest.GetPositionZ());
|
||||||
{
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After clip, points may have collapsed to one element (mob right
|
// Empty / non-progressing path falls back to dispatching the
|
||||||
// at start). Bail rather than dispatching a degenerate spline.
|
// destination as a single waypoint. Spline only when target is
|
||||||
if (points.size() < 2)
|
// line-of-sight: dispatching a straight line through walls
|
||||||
return false;
|
// produces visible clipping/glitching. If LOS is blocked we
|
||||||
|
// refuse and let UnstuckAction (5/10 min) catch the stuck.
|
||||||
G3D::Vector3 const& last = points.back();
|
bool const inLOS = bot->IsWithinLOS(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||||
|
LOG_INFO("playerbots", "[MoveFar] {} spline | dis={:.0f} | probe.empty={} | LOS={}",
|
||||||
float totalDist = 0.f;
|
bot->GetName(), dis,
|
||||||
for (size_t i = 1; i < points.size(); ++i)
|
probe.empty() ? "y" : "n",
|
||||||
totalDist += (points[i] - points[i - 1]).length();
|
inLOS ? "y" : "n");
|
||||||
|
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))
|
|
||||||
{
|
{
|
||||||
WorldPosition tail(dest.GetMapId(), last.x, last.y, last.z);
|
EmitDebugMove("MoveFar", "spline-blocked",
|
||||||
time_t now = time(nullptr);
|
dest.GetPositionX(), dest.GetPositionY(),
|
||||||
if (totalDist > sPlayerbotAIConfig.reactDistance &&
|
dest.GetPositionZ());
|
||||||
lastMove.nextTeleport <= now &&
|
return false; // Refuse to dispatch a straight line through geometry.
|
||||||
!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",
|
||||||
// masterWalking — match the master's walk pace when they're nearby
|
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||||
// and walking. Lets a follower bot trail at walk speed instead of
|
// Same exact_waypoint=false rationale as the mmap branch — terrain-
|
||||||
// sprinting past. No-op for masterless RPG bots. Mirrors cmangos
|
// following spline, not a straight diagonal.
|
||||||
// MovementActions.cpp:1212-1221. (Flying-mount mode flip deferred —
|
return MoveTo(dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
|
||||||
// requires takeoff/landing infrastructure we haven't ported.)
|
false, false, false, false);
|
||||||
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,20 +76,19 @@ 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)
|
||||||
{
|
{
|
||||||
Reset();
|
startT = getMSTime();
|
||||||
OutdoorPvP pvp;
|
OutdoorPvP pvp;
|
||||||
pvp.capturePointSpawnId = capturePointSpawnId;
|
pvp.capturePointSpawnId = capturePointSpawnId;
|
||||||
data = pvp;
|
data = pvp;
|
||||||
|
|||||||
@ -721,22 +721,6 @@ 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
|
||||||
@ -745,12 +729,16 @@ 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
|
||||||
@ -766,50 +754,11 @@ 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 {};
|
return fromPointsArray(points);
|
||||||
|
|
||||||
std::vector<WorldPosition> retvec = fromPointsArray(points);
|
return {};
|
||||||
|
|
||||||
// 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)
|
||||||
@ -845,50 +794,11 @@ 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, path);
|
subPath = getPathStepFrom(currentPos, bot);
|
||||||
|
|
||||||
// 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)
|
||||||
@ -905,9 +815,6 @@ std::vector<WorldPosition> WorldPosition::getPathFromPath(std::vector<WorldPosit
|
|||||||
currentPos = subPath.back();
|
currentPos = subPath.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tempCreature)
|
|
||||||
delete tempCreature;
|
|
||||||
|
|
||||||
return fullPath;
|
return fullPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,6 @@
|
|||||||
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;
|
||||||
@ -284,7 +283,6 @@ 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,31 +1285,18 @@ 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, Unit* bot)
|
WorldPosition destination)
|
||||||
{
|
{
|
||||||
plan.Reset();
|
plan.Reset();
|
||||||
plan.destination = destination;
|
plan.destination = destination;
|
||||||
|
|
||||||
// mmap probe first — mirrors cmangos getFullPath (TravelNode.cpp:1887-1895).
|
// Short distance — direct walk, no nodes needed
|
||||||
// 40-step chained probe from bot; if it gets within spellDistance of dest
|
if (botPos.fDist(destination) < MAX_PATHFINDING_DISTANCE &&
|
||||||
// we skip the graph entirely (a short walk is always better than a node
|
botPos.GetMapId() == destination.GetMapId())
|
||||||
// hop). When the probe falls short, fall through to graph routing.
|
|
||||||
if (botPos.GetMapId() == destination.GetMapId())
|
|
||||||
{
|
{
|
||||||
std::vector<WorldPosition> probe = destination.getPathFromPath({botPos}, bot, 40);
|
plan.steps.addPoint(botPos, PathNodeType::NODE_PREPATH);
|
||||||
// Guard the degenerate case: getPathFromPath always returns at
|
plan.steps.addPoint(destination, PathNodeType::NODE_PATH);
|
||||||
// least {botPos}, and isPathTo(probe, spellDistance) succeeds
|
return true;
|
||||||
// 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, Unit* bot = nullptr);
|
uint32 botZoneId, WorldPosition destination);
|
||||||
|
|
||||||
// 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,7 +98,6 @@ 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, walkDistance;
|
targetPosRecalcDistance, farDistance, healDistance, aggroDistance;
|
||||||
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