mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
refactor(Core/Movement): Rewrite MoveFarTo to use ResolveMovePath + HandleSpecialMovement
This commit is contained in:
parent
05cf5a7702
commit
8b87ab091f
@ -62,19 +62,21 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Already-at-dest short-stop. Below targetPosRecalcDistance the
|
// Resume a transport ride if we're still on the same boat as last tick.
|
||||||
// move is effectively done — stop any active spline and clear
|
if (WaitForTransport())
|
||||||
// the cached path if it pointed here, so we don't keep gliding.
|
return true;
|
||||||
|
|
||||||
|
WorldPosition botPos(bot);
|
||||||
|
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
||||||
|
|
||||||
|
// Short-stop: at destination — stop and clear the cached path.
|
||||||
{
|
{
|
||||||
float const totalDistance = bot->GetExactDist(dest);
|
float const totalDistance = botPos.distance(dest);
|
||||||
if (totalDistance < sPlayerbotAIConfig.targetPosRecalcDistance)
|
if (totalDistance < sPlayerbotAIConfig.targetPosRecalcDistance)
|
||||||
{
|
{
|
||||||
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
|
||||||
if (!lastMove.lastPath.empty() &&
|
if (!lastMove.lastPath.empty() &&
|
||||||
lastMove.lastPath.getBack().distance(dest) <= totalDistance)
|
lastMove.lastPath.getBack().distance(dest) <= totalDistance)
|
||||||
{
|
|
||||||
lastMove.clear();
|
lastMove.clear();
|
||||||
}
|
|
||||||
bot->StopMoving();
|
bot->StopMoving();
|
||||||
EmitDebugMove("MoveFar", "arrived",
|
EmitDebugMove("MoveFar", "arrived",
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||||
@ -82,174 +84,65 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10% lastPath reuse — if the cached path's endpoint is still
|
// Per-tick re-resolve: rebuild the TravelPath from the bot's current
|
||||||
// close (within 10%) to the new dest, trim the cached path to
|
// position every tick (10% reuse short-circuits via the cached
|
||||||
// the bot's current position via makeShortCut and re-dispatch.
|
// lastPath). Recovers naturally from knockback, off-route drift,
|
||||||
// Per-tick re-dispatch of the (trimmed) last path keeps the bot
|
// destination changes, and blocked waypoints.
|
||||||
// on-route after interrupts (knockback, combat, manual move)
|
TravelPath path = ResolveMovePath(botPos, dest, lastMove);
|
||||||
// without needing a full replan.
|
lastMove.setPath(path);
|
||||||
{
|
|
||||||
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
|
||||||
if (!lastMove.lastPath.empty())
|
|
||||||
{
|
|
||||||
WorldPosition lastBack = lastMove.lastPath.getBack();
|
|
||||||
if (lastBack.GetMapId() == dest.GetMapId())
|
|
||||||
{
|
|
||||||
float totalDist = bot->GetExactDist(dest);
|
|
||||||
float maxDistChange = totalDist * 0.10f;
|
|
||||||
float distFromBotToBack = bot->GetExactDist(&lastBack);
|
|
||||||
if (lastBack.distance(dest) < maxDistChange && distFromBotToBack > 10.0f)
|
|
||||||
{
|
|
||||||
WorldPosition botPos(bot);
|
|
||||||
lastMove.lastPath.makeShortCut(botPos, sPlayerbotAIConfig.reactDistance, bot);
|
|
||||||
|
|
||||||
// makeShortCut may clear the path if the bot drifted
|
if (path.empty())
|
||||||
// too far off (>reactDistance from any waypoint). In
|
|
||||||
// that case fall through to fresh planning.
|
|
||||||
if (lastMove.lastPath.empty())
|
|
||||||
{
|
{
|
||||||
EmitDebugMove("MoveFar", "reuse-trim-failed",
|
EmitDebugMove("MoveFar", "no-path",
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
|
||||||
}
|
|
||||||
if (!lastMove.lastPath.empty())
|
|
||||||
{
|
|
||||||
std::vector<WorldPosition> const& pts = lastMove.lastPath.getPointPath();
|
|
||||||
if (pts.size() >= 2)
|
|
||||||
{
|
|
||||||
Movement::PointsArray points;
|
|
||||||
points.reserve(pts.size());
|
|
||||||
for (auto const& wp : pts)
|
|
||||||
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
|
||||||
return DispatchPathPoints(dest, points, "reuse");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Path was cleared or collapsed — fall through to fresh planning.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float disToDest = bot->GetDistance(dest);
|
|
||||||
float dis = bot->GetExactDist(dest);
|
|
||||||
|
|
||||||
// Try the travel-node graph for cross-map or moves longer than the
|
|
||||||
// bot's sight distance; otherwise the chained mmap probe handles it.
|
|
||||||
// BGs skip the graph.
|
|
||||||
bool tryNodes = sPlayerbotAIConfig.enableTravelNodes &&
|
|
||||||
!bot->InBattleground() &&
|
|
||||||
((bot->GetMapId() != dest.GetMapId()) ||
|
|
||||||
(dis > sPlayerbotAIConfig.sightDistance));
|
|
||||||
|
|
||||||
// Per-tick re-resolve (cmangos pattern). Rebuild the travel plan
|
|
||||||
// from the bot's CURRENT position every tick rather than caching
|
|
||||||
// a multi-step plan and advancing through it. Recovers naturally
|
|
||||||
// from knockback, off-route drift, mid-execution destination
|
|
||||||
// changes, and blocked waypoints. Cost: per-tick GetFullPath call;
|
|
||||||
// the lastPath cache (10% reuse block above) handles the common
|
|
||||||
// case where the cached path still ends near the same destination
|
|
||||||
// and avoids re-derivation.
|
|
||||||
if (tryNodes)
|
|
||||||
{
|
|
||||||
if (botAI->rpgInfo.HasActiveTravelPlan() &&
|
|
||||||
botAI->rpgInfo.travelPlan.destination.distance(dest) > 10.0f)
|
|
||||||
botAI->rpgInfo.ClearTravel();
|
|
||||||
|
|
||||||
StartTravelPlan(dest);
|
|
||||||
if (botAI->rpgInfo.HasActiveTravelPlan())
|
|
||||||
{
|
|
||||||
// No `travelplan` label here — per-tick re-resolve calls
|
|
||||||
// StartTravelPlan every tick, which would whisper-spam.
|
|
||||||
// The executor emits per-step labels (TravelPlan:walk-start,
|
|
||||||
// TravelPlan:flight, TravelPlan:transport-*) on actual dispatch.
|
|
||||||
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 — primary for short moves and
|
|
||||||
// fallback when the node graph returned no plan.
|
|
||||||
WorldPosition botPos(bot);
|
|
||||||
std::vector<WorldPosition> probe = botPos.getPathTo(dest, bot);
|
|
||||||
|
|
||||||
// Regression guard: prefer cached lastPath if it still ends closer
|
|
||||||
// to dest than the new probe — catches probes blocked by geometry.
|
|
||||||
{
|
|
||||||
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
|
||||||
if (!lastMove.lastPath.empty() && !probe.empty() && probe.size() >= 2)
|
|
||||||
{
|
|
||||||
WorldPosition lastBack = lastMove.lastPath.getBack();
|
|
||||||
if (lastBack.GetMapId() == dest.GetMapId())
|
|
||||||
{
|
|
||||||
float cachedToDest = lastBack.distance(dest);
|
|
||||||
float probeToDest = dest.GetExactDist(probe.back().GetPositionX(),
|
|
||||||
probe.back().GetPositionY(),
|
|
||||||
probe.back().GetPositionZ());
|
|
||||||
if (cachedToDest <= probeToDest)
|
|
||||||
{
|
|
||||||
WorldPosition botPosNow(bot);
|
|
||||||
lastMove.lastPath.makeShortCut(botPosNow, sPlayerbotAIConfig.reactDistance, bot);
|
|
||||||
if (!lastMove.lastPath.empty())
|
|
||||||
{
|
|
||||||
std::vector<WorldPosition> const& pts = lastMove.lastPath.getPointPath();
|
|
||||||
if (pts.size() >= 2)
|
|
||||||
{
|
|
||||||
Movement::PointsArray points;
|
|
||||||
points.reserve(pts.size());
|
|
||||||
for (auto const& wp : pts)
|
|
||||||
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
|
||||||
return DispatchPathPoints(dest, points, "regress-keep");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk the chained probe's full waypoint chain via DispatchPathPoints.
|
|
||||||
if (!probe.empty() && probe.size() >= 2)
|
|
||||||
{
|
|
||||||
float endDistToDest = dest.GetExactDist(probe.back().GetPositionX(),
|
|
||||||
probe.back().GetPositionY(), probe.back().GetPositionZ());
|
|
||||||
if (endDistToDest + 5.0f < disToDest)
|
|
||||||
{
|
|
||||||
Movement::PointsArray points;
|
|
||||||
points.reserve(probe.size());
|
|
||||||
for (auto const& wp : probe)
|
|
||||||
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
|
||||||
|
|
||||||
if (points.size() >= 2)
|
|
||||||
{
|
|
||||||
// Mount up if outdoors and not in combat.
|
|
||||||
if (!bot->IsMounted() && !bot->IsInCombat() && bot->IsOutdoors() && bot->IsAlive())
|
|
||||||
botAI->DoSpecificAction("check mount state", Event(), true);
|
|
||||||
|
|
||||||
return DispatchPathPoints(dest, points, "mmap");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probe failed or didn't progress. Attempt straight-line MoveTo to
|
|
||||||
// the destination — engine PathFinder handles per-poly filtering and
|
|
||||||
// the bot's STEEP/water filter is honored via CreateFilter. If even
|
|
||||||
// that fails, the engine falls back to a direct spline.
|
|
||||||
if (bot->GetMapId() != dest.GetMapId())
|
|
||||||
{
|
|
||||||
EmitDebugMove("MoveFar", "cross-map",
|
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char const* reason = (probe.empty() || probe.size() < 2) ? "mmap-empty" : "mmap-noprogress";
|
// Trim leading waypoints behind the bot, bridge with mmap probe if
|
||||||
EmitDebugMove("MoveFar", reason,
|
// the new head requires it. May empty the path (collapsed) — let
|
||||||
dest.GetPositionX(), dest.GetPositionY(),
|
// the next tick rebuild from a fresh start.
|
||||||
dest.GetPositionZ());
|
path.makeShortCut(botPos, sPlayerbotAIConfig.reactDistance, bot);
|
||||||
return MoveTo(dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(),
|
if (path.empty())
|
||||||
dest.GetPositionZ(), false, false, false, false);
|
return true;
|
||||||
|
|
||||||
|
// Special head segment (portal / area-trigger / transport / flight)?
|
||||||
|
// UpcommingSpecialMovement cuts the path so the head is the special;
|
||||||
|
// HandleSpecialMovement dispatches the matching action.
|
||||||
|
bool const onTransport = bot->GetTransport() != nullptr;
|
||||||
|
if (path.UpcommingSpecialMovement(botPos,
|
||||||
|
sPlayerbotAIConfig.reactDistance,
|
||||||
|
onTransport))
|
||||||
|
{
|
||||||
|
if (HandleSpecialMovement(path))
|
||||||
|
return true;
|
||||||
|
// Special handler declined (e.g. AREA_TRIGGER with entry → caller
|
||||||
|
// dispatches the walk into the trigger volume). Fall through.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk dispatch.
|
||||||
|
std::vector<WorldPosition> const& pts = path.getPointPath();
|
||||||
|
Movement::PointsArray points;
|
||||||
|
points.reserve(pts.size());
|
||||||
|
for (auto const& wp : pts)
|
||||||
|
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
|
||||||
|
|
||||||
|
if (points.size() < 2)
|
||||||
|
{
|
||||||
|
// Single-point fallback path (cmangos pattern: ResolveMovePath
|
||||||
|
// emits a single dest point if nothing else worked). Hand it
|
||||||
|
// to the engine's MovePoint via MoveTo.
|
||||||
|
EmitDebugMove("MoveFar", "single-point",
|
||||||
|
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||||
|
return MoveTo(dest.GetMapId(), dest.GetPositionX(),
|
||||||
|
dest.GetPositionY(), dest.GetPositionZ(),
|
||||||
|
false, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bot->IsMounted() && !bot->IsInCombat() &&
|
||||||
|
bot->IsOutdoors() && bot->IsAlive())
|
||||||
|
botAI->DoSpecificAction("check mount state", Event(), true);
|
||||||
|
|
||||||
|
return DispatchPathPoints(dest, points, "walk");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest,
|
bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user