Compare commits

..

3 Commits

4 changed files with 26 additions and 60 deletions

View File

@ -1264,8 +1264,6 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
return false;
}
EmitDebugMove("Follow", "follow", target->GetPositionX(), target->GetPositionY(), target->GetPositionZ());
/*
if (!bot->InBattleground()
&& ServerFacade::instance().IsDistanceLessOrEqualThan(ServerFacade::instance().GetDistance2d(bot, target->GetPositionX(),
@ -1339,6 +1337,8 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
if (ServerFacade::instance().IsDistanceGreaterOrEqualThan(ServerFacade::instance().GetDistance2d(bot, target),
sPlayerbotAIConfig.sightDistance))
{
EmitDebugMove("Follow", "mmap", target->GetPositionX(), target->GetPositionY(), target->GetPositionZ());
if (target->GetGUID().IsPlayer())
{
Player* pTarget = (Player*)target;
@ -1424,6 +1424,7 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FOLLOW_MOTION_TYPE)
bot->GetMotionMaster()->Clear();
EmitDebugMove("Follow", "follow", target->GetPositionX(), target->GetPositionY(), target->GetPositionZ());
bot->GetMotionMaster()->MoveFollow(target, distance, angle);
return true;
}
@ -3231,39 +3232,6 @@ bool MovementAction::LaunchWalkSpline(TravelPlan& state)
for (auto& pt : state.walkPoints)
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
// Drop waypoints whose segment from the previous point crosses
// solid geometry. Z-snapping each point to ground is necessary
// but not sufficient — two ground-level waypoints A and B with a
// mountain between them produce a spline that linearly
// interpolates straight through the mountain. vmap LoS check on
// each segment catches that. We only drop the offending B
// (skipping it) — if A→C is also blocked, the loop drops C too,
// until either the path becomes contiguous or empties out.
if (Map* losMap = bot->GetMap())
{
uint32 const phaseMask = bot->GetPhaseMask();
for (size_t i = 1; i < state.walkPoints.size(); /* incremented in body */)
{
G3D::Vector3 const& a = state.walkPoints[i - 1];
G3D::Vector3 const& b = state.walkPoints[i];
// +2y on Z so the raycast starts/ends near the bot's
// chest level rather than ground (avoids false positives
// from sub-floor poly).
if (!losMap->isInLineOfSight(a.x, a.y, a.z + 2.0f, b.x, b.y, b.z + 2.0f,
phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing))
{
state.walkPoints.erase(state.walkPoints.begin() + i);
continue;
}
++i;
}
if (state.walkPoints.size() < 2)
{
state.walkPoints.clear();
return true;
}
}
// Mount up
if (!bot->IsMounted() && !bot->IsInCombat() && bot->IsOutdoors() && bot->IsAlive())
botAI->DoSpecificAction("check mount state", Event(), true);

View File

@ -234,28 +234,6 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
for (auto& pt : points)
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
// Drop waypoints whose segment crosses geometry (Fix B
// logic, mirrored from LaunchWalkSpline). A pair of
// ground-level waypoints with a mountain between them
// would otherwise spline straight through.
if (Map* losMap = bot->GetMap())
{
uint32 const phaseMask = bot->GetPhaseMask();
for (size_t i = 1; i < points.size(); /* incremented in body */)
{
G3D::Vector3 const& a = points[i - 1];
G3D::Vector3 const& b = points[i];
if (!losMap->isInLineOfSight(a.x, a.y, a.z + 2.0f,
b.x, b.y, b.z + 2.0f, phaseMask, LINEOFSIGHT_ALL_CHECKS,
VMAP::ModelIgnoreFlags::Nothing))
{
points.erase(points.begin() + i);
continue;
}
++i;
}
}
if (points.size() >= 2)
{
// Cap the chain at 20 waypoints. Beyond that, the

View File

@ -78,12 +78,25 @@ void NewRpgInfo::Reset()
data = Idle{};
startT = getMSTime();
ClearTravel();
recentMoveFarAttempts.clear();
// recentMoveFarAttempts is intentionally NOT cleared. Reset() runs
// on every state change (ChangeToDoQuest, ChangeToIdle, etc.) and
// the do-quest action oscillates through transitions during a
// failure cycle — wiping the deque here would prevent the
// MoveFarTo loop-breaker (nF >= 3 AND mF >= 3 → bothExhausted)
// from converging. CountRecentAttempts already filters by
// destination (within 10y), so stale entries for previous quests
// don't affect new ones.
}
void NewRpgInfo::RecordMoveFarAttempt(WorldPosition const& dest, bool wasNodeTravel)
{
if (recentMoveFarAttempts.size() >= 3)
// Cap at 6 (3 node + 3 mmap). The loop-breaker in MoveFarTo
// requires nF >= 3 AND mF >= 3 to declare bothExhausted. Each
// MoveFarTo failure cycle records BOTH a node attempt and a mmap
// attempt, so a single 3-cap deque would pop the older type
// before its count reached 3, structurally preventing
// bothExhausted from triggering.
if (recentMoveFarAttempts.size() >= 6)
recentMoveFarAttempts.pop_front();
MoveFarAttempt a;
a.dest = dest;

View File

@ -739,7 +739,14 @@ std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos
if (tempCreature)
delete tempCreature;
if (type == PATHFIND_INCOMPLETE || type == PATHFIND_NORMAL)
// PathType is a bitmask (PathGenerator.h). Detour can return e.g.
// PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY_END (0x84) when the
// destination is a few yards off the nearest polygon — a strict
// `== PATHFIND_INCOMPLETE` check would reject the perfectly usable
// partial path and the chained probe would terminate empty on the
// very first call. PathGenerator's own internal code uses bitwise
// tests like `!(_type & PATHFIND_INCOMPLETE)`.
if (type & (PATHFIND_NORMAL | PATHFIND_INCOMPLETE))
return fromPointsArray(points);
return {};