fix(Core/Movement): prevent false MovementInform on gossip pause (#25428)

This commit is contained in:
sogladev 2026-04-12 22:55:54 +02:00 committed by GitHub
parent c839ddf829
commit 49b3926f6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 160 additions and 37 deletions

View file

@ -3091,6 +3091,21 @@ CreatureMovementData const& Creature::GetMovementTemplate() const
return GetCreatureTemplate()->Movement;
}
void Creature::PauseMovementForInteraction()
{
if (uint32 pause = GetMovementTemplate().GetInteractionPauseTimer())
{
uint8 pauseSlot = MOTION_SLOT_IDLE;
if (GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == POINT_MOTION_TYPE)
pauseSlot = MOTION_SLOT_ACTIVE;
else if (GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == POINT_MOTION_TYPE)
pauseSlot = MOTION_SLOT_CONTROLLED;
PauseMovement(pause, pauseSlot);
}
}
void Creature::AllLootRemovedFromCorpse()
{
if (loot.loot_type != LOOT_SKINNING && !IsPet() && GetCreatureTemplate()->SkinLootId && hasLootRecipient())

View file

@ -80,6 +80,7 @@ public:
[[nodiscard]] bool IsTrigger() const { return HasFlagsExtra(CREATURE_FLAG_EXTRA_TRIGGER); }
[[nodiscard]] bool IsGuard() const { return HasFlagsExtra(CREATURE_FLAG_EXTRA_GUARD); }
CreatureMovementData const& GetMovementTemplate() const;
void PauseMovementForInteraction();
[[nodiscard]] bool CanWalk() const { return GetMovementTemplate().IsGroundAllowed(); }
[[nodiscard]] bool CanSwim() const override;
[[nodiscard]] bool CanEnterWater() const override;

View file

@ -47,9 +47,7 @@ void WorldSession::HandleBattlemasterHelloOpcode(WorldPacket& recvData)
if (!unit->IsBattleMaster()) // it's not battlemaster
return;
// Stop the npc if moving
if (uint32 pause = unit->GetMovementTemplate().GetInteractionPauseTimer())
unit->PauseMovement(pause);
unit->PauseMovementForInteraction();
unit->SetHomePosition(unit->GetPosition());
BattlegroundTypeId bgTypeId = sBattlegroundMgr->GetBattleMasterBG(unit->GetEntry());

View file

@ -871,9 +871,7 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid, uint32 vendorEntry)
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
}
// Stop the npc if moving
if (uint32 pause = vendor->GetMovementTemplate().GetInteractionPauseTimer())
vendor->PauseMovement(pause);
vendor->PauseMovementForInteraction();
// Update home position for patrolling NPCs only (prevents drift for stationary NPCs)
if (vendor->GetDefaultMovementType() == WAYPOINT_MOTION_TYPE ||

View file

@ -110,6 +110,8 @@ void WorldSession::SendTrainerList(Creature* npc)
return;
}
npc->PauseMovementForInteraction();
trainer->SendSpells(npc, _player, GetSessionDbLocaleIndex());
}
@ -165,9 +167,7 @@ void WorldSession::HandleGossipHelloOpcode(WorldPacket& recvData)
//if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
// GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
// Stop the npc if moving
if (uint32 pause = unit->GetMovementTemplate().GetInteractionPauseTimer())
unit->PauseMovement(pause);
unit->PauseMovementForInteraction();
// Update home position for patrolling NPCs only (prevents drift for stationary NPCs)
if (unit->GetDefaultMovementType() == WAYPOINT_MOTION_TYPE ||

View file

@ -93,9 +93,7 @@ void WorldSession::HandleQuestgiverHelloOpcode(WorldPacket& recvData)
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
// Stop the npc if moving
if (uint32 pause = creature->GetMovementTemplate().GetInteractionPauseTimer())
creature->PauseMovement(pause);
creature->PauseMovementForInteraction();
// Update home position for patrolling NPCs only (prevents drift for stationary NPCs)
if (creature->GetDefaultMovementType() == WAYPOINT_MOTION_TYPE ||

View file

@ -23,11 +23,16 @@
#include "ObjectAccessor.h"
#include "Player.h"
#include "World.h"
#include <limits>
//----- Point Movement Generator
template<class T>
void PointMovementGenerator<T>::DoInitialize(T* unit)
{
_stalled = false;
_hasBeenStalled = false;
_pauseTime.reset();
if (unit->HasUnitState(UNIT_STATE_NOT_MOVE) || unit->IsMovementPreventedByCasting())
{
// the next line is to ensure that a new spline is created in DoUpdate() once the unit is no longer rooted/stunned
@ -101,7 +106,7 @@ void PointMovementGenerator<T>::DoInitialize(T* unit)
}
template<class T>
bool PointMovementGenerator<T>::DoUpdate(T* unit, uint32 /*diff*/)
bool PointMovementGenerator<T>::DoUpdate(T* unit, uint32 diff)
{
if (!unit)
return false;
@ -115,38 +120,102 @@ bool PointMovementGenerator<T>::DoUpdate(T* unit, uint32 /*diff*/)
if (unit->HasUnitState(UNIT_STATE_NOT_MOVE))
{
if (!unit->HasUnitState(UNIT_STATE_CHARGING))
{
unit->StopMoving();
}
return true;
}
unit->AddUnitState(UNIT_STATE_ROAMING_MOVE);
if (id != EVENT_CHARGE_PREPATH && i_recalculateSpeed && !unit->movespline->Finalized())
if (_pauseTime.has_value())
{
if (diff >= static_cast<uint32>(_pauseTime.value()))
_pauseTime.reset();
else
{
_pauseTime = static_cast<int32>(_pauseTime.value() - diff);
return true;
}
_hasBeenStalled = false;
_stalled = false;
i_recalculateSpeed = true;
}
// Relaunch path when speed changed or when resuming from a stall.
// Keep an indefinitely paused movement stalled until Resume() clears _stalled.
if (id != EVENT_CHARGE_PREPATH && !_stalled && (i_recalculateSpeed || _hasBeenStalled))
{
i_recalculateSpeed = false;
Movement::MoveSplineInit init(unit);
auto rebasePrecomputedPath = [this, unit](std::optional<uint32> offset = std::nullopt)
{
Movement::PointsArray rebasedPath;
G3D::Vector3 currentPos(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ());
if (m_precomputedPath.empty())
return rebasedPath;
if (offset.has_value())
{
if (offset.value() >= m_precomputedPath.size())
{
rebasedPath.push_back(currentPos);
rebasedPath.push_back(m_precomputedPath.back());
return rebasedPath;
}
rebasedPath.insert(rebasedPath.end(), m_precomputedPath.begin() + offset.value(), m_precomputedPath.end());
}
else
{
uint32 closestPointIndex = 0;
float closestPointDist = std::numeric_limits<float>::max();
for (uint32 pointIndex = 1; pointIndex < m_precomputedPath.size(); ++pointIndex)
{
float const sqDist = (currentPos - m_precomputedPath[pointIndex]).squaredLength();
if (sqDist < closestPointDist)
{
closestPointDist = sqDist;
closestPointIndex = pointIndex;
}
}
rebasedPath.insert(rebasedPath.end(), m_precomputedPath.begin() + closestPointIndex, m_precomputedPath.end());
}
// MovebyPath requires the first point to be the mover's current position.
rebasedPath.insert(rebasedPath.begin(), currentPos);
return rebasedPath;
};
// xinef: speed changed during path execution, calculate remaining path and launch it once more
if (m_precomputedPath.size())
{
uint32 offset = std::min(uint32(unit->movespline->_currentSplineIdx()), uint32(m_precomputedPath.size()));
Movement::PointsArray::iterator offsetItr = m_precomputedPath.begin();
std::advance(offsetItr, offset);
m_precomputedPath.erase(m_precomputedPath.begin(), offsetItr);
if (!unit->movespline->Finalized())
{
uint32 offset = std::min(uint32(unit->movespline->_currentSplineIdx()), uint32(m_precomputedPath.size()));
m_precomputedPath = rebasePrecomputedPath(offset);
// restore 0 element (current position)
m_precomputedPath.insert(m_precomputedPath.begin(), G3D::Vector3(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ()));
if (m_precomputedPath.size() > 2)
init.MovebyPath(m_precomputedPath);
else if (m_precomputedPath.size() == 2)
init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z, true);
}
else
{
// Unit was stopped (finalized) due to Pause/StopMoving; trim path from current position.
m_precomputedPath = rebasePrecomputedPath();
if (m_precomputedPath.size() > 2)
init.MovebyPath(m_precomputedPath);
else if (m_precomputedPath.size() == 2)
init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z, true);
if (m_precomputedPath.size() > 2)
init.MovebyPath(m_precomputedPath);
else if (m_precomputedPath.size() == 2)
init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z, true);
else
init.MoveTo(i_x, i_y, i_z, true);
}
}
else
init.MoveTo(i_x, i_y, i_z, true);
if (speed > 0.0f) // Default value for point motion type is 0.0, if 0.0 spline will use GetSpeed on unit
init.SetVelocity(speed);
@ -159,14 +228,21 @@ bool PointMovementGenerator<T>::DoUpdate(T* unit, uint32 /*diff*/)
init.SetAnimation(*_animTier);
if (i_orientation > 0.0f)
{
init.SetFacing(i_orientation);
}
init.Launch();
}
return !unit->movespline->Finalized();
// If finalized but we were stalled (paused) keep generator active so Finalize() won't be called
if (unit->movespline->Finalized())
{
if (_hasBeenStalled)
return true;
return false;
}
return true;
}
template<class T>
@ -186,10 +262,35 @@ void PointMovementGenerator<T>::DoFinalize(T* unit)
}
}
if (unit->movespline->Finalized())
// Only inform AI if this is a real arrival, not a finalize caused by a Pause/Stop
if (unit->movespline->Finalized() && !_hasBeenStalled)
MovementInform(unit);
}
template<class T>
void PointMovementGenerator<T>::Pause(uint32 timer)
{
_stalled = timer ? false : true;
_hasBeenStalled = true;
if (timer)
_pauseTime = static_cast<int32>(timer);
else
_pauseTime.reset();
}
template<class T>
void PointMovementGenerator<T>::Resume(uint32 overrideTimer)
{
_hasBeenStalled = false;
_stalled = false;
if (overrideTimer)
_pauseTime = static_cast<int32>(overrideTimer);
else
_pauseTime.reset();
i_recalculateSpeed = true;
}
template<class T>
void PointMovementGenerator<T>::DoReset(T* unit)
{
@ -236,6 +337,11 @@ template void PointMovementGenerator<Creature>::DoReset(Creature*);
template bool PointMovementGenerator<Player>::DoUpdate(Player*, uint32);
template bool PointMovementGenerator<Creature>::DoUpdate(Creature*, uint32);
template void PointMovementGenerator<Player>::Pause(uint32);
template void PointMovementGenerator<Creature>::Pause(uint32);
template void PointMovementGenerator<Player>::Resume(uint32);
template void PointMovementGenerator<Creature>::Resume(uint32);
void AssistanceMovementGenerator::Finalize(Unit* unit)
{
unit->ToCreature()->SetNoCallAssistance(false);

View file

@ -39,13 +39,17 @@ public:
void DoInitialize(T*);
void DoFinalize(T*);
void DoReset(T*);
bool DoUpdate(T*, uint32);
void MovementInform(T*);
void unitSpeedChanged() { i_recalculateSpeed = true; }
void Pause(uint32 timer = 0) override;
void Resume(uint32 overrideTimer = 0) override;
MovementGeneratorType GetMovementGeneratorType() { return POINT_MOTION_TYPE; }
void unitSpeedChanged() override { i_recalculateSpeed = true; }
MovementGeneratorType GetMovementGeneratorType() override { return POINT_MOTION_TYPE; }
bool GetDestination(float& x, float& y, float& z) const { x = i_x; y = i_y; z = i_z; return true; }
private:
@ -53,7 +57,7 @@ private:
float i_x, i_y, i_z;
float speed;
float i_orientation;
bool i_recalculateSpeed;
bool i_recalculateSpeed{};
Movement::PointsArray m_precomputedPath;
bool _generatePath;
bool _forceDestination;
@ -62,6 +66,9 @@ private:
ForcedMovement _forcedMovement;
ObjectGuid _facingTargetGuid;
std::optional<AnimTier> _animTier;
bool _stalled{};
bool _hasBeenStalled{};
std::optional<int32> _pauseTime;
};
class AssistanceMovementGenerator : public PointMovementGenerator<Creature>
@ -70,8 +77,8 @@ public:
AssistanceMovementGenerator(float _x, float _y, float _z) :
PointMovementGenerator<Creature>(0, _x, _y, _z, FORCED_MOVEMENT_NONE) {}
MovementGeneratorType GetMovementGeneratorType() { return ASSISTANCE_MOTION_TYPE; }
void Finalize(Unit*);
MovementGeneratorType GetMovementGeneratorType() override { return ASSISTANCE_MOTION_TYPE; }
void Finalize(Unit*) override;
};
// Does almost nothing - just doesn't allows previous movegen interrupt current effect.