mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
fix(Core/Movement): MoveNear engine-aware near-point + FollowOnTransport port + drop dead botZoneId
This commit is contained in:
parent
c4a0d01d34
commit
fa3ffd13fa
@ -214,26 +214,28 @@ bool MovementAction::MoveNear(WorldObject* target, float distance, MovementPrior
|
|||||||
if (!target)
|
if (!target)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Reference uses bounding radius (collision footprint), not combat
|
float const followAngle = GetFollowAngle();
|
||||||
// reach (which is wider for big mobs). Bounding radius lands the bot
|
float const followRange = botAI->GetRange("follow");
|
||||||
// at the requested standoff from the model edge, not arbitrarily far.
|
|
||||||
distance += target->GetObjectSize();
|
|
||||||
|
|
||||||
float followAngle = GetFollowAngle();
|
|
||||||
|
|
||||||
for (float angle = followAngle; angle <= followAngle + static_cast<float>(2 * M_PI);
|
for (float angle = followAngle; angle <= followAngle + static_cast<float>(2 * M_PI);
|
||||||
angle += static_cast<float>(M_PI / 4.f))
|
angle += static_cast<float>(M_PI / 4.f))
|
||||||
{
|
{
|
||||||
float x = target->GetPositionX() + cos(angle) * distance;
|
// GetNearPoint is engine-aware: snaps to walkable terrain,
|
||||||
float y = target->GetPositionY() + sin(angle) * distance;
|
// avoids collision geometry, clamps Z. Drops the raw cos/sin
|
||||||
|
// offset that could land the bot inside walls or on ledges.
|
||||||
|
// Matches the reference's MoveNear shape.
|
||||||
|
float x = target->GetPositionX();
|
||||||
|
float y = target->GetPositionY();
|
||||||
float z = target->GetPositionZ();
|
float z = target->GetPositionZ();
|
||||||
// Clamp Z to the terrain under the offset point so we don't
|
float const dist = distance + target->GetObjectSize();
|
||||||
// hand PointMovementGenerator a Z that matches the target's
|
target->GetNearPoint(bot, x, y, z, bot->GetObjectSize(),
|
||||||
// floor but not the sampled (x,y) — avoids straight-line
|
std::min(dist, followRange), angle);
|
||||||
// fallbacks through geometry.
|
|
||||||
bot->UpdateAllowedPositionZ(x, y, z);
|
|
||||||
|
|
||||||
if (!bot->IsWithinLOS(x, y, z))
|
// LOS test at eye-level (collision-height above feet) ignoring
|
||||||
|
// M2 models — large M2 trees/canopies otherwise block the test
|
||||||
|
// even when the bot can walk to the spot.
|
||||||
|
if (!bot->IsWithinLOS(x, y, z + bot->GetCollisionHeight(),
|
||||||
|
VMAP::ModelIgnoreFlags::M2))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
bool moved = MoveTo(target->GetMapId(), x, y, z, false, false, false, false, priority);
|
bool moved = MoveTo(target->GetMapId(), x, y, z, false, false, false, false, priority);
|
||||||
@ -696,6 +698,14 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
|
|||||||
|
|
||||||
UpdateMovementState();
|
UpdateMovementState();
|
||||||
|
|
||||||
|
// Cross-transport follow: if bot and target are on different
|
||||||
|
// transports (or only one is on a transport) and within sight,
|
||||||
|
// disembark/teleport/board to match. Handled here before the
|
||||||
|
// distance gates so a bot on a stationary boat following a
|
||||||
|
// master who just boarded doesn't get stuck at "no need to follow".
|
||||||
|
if (FollowOnTransport(target))
|
||||||
|
return true;
|
||||||
|
|
||||||
if (!bot->InBattleground() && ServerFacade::instance().IsDistanceLessOrEqualThan(ServerFacade::instance().GetDistance2d(bot, target),
|
if (!bot->InBattleground() && ServerFacade::instance().IsDistanceLessOrEqualThan(ServerFacade::instance().GetDistance2d(bot, target),
|
||||||
sPlayerbotAIConfig.followDistance))
|
sPlayerbotAIConfig.followDistance))
|
||||||
{
|
{
|
||||||
@ -816,6 +826,42 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MovementAction::FollowOnTransport(Unit* target)
|
||||||
|
{
|
||||||
|
if (!target)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool const onDifferentTransports =
|
||||||
|
bot->m_movementInfo.transport.guid != target->m_movementInfo.transport.guid;
|
||||||
|
if (!onDifferentTransports)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!ServerFacade::instance().IsDistanceLessOrEqualThan(
|
||||||
|
ServerFacade::instance().GetDistance2d(bot, target),
|
||||||
|
sPlayerbotAIConfig.sightDistance))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bot->StopMoving();
|
||||||
|
|
||||||
|
// Disembark from our current transport (if any) before relocating.
|
||||||
|
if (Transport* myTransport = bot->GetTransport())
|
||||||
|
myTransport->RemovePassenger(bot);
|
||||||
|
|
||||||
|
// NearTeleportTo is the AC equivalent of cmangos's Relocate+
|
||||||
|
// SendHeartBeat sequence: it relocates the bot AND broadcasts the
|
||||||
|
// movement update so server-side state stays consistent.
|
||||||
|
bot->NearTeleportTo(target->GetPositionX(),
|
||||||
|
target->GetPositionY(),
|
||||||
|
target->GetPositionZ(),
|
||||||
|
bot->GetOrientation());
|
||||||
|
|
||||||
|
// Board target's transport (if any).
|
||||||
|
if (Transport* hisTransport = target->GetTransport())
|
||||||
|
hisTransport->AddPassenger(bot);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool MovementAction::ChaseTo(WorldObject* obj, float distance)
|
bool MovementAction::ChaseTo(WorldObject* obj, float distance)
|
||||||
{
|
{
|
||||||
if (!IsMovingAllowed())
|
if (!IsMovingAllowed())
|
||||||
@ -2530,7 +2576,7 @@ TravelPath MovementAction::ResolveMovePath(WorldPosition startPos,
|
|||||||
|
|
||||||
if (needsLongPath && !sTravelNodeMap.getNodes().empty() && !bot->InBattleground())
|
if (needsLongPath && !sTravelNodeMap.getNodes().empty() && !bot->InBattleground())
|
||||||
{
|
{
|
||||||
out = sTravelNodeMap.GetFullPath(startPos, bot->GetZoneId(), endPos, bot);
|
out = sTravelNodeMap.GetFullPath(startPos, endPos, bot);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@ -111,6 +111,14 @@ protected:
|
|||||||
float GetFollowAngle();
|
float GetFollowAngle();
|
||||||
bool Follow(Unit* target, float distance = sPlayerbotAIConfig.followDistance);
|
bool Follow(Unit* target, float distance = sPlayerbotAIConfig.followDistance);
|
||||||
bool Follow(Unit* target, float distance, float angle);
|
bool Follow(Unit* target, float distance, float angle);
|
||||||
|
// Handles the cross-transport follow case: when bot and target are
|
||||||
|
// on different transports (or one is off-transport) and within
|
||||||
|
// sight, this disembarks the bot from its current transport (if
|
||||||
|
// any), teleports it to the target's position, and boards the
|
||||||
|
// target's transport (if any). Returns true if the transport
|
||||||
|
// transition was performed this tick (caller should skip the
|
||||||
|
// engine-level follow for this tick).
|
||||||
|
bool FollowOnTransport(Unit* target);
|
||||||
bool ChaseTo(WorldObject* obj, float distance = 0.0f);
|
bool ChaseTo(WorldObject* obj, float distance = 0.0f);
|
||||||
bool ReachCombatTo(Unit* target, float distance = 0.0f);
|
bool ReachCombatTo(Unit* target, float distance = 0.0f);
|
||||||
float MoveDelay(float distance, bool backwards = false);
|
float MoveDelay(float distance, bool backwards = false);
|
||||||
|
|||||||
@ -1512,7 +1512,7 @@ TravelNodeRoute TravelNodeMap::FindRouteNearestNodes(WorldPosition startPos, Wor
|
|||||||
return TravelNodeRoute();
|
return TravelNodeRoute();
|
||||||
}
|
}
|
||||||
|
|
||||||
TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos, [[maybe_unused]] uint32 botZoneId,
|
TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos,
|
||||||
WorldPosition destination, Unit* bot)
|
WorldPosition destination, Unit* bot)
|
||||||
{
|
{
|
||||||
TravelPath path;
|
TravelPath path;
|
||||||
|
|||||||
@ -714,7 +714,7 @@ public:
|
|||||||
// Resolve a full TravelPath from botPos to destination. Returns an
|
// Resolve a full TravelPath from botPos to destination. Returns an
|
||||||
// empty TravelPath if no graph route + mmap stitch is reachable;
|
// empty TravelPath if no graph route + mmap stitch is reachable;
|
||||||
// the caller is then expected to fall back to a single-point path.
|
// the caller is then expected to fall back to a single-point path.
|
||||||
TravelPath GetFullPath(WorldPosition botPos, uint32 botZoneId,
|
TravelPath GetFullPath(WorldPosition botPos,
|
||||||
WorldPosition destination, Unit* bot = nullptr);
|
WorldPosition destination, Unit* bot = nullptr);
|
||||||
|
|
||||||
// Resolve A* route between two world positions (returns node vector)
|
// Resolve A* route between two world positions (returns node vector)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user