mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
fix(Core/RPG): Align MoveFarTo, MoveWorldObjectTo, MoveRandomNear with cmangos
This commit is contained in:
parent
4e8e3e2afe
commit
0d3d38b007
@ -237,18 +237,19 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Probe failed or didn't progress. Emit visibility whisper and bail.
|
// Probe failed or didn't progress. Attempt straight-line MoveTo to
|
||||||
// No straight-line fallback: dispatching a single-waypoint MoveTo
|
// the destination — engine PathFinder handles per-poly filtering and
|
||||||
// here delegates to the engine's PointMovementGenerator, which on
|
// the bot's STEEP/water filter is honored via CreateFilter. If even
|
||||||
// PATHFIND_NOPATH issues a straight-line spline through any terrain.
|
// that fails, the engine falls back to a direct spline.
|
||||||
// That caused bots to tunnel into mountains/trees when the chained
|
if (bot->GetMapId() != dest.GetMapId())
|
||||||
// probe couldn't find a valid path. Better to return false and let
|
return false;
|
||||||
// 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());
|
||||||
return false;
|
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,
|
||||||
@ -287,52 +288,7 @@ bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sparse-segment clip (cmangos parity): if any consecutive segment
|
// Save planner output for next-tick reuse.
|
||||||
// is longer than ~11.18y, truncate the path at that point. Short,
|
|
||||||
// dense waypoints reduce spline interpolation across visual
|
|
||||||
// obstacles between sparse points; bot re-plans from a closer
|
|
||||||
// position next tick.
|
|
||||||
{
|
|
||||||
constexpr float SPARSE_SEG_SQ = 125.0f; // sqrt(125) ≈ 11.18y
|
|
||||||
for (size_t i = 1; i < points.size(); ++i)
|
|
||||||
{
|
|
||||||
float dx = points[i].x - points[i - 1].x;
|
|
||||||
float dy = points[i].y - points[i - 1].y;
|
|
||||||
float dz = points[i].z - points[i - 1].z;
|
|
||||||
if (dx * dx + dy * dy + dz * dz > SPARSE_SEG_SQ)
|
|
||||||
{
|
|
||||||
points.resize(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (points.size() < 2)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LOS gate: reject paths whose segments pass through visual
|
|
||||||
// geometry. mmap is blind to M2 models (trees, decorative props)
|
|
||||||
// and will route through them; vmap LOS catches the cases that
|
|
||||||
// matter — solid trunks, walls, terrain features.
|
|
||||||
if (Map* map = bot->GetMap())
|
|
||||||
{
|
|
||||||
float const eye = bot->GetCollisionHeight();
|
|
||||||
for (size_t i = 0; i + 1 < points.size(); ++i)
|
|
||||||
{
|
|
||||||
if (!map->isInLineOfSight(points[i].x, points[i].y, points[i].z + eye,
|
|
||||||
points[i + 1].x, points[i + 1].y, points[i + 1].z + eye,
|
|
||||||
bot->GetPhaseMask(),
|
|
||||||
LINEOFSIGHT_ALL_CHECKS,
|
|
||||||
VMAP::ModelIgnoreFlags::Nothing))
|
|
||||||
{
|
|
||||||
EmitDebugMove("MoveFar", "blocked",
|
|
||||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save planner output before clip/fixup so next-tick reuse sees
|
|
||||||
// the original intent, not a truncated tail.
|
|
||||||
{
|
{
|
||||||
LastMovement& lm = AI_VALUE(LastMovement&, "last movement");
|
LastMovement& lm = AI_VALUE(LastMovement&, "last movement");
|
||||||
std::vector<WorldPosition> wpts;
|
std::vector<WorldPosition> wpts;
|
||||||
@ -342,26 +298,6 @@ bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest,
|
|||||||
lm.setPath(TravelPath(wpts));
|
lm.setPath(TravelPath(wpts));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Underwater fixup: lift submerged waypoints to the surface,
|
|
||||||
// unless the destination is itself underwater.
|
|
||||||
if (Map* map = bot->GetMap())
|
|
||||||
{
|
|
||||||
WorldPosition destWp = dest;
|
|
||||||
if (!destWp.isUnderWater())
|
|
||||||
{
|
|
||||||
for (auto& pt : points)
|
|
||||||
{
|
|
||||||
WorldPosition wp(dest.GetMapId(), pt.x, pt.y, pt.z);
|
|
||||||
if (wp.isUnderWater())
|
|
||||||
{
|
|
||||||
float surface = map->GetWaterLevel(pt.x, pt.y);
|
|
||||||
if (surface != INVALID_HEIGHT && surface > pt.z)
|
|
||||||
pt.z = surface;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& pt : points)
|
for (auto& pt : points)
|
||||||
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
|
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
|
||||||
|
|
||||||
@ -508,64 +444,24 @@ bool NewRpgBaseAction::MoveWorldObjectTo(ObjectGuid guid, float distance)
|
|||||||
if (!map)
|
if (!map)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// 8-angle deterministic iteration around the target. For each angle,
|
// 8 angles around the target, first one reachable wins. LOS check
|
||||||
// validate the candidate against the navmesh with a strict ground-only
|
// ignores M2 models (trees, decorative props) so long-distance NPCs
|
||||||
// filter (NAV_GROUND, exclude STEEP/WATER/MAGMA/SLIME). Reject if no
|
// through forested terrain still pass — the mmap probe in MoveFarTo
|
||||||
// valid poly within 5y XY+Z or if the snap drifts the Z by >10y.
|
// is the authoritative reachability check.
|
||||||
// First angle that passes both LOS and navmesh-snap wins.
|
|
||||||
dtNavMeshQuery const* navMeshQuery =
|
|
||||||
map->GetMapCollisionData().GetMMapData().GetNavMeshQuery();
|
|
||||||
float const baseAngle = object->GetAngle(bot);
|
float const baseAngle = object->GetAngle(bot);
|
||||||
|
|
||||||
for (float step = 0.0f; step < 2.0f * static_cast<float>(M_PI);
|
for (float step = 0.0f; step < 2.0f * static_cast<float>(M_PI);
|
||||||
step += static_cast<float>(M_PI) / 4.0f)
|
step += static_cast<float>(M_PI) / 4.0f)
|
||||||
{
|
{
|
||||||
float const angle = baseAngle + step;
|
float const angle = baseAngle + step;
|
||||||
float x = object->GetPositionX() + std::cos(angle) * distance;
|
float const x = object->GetPositionX() + std::cos(angle) * distance;
|
||||||
float y = object->GetPositionY() + std::sin(angle) * distance;
|
float const y = object->GetPositionY() + std::sin(angle) * distance;
|
||||||
float z = object->GetPositionZ();
|
float const z = object->GetPositionZ();
|
||||||
|
|
||||||
// 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
|
VMAP::ModelIgnoreFlags::M2))
|
||||||
// between candidate and GO). Without this, the 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
|
|
||||||
// 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()))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Strict navmesh-snap validation (cmangos ClosestCorrectPoint port).
|
|
||||||
if (navMeshQuery)
|
|
||||||
{
|
|
||||||
dtQueryFilter filter;
|
|
||||||
filter.setIncludeFlags(NAV_GROUND);
|
|
||||||
filter.setExcludeFlags(NAV_GROUND_STEEP | NAV_WATER | NAV_MAGMA | NAV_SLIME);
|
|
||||||
|
|
||||||
float const point[VERTEX_SIZE] = { y, z, x };
|
|
||||||
float const extents[VERTEX_SIZE] = { 5.0f, 5.0f, 5.0f };
|
|
||||||
float closest[VERTEX_SIZE] = { 0.0f, 0.0f, 0.0f };
|
|
||||||
dtPolyRef polyRef = INVALID_POLYREF;
|
|
||||||
|
|
||||||
if (!dtStatusSucceed(navMeshQuery->findNearestPoly(
|
|
||||||
point, extents, &filter, &polyRef, closest)) ||
|
|
||||||
polyRef == INVALID_POLYREF)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
float const snappedZ = closest[1];
|
|
||||||
if (std::fabs(snappedZ - z) > 10.0f)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
x = closest[2];
|
|
||||||
y = closest[0];
|
|
||||||
z = snappedZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MoveFarTo(WorldPosition(object->GetMapId(), x, y, z));
|
return MoveFarTo(WorldPosition(object->GetMapId(), x, y, z));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -577,47 +473,15 @@ bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority,
|
|||||||
if (IsWaitingForLastMove(priority))
|
if (IsWaitingForLastMove(priority))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Map* map = bot->GetMap();
|
float const distance = (0.4f + rand_norm() * 0.6f) * moveStep;
|
||||||
const float x = bot->GetPositionX();
|
float const angle = (float)rand_norm() * 2 * static_cast<float>(M_PI);
|
||||||
const float y = bot->GetPositionY();
|
float const dx = bot->GetPositionX() + distance * cos(angle);
|
||||||
const float z = bot->GetPositionZ();
|
float const dy = bot->GetPositionY() + distance * sin(angle);
|
||||||
// Retry random samples so one bad roll doesn't lock the bot in place.
|
float const dz = bot->GetPositionZ();
|
||||||
for (int attempt = 0; attempt < 8; ++attempt)
|
|
||||||
{
|
|
||||||
float distance = (0.4f + rand_norm() * 0.6f) * moveStep;
|
|
||||||
float angle = (float)rand_norm() * 2 * static_cast<float>(M_PI);
|
|
||||||
float dx = x + distance * cos(angle);
|
|
||||||
float dy = y + distance * sin(angle);
|
|
||||||
float dz = z;
|
|
||||||
|
|
||||||
PathResult path = GeneratePath(dx, dy, dz, RELAXED_PATH_ACCEPT_MASK, /*forceDestination=*/false);
|
bool moved = MoveTo(bot->GetMapId(), dx, dy, dz, false, false, false, true, priority);
|
||||||
|
EmitDebugMove("MoveRandomNear", moved ? "mmap" : "fail", dx, dy, dz);
|
||||||
if (!path.reachable)
|
return moved;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!map->CanReachPositionAndGetValidCoords(bot, dx, dy, dz))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (map->IsInWater(bot->GetPhaseMask(), dx, dy, dz, bot->GetCollisionHeight()))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Reject samples whose straight-line passes through visual
|
|
||||||
// obstacles (trees, models) that aren't in the navmesh. The
|
|
||||||
// smooth-path step can otherwise interpolate a waypoint inside
|
|
||||||
// a tree, making the bot visibly walk through it.
|
|
||||||
if (!bot->IsWithinLOS(dx, dy, dz))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bool moved = MoveTo(bot->GetMapId(), dx, dy, dz, false, false, false, true, priority);
|
|
||||||
if (moved)
|
|
||||||
{
|
|
||||||
EmitDebugMove("MoveRandomNear", "mmap", dx, dy, dz);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EmitDebugMove("MoveRandomNear", "all-fail", x, y, z);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NewRpgBaseAction::ForceToWait(uint32 duration, MovementPriority priority)
|
bool NewRpgBaseAction::ForceToWait(uint32 duration, MovementPriority priority)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user