diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 04cda14ad..c55af0698 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -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()) diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 7e86608af..c0eb2a753 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -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; diff --git a/src/server/game/Handlers/BattleGroundHandler.cpp b/src/server/game/Handlers/BattleGroundHandler.cpp index dba88af1b..499bae1e6 100644 --- a/src/server/game/Handlers/BattleGroundHandler.cpp +++ b/src/server/game/Handlers/BattleGroundHandler.cpp @@ -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()); diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp index c2cc245b6..93430dbe9 100644 --- a/src/server/game/Handlers/ItemHandler.cpp +++ b/src/server/game/Handlers/ItemHandler.cpp @@ -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 || diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index 414ec5527..57d560807 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -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 || diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp index 33c4aeeda..a2c775328 100644 --- a/src/server/game/Handlers/QuestHandler.cpp +++ b/src/server/game/Handlers/QuestHandler.cpp @@ -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 || diff --git a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp index 66ba79be5..d1e79d0b3 100644 --- a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp @@ -23,11 +23,16 @@ #include "ObjectAccessor.h" #include "Player.h" #include "World.h" +#include //----- Point Movement Generator template void PointMovementGenerator::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::DoInitialize(T* unit) } template -bool PointMovementGenerator::DoUpdate(T* unit, uint32 /*diff*/) +bool PointMovementGenerator::DoUpdate(T* unit, uint32 diff) { if (!unit) return false; @@ -115,38 +120,102 @@ bool PointMovementGenerator::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(_pauseTime.value())) + _pauseTime.reset(); + else + { + _pauseTime = static_cast(_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 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::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::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 @@ -186,10 +262,35 @@ void PointMovementGenerator::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 +void PointMovementGenerator::Pause(uint32 timer) +{ + _stalled = timer ? false : true; + _hasBeenStalled = true; + if (timer) + _pauseTime = static_cast(timer); + else + _pauseTime.reset(); +} + +template +void PointMovementGenerator::Resume(uint32 overrideTimer) +{ + _hasBeenStalled = false; + _stalled = false; + if (overrideTimer) + _pauseTime = static_cast(overrideTimer); + else + _pauseTime.reset(); + + i_recalculateSpeed = true; +} + template void PointMovementGenerator::DoReset(T* unit) { @@ -236,6 +337,11 @@ template void PointMovementGenerator::DoReset(Creature*); template bool PointMovementGenerator::DoUpdate(Player*, uint32); template bool PointMovementGenerator::DoUpdate(Creature*, uint32); +template void PointMovementGenerator::Pause(uint32); +template void PointMovementGenerator::Pause(uint32); +template void PointMovementGenerator::Resume(uint32); +template void PointMovementGenerator::Resume(uint32); + void AssistanceMovementGenerator::Finalize(Unit* unit) { unit->ToCreature()->SetNoCallAssistance(false); diff --git a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.h b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.h index b4b782bfd..b30db5ed2 100644 --- a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.h @@ -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; + bool _stalled{}; + bool _hasBeenStalled{}; + std::optional _pauseTime; }; class AssistanceMovementGenerator : public PointMovementGenerator @@ -70,8 +77,8 @@ public: AssistanceMovementGenerator(float _x, float _y, float _z) : PointMovementGenerator(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.