mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
refactor(Core/Movement): Close walking-path divergences from reference (A-E)
This commit is contained in:
parent
c82cd18677
commit
1d85510a9e
@ -31,7 +31,6 @@
|
|||||||
#include "Position.h"
|
#include "Position.h"
|
||||||
#include "PositionValue.h"
|
#include "PositionValue.h"
|
||||||
#include "Random.h"
|
#include "Random.h"
|
||||||
#include "RandomPlayerbotMgr.h"
|
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
#include "SpellAuraEffects.h"
|
#include "SpellAuraEffects.h"
|
||||||
@ -436,8 +435,11 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
|
|||||||
// Combat callers pass ignoreEnemyTargets=true so ClipPath doesn't
|
// Combat callers pass ignoreEnemyTargets=true so ClipPath doesn't
|
||||||
// halt the chase at an intermediate hostile when funnelling through
|
// halt the chase at an intermediate hostile when funnelling through
|
||||||
// MoveTo2 — the chase target itself is the enemy we want to reach.
|
// MoveTo2 — the chase target itself is the enemy we want to reach.
|
||||||
bool moved = MoveTo(target->GetMapId(), endPos.x, endPos.y, endPos.z, false, false, false, false,
|
// react=true skips the end-of-dispatch WaitForReach so the bot keeps
|
||||||
MovementPriority::MOVEMENT_COMBAT, true, false, /*ignoreEnemyTargets*/true);
|
// re-evaluating mid-chase instead of waiting for the spline to play
|
||||||
|
// out (which would suspend combat reactions for seconds at a time).
|
||||||
|
bool moved = MoveTo(target->GetMapId(), endPos.x, endPos.y, endPos.z, /*idle*/false, /*react*/true, false, false,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT, /*lessDelay*/true, false, /*ignoreEnemyTargets*/true);
|
||||||
// Only emit on a successful new commit — combat ticks call this
|
// Only emit on a successful new commit — combat ticks call this
|
||||||
// many times per second and MoveTo internally suppresses while a
|
// many times per second and MoveTo internally suppresses while a
|
||||||
// prior spline is still playing. Emitting before the suppression
|
// prior spline is still playing. Emitting before the suppression
|
||||||
@ -2898,7 +2900,7 @@ bool MovementAction::BoardTransport(Transport* transport)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MovementAction::MoveTo2(WorldPosition endPos,
|
bool MovementAction::MoveTo2(WorldPosition endPos,
|
||||||
bool idle, [[maybe_unused]] bool react,
|
bool idle, bool react,
|
||||||
[[maybe_unused]] bool noPath,
|
[[maybe_unused]] bool noPath,
|
||||||
bool ignoreEnemyTargets,
|
bool ignoreEnemyTargets,
|
||||||
MovementPriority priority,
|
MovementPriority priority,
|
||||||
@ -2918,6 +2920,25 @@ bool MovementAction::MoveTo2(WorldPosition endPos,
|
|||||||
WorldPosition botPos(bot);
|
WorldPosition botPos(bot);
|
||||||
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
||||||
|
|
||||||
|
// Detailed-move throttle: if this bot is in low-activity mode
|
||||||
|
// (random/background) and a teleport cooldown is still in effect
|
||||||
|
// from a prior dispatch, postpone re-evaluation until the cooldown
|
||||||
|
// expires instead of re-resolving the path every tick.
|
||||||
|
bool const detailedMove = botAI->AllowActivity(DETAILED_MOVE_ACTIVITY, true);
|
||||||
|
if (!detailedMove && lastMove.nextTeleport)
|
||||||
|
{
|
||||||
|
time_t const now = time(nullptr);
|
||||||
|
if (lastMove.nextTeleport > now)
|
||||||
|
{
|
||||||
|
botAI->SetNextCheckDelay((uint32)((lastMove.nextTeleport - now) * 1000));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lastMove.nextTeleport = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Short-stop: at destination — stop and clear the cached path.
|
// Short-stop: at destination — stop and clear the cached path.
|
||||||
float const totalDistance = botPos.distance(endPos);
|
float const totalDistance = botPos.distance(endPos);
|
||||||
if (totalDistance < sPlayerbotAIConfig.targetPosRecalcDistance)
|
if (totalDistance < sPlayerbotAIConfig.targetPosRecalcDistance)
|
||||||
@ -2976,6 +2997,10 @@ bool MovementAction::MoveTo2(WorldPosition endPos,
|
|||||||
if (path.empty())
|
if (path.empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// If destination is on land, snap any underwater waypoints to the
|
||||||
|
// water surface so the bot swims along the top instead of diving.
|
||||||
|
path.surfaceSnapWaypoints(endPos);
|
||||||
|
|
||||||
// Telemetry: show the path's actual tail coords vs bot + dest so we
|
// Telemetry: show the path's actual tail coords vs bot + dest so we
|
||||||
// can see whether the resolved path is heading toward the right place.
|
// can see whether the resolved path is heading toward the right place.
|
||||||
if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
||||||
@ -3001,7 +3026,7 @@ bool MovementAction::MoveTo2(WorldPosition endPos,
|
|||||||
botAI->DoSpecificAction("check mount state", Event(), true);
|
botAI->DoSpecificAction("check mount state", Event(), true);
|
||||||
|
|
||||||
bool const dispatched =
|
bool const dispatched =
|
||||||
DispatchMovement(path, endPos, "walk", priority, lessDelay);
|
DispatchMovement(path, endPos, "walk", priority, lessDelay, react);
|
||||||
|
|
||||||
if (dispatched && !idle)
|
if (dispatched && !idle)
|
||||||
ClearIdleState();
|
ClearIdleState();
|
||||||
@ -3013,7 +3038,8 @@ bool MovementAction::DispatchMovement(TravelPath path,
|
|||||||
WorldPosition dest,
|
WorldPosition dest,
|
||||||
char const* label,
|
char const* label,
|
||||||
MovementPriority priority,
|
MovementPriority priority,
|
||||||
bool lessDelay)
|
bool lessDelay,
|
||||||
|
bool react)
|
||||||
{
|
{
|
||||||
// Build the PointsArray from the TravelPath. Done here (not at the
|
// Build the PointsArray from the TravelPath. Done here (not at the
|
||||||
// caller) so DispatchMovement can be invoked with a TravelPath
|
// caller) so DispatchMovement can be invoked with a TravelPath
|
||||||
@ -3033,11 +3059,11 @@ bool MovementAction::DispatchMovement(TravelPath path,
|
|||||||
for (size_t i = 1; i < points.size(); ++i)
|
for (size_t i = 1; i < points.size(); ++i)
|
||||||
totalDist += (points[i] - points[i - 1]).length();
|
totalDist += (points[i] - points[i - 1]).length();
|
||||||
|
|
||||||
// Skip cosmetic walking for random bots with no nearby player —
|
// Skip cosmetic walking for low-activity bots with no nearby
|
||||||
// teleport to the path tail and schedule a cooldown instead.
|
// player — teleport to the path tail and schedule a cooldown
|
||||||
// (Reference equivalent: long-distance teleport in MoveTo2 gated
|
// instead. Matches the reference's MoveTo2 gate
|
||||||
// on !detailedMove && !HasPlayerNearby. Our gate is IsRandomBot.)
|
// (`!detailedMove && !HasPlayerNearby`).
|
||||||
if (sRandomPlayerbotMgr.IsRandomBot(bot))
|
if (!botAI->AllowActivity(DETAILED_MOVE_ACTIVITY, true))
|
||||||
{
|
{
|
||||||
WorldPosition tail(dest.GetMapId(), last.x, last.y, last.z);
|
WorldPosition tail(dest.GetMapId(), last.x, last.y, last.z);
|
||||||
time_t now = time(nullptr);
|
time_t now = time(nullptr);
|
||||||
@ -3072,7 +3098,11 @@ bool MovementAction::DispatchMovement(TravelPath path,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool const generatePath = !bot->IsFlying() && !bot->isSwimming();
|
// Reference: also gates on !IsInWater && !IsUnderWater so a bot
|
||||||
|
// wading through shallow water (no SWIMMING movement flag yet)
|
||||||
|
// doesn't trigger engine pathfinding mid-dispatch.
|
||||||
|
bool const generatePath = !bot->IsFlying() && !bot->isSwimming() &&
|
||||||
|
!bot->IsInWater() && !bot->IsUnderWater();
|
||||||
|
|
||||||
// Pre-dispatch normalization: clear looping emote, stand, interrupt
|
// Pre-dispatch normalization: clear looping emote, stand, interrupt
|
||||||
// non-melee cast. Reference does this at MoveTo2 level before
|
// non-melee cast. Reference does this at MoveTo2 level before
|
||||||
@ -3132,5 +3162,11 @@ bool MovementAction::DispatchMovement(TravelPath path,
|
|||||||
lastMove.Set(bot->GetMapId(), last.x, last.y, last.z,
|
lastMove.Set(bot->GetMapId(), last.x, last.y, last.z,
|
||||||
bot->GetOrientation(), (uint32)duration, priority);
|
bot->GetOrientation(), (uint32)duration, priority);
|
||||||
|
|
||||||
|
// Reference: DispatchMovement ends with WaitForReach(size) to block
|
||||||
|
// the AI loop while the spline plays. Combat callers (react=true)
|
||||||
|
// opt out so they can keep re-evaluating mid-chase.
|
||||||
|
if (!react)
|
||||||
|
WaitForReach(points);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,6 +69,10 @@ protected:
|
|||||||
// MoveTo(mapId,...) delegates here unless an intentional bypass
|
// MoveTo(mapId,...) delegates here unless an intentional bypass
|
||||||
// (exact_waypoint / disableMoveSplinePath / flying / swimming /
|
// (exact_waypoint / disableMoveSplinePath / flying / swimming /
|
||||||
// backwards) routes the move straight to DoMovePoint.
|
// backwards) routes the move straight to DoMovePoint.
|
||||||
|
// `react=true` opts the move out of the end-of-dispatch
|
||||||
|
// WaitForReach AI-loop block — combat callers should set this so the
|
||||||
|
// bot can keep re-evaluating mid-chase. Default false matches the
|
||||||
|
// reference's MoveTo2 default.
|
||||||
bool MoveTo2(WorldPosition endPos,
|
bool MoveTo2(WorldPosition endPos,
|
||||||
bool idle = false, bool react = false,
|
bool idle = false, bool react = false,
|
||||||
bool noPath = false, bool ignoreEnemyTargets = false,
|
bool noPath = false, bool ignoreEnemyTargets = false,
|
||||||
@ -91,11 +95,15 @@ protected:
|
|||||||
// re-evaluation for the full move duration. Until combat dispatch is
|
// re-evaluation for the full move duration. Until combat dispatch is
|
||||||
// restructured to bypass MoveTo2, the WaitForReach is deliberately
|
// restructured to bypass MoveTo2, the WaitForReach is deliberately
|
||||||
// omitted.
|
// omitted.
|
||||||
|
// `react=true` skips the end-of-dispatch WaitForReach so the AI
|
||||||
|
// loop isn't blocked while the spline plays — combat callers use
|
||||||
|
// this to keep re-evaluating mid-chase.
|
||||||
bool DispatchMovement(TravelPath path,
|
bool DispatchMovement(TravelPath path,
|
||||||
WorldPosition dest,
|
WorldPosition dest,
|
||||||
char const* label,
|
char const* label,
|
||||||
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL,
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL,
|
||||||
bool lessDelay = false);
|
bool lessDelay = false,
|
||||||
|
bool react = false);
|
||||||
bool MoveTo(WorldObject* target, float distance = 0.0f,
|
bool MoveTo(WorldObject* target, float distance = 0.0f,
|
||||||
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||||
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig.contactDistance,
|
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig.contactDistance,
|
||||||
|
|||||||
@ -274,6 +274,27 @@ bool WorldPosition::isUnderWater()
|
|||||||
: false;
|
: false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool WorldPosition::setAtWaterSurface()
|
||||||
|
{
|
||||||
|
if (!isInWater() && !isUnderWater())
|
||||||
|
return false;
|
||||||
|
Map* map = getMap();
|
||||||
|
if (!map)
|
||||||
|
return false;
|
||||||
|
// Returns the water level when liquid is present; falls back to
|
||||||
|
// ground level otherwise. Our isInWater/isUnderWater preconditions
|
||||||
|
// ensure liquid exists, so the +0.5y nudge lands the point on top
|
||||||
|
// of the surface (matches the reference's surface snap).
|
||||||
|
float const level = map->GetWaterOrGroundLevel(PHASEMASK_NORMAL,
|
||||||
|
GetPositionX(),
|
||||||
|
GetPositionY(),
|
||||||
|
GetPositionZ());
|
||||||
|
if (level <= INVALID_HEIGHT)
|
||||||
|
return false;
|
||||||
|
setZ(level + 0.5f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool WorldPosition::IsValid()
|
bool WorldPosition::IsValid()
|
||||||
{
|
{
|
||||||
return !(GetMapId() == MAPID_INVALID && GetPositionX() == 0 && GetPositionY() == 0 && GetPositionZ() == 0);
|
return !(GetMapId() == MAPID_INVALID && GetPositionX() == 0 && GetPositionY() == 0 && GetPositionZ() == 0);
|
||||||
|
|||||||
@ -138,6 +138,9 @@ public:
|
|||||||
bool isOverworld();
|
bool isOverworld();
|
||||||
bool isInWater();
|
bool isInWater();
|
||||||
bool isUnderWater();
|
bool isUnderWater();
|
||||||
|
// Snap Z to the water surface (level + 0.5y). Returns false if the
|
||||||
|
// point isn't in/under water or the water level can't be sampled.
|
||||||
|
bool setAtWaterSurface();
|
||||||
bool IsValid();
|
bool IsValid();
|
||||||
|
|
||||||
WorldPosition relPoint(WorldPosition* center);
|
WorldPosition relPoint(WorldPosition* center);
|
||||||
|
|||||||
@ -908,6 +908,22 @@ void TravelPath::ClipPath(PlayerbotAI* ai, Unit* mover, bool ignoreEnemyTargets)
|
|||||||
fullPath.erase(std::next(endP), fullPath.end());
|
fullPath.erase(std::next(endP), fullPath.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TravelPath::surfaceSnapWaypoints(WorldPosition endPos)
|
||||||
|
{
|
||||||
|
if (fullPath.empty())
|
||||||
|
return;
|
||||||
|
// Same map + dest is on land. If dest is itself underwater the bot
|
||||||
|
// wants to dive; leave waypoints alone.
|
||||||
|
if (fullPath.front().point.GetMapId() != endPos.GetMapId() ||
|
||||||
|
endPos.isUnderWater())
|
||||||
|
return;
|
||||||
|
for (auto& p : fullPath)
|
||||||
|
{
|
||||||
|
if (p.point.isUnderWater())
|
||||||
|
p.point.setAtWaterSurface();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool TravelPath::makeShortCut(WorldPosition startPos, float maxDist, Unit* bot)
|
bool TravelPath::makeShortCut(WorldPosition startPos, float maxDist, Unit* bot)
|
||||||
{
|
{
|
||||||
if (GetPath().empty())
|
if (GetPath().empty())
|
||||||
|
|||||||
@ -486,6 +486,14 @@ public:
|
|||||||
|
|
||||||
bool makeShortCut(WorldPosition startPos, float maxDist, Unit* bot = nullptr);
|
bool makeShortCut(WorldPosition startPos, float maxDist, Unit* bot = nullptr);
|
||||||
|
|
||||||
|
// For each waypoint that's in/under water, snap its Z to the water
|
||||||
|
// surface. No-op when destination is itself underwater (caller wants
|
||||||
|
// the bot to dive) or path's front map differs from dest map.
|
||||||
|
// Mirrors the reference's underwater→surface snap so bots swim
|
||||||
|
// along the top of shallow water on land-bound paths instead of
|
||||||
|
// diving and air-walking the seafloor.
|
||||||
|
void surfaceSnapWaypoints(WorldPosition endPos);
|
||||||
|
|
||||||
// Trim the path up to (and optionally including) the given point.
|
// Trim the path up to (and optionally including) the given point.
|
||||||
// Returns true if the point was found. Used by upcoming special-
|
// Returns true if the point was found. Used by upcoming special-
|
||||||
// movement detection to advance the path past a portal/transport/
|
// movement detection to advance the path past a portal/transport/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user