fix(Core/Movement): Prevent Follow predictions from clipping through walls and add teleport as last resort (#24287)

This commit is contained in:
Gultask
2026-02-06 18:08:27 -03:00
committed by GitHub
parent cda6e619d0
commit c09ee1ddf3
2 changed files with 48 additions and 7 deletions
@@ -461,8 +461,7 @@ static Optional<float> GetVelocity(Unit* owner, Unit* target, G3D::Vector3 const
static Position const PredictPosition(Unit* target) static Position const PredictPosition(Unit* target)
{ {
Position pos = target->GetPosition(); Position pos = target->GetPosition();
// 0.5 - it's time (0.5 sec) between starting movement opcode (e.g. MSG_MOVE_START_FORWARD) and MSG_MOVE_HEARTBEAT sent by client
// 0.5 - it's time (0.5 sec) between starting movement opcode (e.g. MSG_MOVE_START_FORWARD) and MSG_MOVE_HEARTBEAT sent by client
float speed = target->GetSpeed(Movement::SelectSpeedType(target->GetUnitMovementFlags())) * 0.5f; float speed = target->GetSpeed(Movement::SelectSpeedType(target->GetUnitMovementFlags())) * 0.5f;
float orientation = target->GetOrientation(); float orientation = target->GetOrientation();
@@ -491,6 +490,20 @@ static Position const PredictPosition(Unit* target)
return pos; return pos;
} }
static bool IsValidPredictedPosition(Unit* target, Position const& predicted)
{
Position current = target->GetPosition();
if (current.GetExactDist2d(&predicted) > 15.0f)
return false;
// Check line of sight from current to predicted to avoid clipping through geometry
if (!target->IsWithinLOS(predicted.GetPositionX(), predicted.GetPositionY(), predicted.GetPositionZ()))
return false;
return true;
}
template<class T> template<class T>
bool FollowMovementGenerator<T>::PositionOkay(Unit* target, bool isPlayerPet, bool& targetIsMoving, uint32 diff) bool FollowMovementGenerator<T>::PositionOkay(Unit* target, bool isPlayerPet, bool& targetIsMoving, uint32 diff)
{ {
@@ -571,7 +584,10 @@ bool FollowMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff)
; // closes "bool forceDest", that way it is more appropriate, so we can comment out crap whenever we need to ; // closes "bool forceDest", that way it is more appropriate, so we can comment out crap whenever we need to
bool targetIsMoving = false; bool targetIsMoving = false;
if (PositionOkay(target, owner->IsGuardian() && target->IsPlayer(), targetIsMoving, time_diff)) bool isPlayerPet = owner->IsGuardian() && target->IsPlayer();
bool isFollowingPlayer = target->IsPlayer();
if (PositionOkay(target, isPlayerPet, targetIsMoving, time_diff))
{ {
if (owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE) && owner->movespline->Finalized()) if (owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE) && owner->movespline->Finalized())
{ {
@@ -599,14 +615,23 @@ bool FollowMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff)
if (_lastPredictedPosition && _lastPredictedPosition->GetExactDistSq(&predictedPosition) < 0.25f) if (_lastPredictedPosition && _lastPredictedPosition->GetExactDistSq(&predictedPosition) < 0.25f)
return true; return true;
_lastPredictedPosition = predictedPosition; if (IsValidPredictedPosition(target, predictedPosition))
targetPosition = predictedPosition; {
i_recheckPredictedDistance = true; _lastPredictedPosition = predictedPosition;
targetPosition = predictedPosition;
i_recheckPredictedDistance = true;
}
else
{
_lastPredictedPosition.reset();
i_recheckPredictedDistance = false;
}
} }
else else
{ {
i_recheckPredictedDistance = false; i_recheckPredictedDistance = false;
i_recheckPredictedDistanceTimer.Reset(0); i_recheckPredictedDistanceTimer.Reset(0);
_lastPredictedPosition.reset();
} }
if (!i_path) if (!i_path)
@@ -628,6 +653,22 @@ bool FollowMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff)
if (!owner->IsStopped()) if (!owner->IsStopped())
owner->StopMoving(); owner->StopMoving();
// Teleport if stuck and too far away
if (cOwner && isFollowingPlayer)
{
float distance = owner->GetDistance(target);
if (distance > 20.f)
{
float teleX;
float teleY;
float teleZ;
target->GetClosePoint(teleX, teleY, teleZ, owner->GetCombatReach());
owner->NearTeleportTo(teleX, teleY, teleZ, target->GetOrientation());
_lastTargetPosition.reset();
_lastPredictedPosition.reset();
}
}
return true; return true;
} }
@@ -91,7 +91,7 @@ class FollowMovementGenerator : public MovementGeneratorMedium<T, FollowMovement
{ {
public: public:
FollowMovementGenerator(Unit* target, float range, ChaseAngle angle, bool inheritWalkState, bool inheritSpeed) FollowMovementGenerator(Unit* target, float range, ChaseAngle angle, bool inheritWalkState, bool inheritSpeed)
: TargetedMovementGeneratorBase(target), i_path(nullptr), i_recheckPredictedDistanceTimer(0), i_recheckPredictedDistance(false), _range(range), _angle(angle),_inheritWalkState(inheritWalkState), _inheritSpeed(inheritSpeed) {} : TargetedMovementGeneratorBase(target), i_path(nullptr), i_recheckPredictedDistanceTimer(0), i_recheckPredictedDistance(false), _range(range), _angle(angle), _inheritWalkState(inheritWalkState), _inheritSpeed(inheritSpeed) {}
~FollowMovementGenerator() { } ~FollowMovementGenerator() { }
MovementGeneratorType GetMovementGeneratorType() { return FOLLOW_MOTION_TYPE; } MovementGeneratorType GetMovementGeneratorType() { return FOLLOW_MOTION_TYPE; }