fix(Core/RPG): Drop over-strict MoveFarTo and MoveWorldObjectTo guards

This commit is contained in:
bash 2026-05-30 14:54:36 +02:00
parent f868b6f96b
commit 721f81a119

View File

@ -217,26 +217,10 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
// Walk the chained probe's full waypoint chain via DispatchPathPoints. // Walk the chained probe's full waypoint chain via DispatchPathPoints.
if (!probe.empty() && probe.size() >= 2) if (!probe.empty() && probe.size() >= 2)
{ {
WorldPosition stepDest = probe.back(); float endDistToDest = dest.GetExactDist(probe.back().GetPositionX(),
float endDistToDest = dest.GetExactDist(stepDest.GetPositionX(), probe.back().GetPositionY(), probe.back().GetPositionZ());
stepDest.GetPositionY(), stepDest.GetPositionZ());
if (endDistToDest + 5.0f < disToDest) if (endDistToDest + 5.0f < disToDest)
{ {
// Z gap check: if the probe's last waypoint is well below
// the requested destination Z, the chain walked the ground
// polygon graph toward an elevated target it can't reach
// (quest giver on top of Aldrassil etc.). Refuse to dispatch
// — bot waits instead of tunneling into the visual model.
// 10y tolerates normal terrain variation (ramp ends, hill
// tops) while still catching clearly unreachable elevations.
if (std::fabs(stepDest.GetPositionZ() - dest.GetPositionZ()) > 10.0f)
{
EmitDebugMove("MoveFar", "z-mismatch",
dest.GetPositionX(), dest.GetPositionY(),
dest.GetPositionZ());
return false;
}
Movement::PointsArray points; Movement::PointsArray points;
points.reserve(probe.size()); points.reserve(probe.size());
for (auto const& wp : probe) for (auto const& wp : probe)
@ -253,39 +237,18 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
} }
} }
// Probe failed or didn't progress — emit visibility whisper so // Probe failed or didn't progress. Emit visibility whisper and bail.
// the user can see WHY mmap didn't dispatch. // No straight-line fallback: dispatching a single-waypoint MoveTo
{ // here delegates to the engine's PointMovementGenerator, which on
bool const probeProgressed = !probe.empty() && probe.size() >= 2 && // PATHFIND_NOPATH issues a straight-line spline through any terrain.
(dest.GetExactDist(probe.back().GetPositionX(), // That caused bots to tunnel into mountains/trees when the chained
probe.back().GetPositionY(), probe.back().GetPositionZ()) + 5.0f < disToDest); // probe couldn't find a valid path. Better to return false and let
if (!probeProgressed) // the caller pick a different objective.
{
char const* reason = (probe.empty() || probe.size() < 2) ? "mmap-empty" : "mmap-noprogress"; char const* reason = (probe.empty() || probe.size() < 2) ? "mmap-empty" : "mmap-noprogress";
EmitDebugMove("MoveFar", reason, EmitDebugMove("MoveFar", reason,
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionX(), dest.GetPositionY(),
dest.GetPositionZ()); dest.GetPositionZ());
}
}
// Empty-probe fallback: single-waypoint MoveTo via engine PathGenerator.
// Cross-map can't be served by a single-map spline — bail.
if (bot->GetMapId() != dest.GetMapId())
return false; return false;
// LOS gate: don't air-walk through trees/walls when the engine
// would otherwise drop to a straight-line BuildShortcut spline.
if (!bot->IsWithinLOS(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()))
{
EmitDebugMove("MoveFar", "spline-blocked",
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
return false;
}
EmitDebugMove("MoveFar", "spline",
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
return MoveTo(dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
false, false, false, false);
} }
bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest, bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest,
@ -562,16 +525,18 @@ bool NewRpgBaseAction::MoveWorldObjectTo(ObjectGuid guid, float distance)
float y = object->GetPositionY() + std::sin(angle) * distance; float y = object->GetPositionY() + std::sin(angle) * distance;
float z = object->GetPositionZ(); float z = object->GetPositionZ();
// LOS check at eye height — bot must be able to reach the candidate. // Candidate must have LOS to the GO — otherwise the bot would
if (!bot->IsWithinLOS(x, y, z + bot->GetCollisionHeight())) // arrive at a spot where it can't interact (e.g., a tree stands
continue; // between candidate and GO). Without this, the first angle
// (toward the bot) wins and lands the bot up against the
// Candidate must also have LOS to the GO itself — otherwise the
// bot arrives at a spot where it can't interact (e.g., a tree
// stands between candidate and GO). Without this, the very first
// angle (toward the bot) wins and lands the bot up against the
// obstacle instead of forcing iteration to an angle on the GO's // obstacle instead of forcing iteration to an angle on the GO's
// far side from the blocker. // far side from the blocker.
//
// Note: bot→candidate LOS is intentionally NOT checked. For
// long-distance targets (NPC across the zone, quest source
// 200y away) a direct line-of-sight through terrain almost
// always fails — bots aren't expected to see through hills.
// The mmap probe in MoveFarTo validates actual reachability.
if (!object->IsWithinLOS(x, y, z + bot->GetCollisionHeight())) if (!object->IsWithinLOS(x, y, z + bot->GetCollisionHeight()))
continue; continue;