From 08401431370e40c15e194133abc31b419ebe00a6 Mon Sep 17 00:00:00 2001 From: Ovahlord Date: Thu, 24 Aug 2023 20:16:41 +0200 Subject: [PATCH] Core/Movement: reworked chase movement * the destination prediction is now much more reliable * the movement generator will now more frequently check for valid positions to match retail behavior * the catch-up mechanic will now adapt its speed to the target's one when being close enough to hit it * temporarily disabled backwards movement when being too close to a target to avoid some edge cases that need additional research * fixed the pathfinding reaching behavior so that they no longer follow you to unreachable obstacles and return home when they can't reach you anymore closes #345 closes #260 --- src/server/game/AI/CoreAI/PetAI.cpp | 7 +- .../game/AI/ScriptedAI/ScriptedCreature.cpp | 2 +- .../game/AI/ScriptedAI/ScriptedCreature.h | 2 +- .../game/AI/SmartScripts/SmartScript.cpp | 5 +- src/server/game/Movement/MotionMaster.cpp | 2 +- src/server/game/Movement/MotionMaster.h | 3 +- .../ChaseMovementGenerator.cpp | 271 +++++++++++------- .../ChaseMovementGenerator.h | 30 +- .../GrimBatol/boss_drahga_shadowburner.cpp | 2 +- 9 files changed, 204 insertions(+), 120 deletions(-) diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index 00176203d2..8416b5300a 100644 --- a/src/server/game/AI/CoreAI/PetAI.cpp +++ b/src/server/game/AI/CoreAI/PetAI.cpp @@ -485,9 +485,10 @@ void PetAI::DoAttack(Unit* target, bool chase) float chaseDistance = me->GetPetChaseDistance(); - // Pets with ranged attacks should not care about the chase angle at all. - ChaseAngle angle = ChaseAngle(chaseDistance == 0.f ? float(M_PI) : 0.f, chaseDistance == 0.f ? float(M_PI_4) : float(M_PI * 2)); - me->GetMotionMaster()->MoveChase(target, chaseDistance, angle); + if (chaseDistance == 0.f) // @todo: the chase angle of pets is relative to the angle between target and owner so it always faces away from the owner + me->GetMotionMaster()->MoveChase(target/*, std::nullopt, ChaseAngle(float(M_PI), float(M_PI_4)) */); + else + me->GetMotionMaster()->MoveChase(target, chaseDistance); } else // (Stay && ((Aggressive || Defensive) && In Melee Range))) { diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index 7167c79ac7..df85e6be9e 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -156,7 +156,7 @@ void ScriptedAI::UpdateAI(uint32 /*diff*/) DoMeleeAttackIfReady(); } -void ScriptedAI::DoStartMovement(Unit* victim, float distance, float angle) +void ScriptedAI::DoStartMovement(Unit* victim, Optional distance /*= std::nullopt*/, Optional angle /*= std::nullopt*/) { if (victim) me->GetMotionMaster()->MoveChase(victim, distance, angle); diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h index 4d2c0837ed..fccf04119d 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -162,7 +162,7 @@ struct TC_GAME_API ScriptedAI : public CreatureAI // ************* //Start movement toward victim - void DoStartMovement(Unit* target, float distance = 0.0f, float angle = 0.0f); + void DoStartMovement(Unit* target, Optional distance = std::nullopt, Optional angle = std::nullopt); //Start no movement on victim void DoStartNoMovement(Unit* target); diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index 48e96f65c9..454ed07851 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -1669,12 +1669,15 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u float attackDistance = float(e.action.setRangedMovement.distance); float attackAngle = float(e.action.setRangedMovement.angle) / 180.0f * float(M_PI); + Optional angle; + if (attackAngle != 0.f) + angle = ChaseAngle(attackAngle); for (WorldObject* target : targets) if (Creature* creature = target->ToCreature()) if (IsSmart(creature) && creature->GetVictim()) if (ENSURE_AI(SmartAI, creature->AI())->CanCombatMove()) - creature->GetMotionMaster()->MoveChase(creature->GetVictim(), attackDistance, attackAngle); + creature->GetMotionMaster()->MoveChase(creature->GetVictim(), attackDistance, angle); break; } diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index cc1368f9e6..40476c5597 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -268,7 +268,7 @@ void MotionMaster::MoveFollow(Unit* target, float dist, float angle, bool joinFo Mutate(new FollowMovementGenerator(target, dist, angle, joinFormation, catchUpToTarget, faceTarget), slot); } -void MotionMaster::MoveChase(Unit* target, float dist, Optional angle) +void MotionMaster::MoveChase(Unit* target, Optional dist /*= std::nullopt*/, Optional angle /*= std::nullopt*/) { // ignore movement request if target not exist if (!target || target == _owner) diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h index 406501a5ae..e42b68f4ec 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -150,8 +150,7 @@ class TC_GAME_API MotionMaster void MoveRandom(float wanderDistance = 0.0f); void MoveFollow(Unit* target, float dist, float angle, bool joinFormation = false, bool catchUpToTarget = false, bool faceTarget = false, MovementSlot slot = MOTION_SLOT_IDLE); - void MoveChase(Unit* target, float dist = 0.f, Optional angle = {}); - void MoveChase(Unit* target, float dist, float angle) { MoveChase(target, dist, ChaseAngle(angle)); } + void MoveChase(Unit* target, Optional dist = std::nullopt, Optional angle = std::nullopt); void MoveConfused(); void MoveFleeing(Unit* enemy, uint32 time = 0); diff --git a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.cpp index 13dab69aca..4655fcad97 100644 --- a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.cpp @@ -27,6 +27,7 @@ #include "Util.h" #include "Vehicle.h" +/* static bool IsMutualChase(Unit const* owner, Unit const* target) { MovementGenerator* gen = target->GetMotionMaster()->topOrNull(); @@ -35,39 +36,38 @@ static bool IsMutualChase(Unit const* owner, Unit const* target) return (static_cast(gen)->GetTarget() == owner); } +*/ -static void DoMovementInform(Unit* owner, Unit const* target) +enum ChaseMovementPositionCheckResult : uint8 { - if (owner->GetTypeId() != TYPEID_UNIT) + CHASE_POSITION_CHECK_RESULT_VALID = 0x0, + CHASE_POSITION_CHECK_RESULT_TOO_FAR = 0x1, + CHASE_POSITION_CHECK_RESULT_TOO_CLOSE = 0x2, + CHASE_POSITION_CHECK_RESULT_ANGLE_INVALID = 0x4, + CHASE_POSITION_CHECK_RESULT_NOT_IN_LOS = 0x8, +}; + +enum ChasePositionCheckOptions : uint8 +{ + CHASE_POSITION_CHECK_NONE = 0x0, // everything's fine + CHASE_POSITION_CHECK_DISTANCE = 0x1, // the target is too far away + CHASE_POSITION_CHECK_BOUNDARY = 0x2, // the target is too close + CHASE_POSITION_CHECK_ANGLE = 0x4, // our angle towards the target is out of bounds + CHASE_POSITION_CHECK_LOS = 0x8 // our target is not in line of sight +}; + +inline void DoMovementInform(Unit* owner, Unit const* target) +{ + if (owner->IsCreature()) return; if (CreatureAI* AI = owner->ToCreature()->AI()) AI->MovementInform(CHASE_MOTION_TYPE, target->GetGUID().GetCounter()); } -static bool PositionOkay(Unit const* owner, Unit const* target, float distance, Optional angle) -{ - float const dist = owner->GetExactDist2d(target); - - // owner's distance to its chase target is outside of its range - if (dist > distance) - return false; - - // owner's relative angle to its target is not within boundaries - if (angle && !angle->IsAngleOkay(target->GetRelativeAngle(owner))) - return false; - - // owner cannot see its target - if (!owner->IsWithinLOSInMap(target)) - return false; - - return true; -} - -inline float GetChaseRange(Unit const* owner, Unit const* target) +inline float GetHitboxSum(Unit const* owner, Unit const* target) { float hitboxSum = owner->GetCombatReach() + target->GetCombatReach(); - float hoverDelta = owner->GetHoverOffset() - target->GetHoverOffset(); if (hoverDelta != 0.0f) return std::sqrt(std::max(hitboxSum * hitboxSum - hoverDelta * hoverDelta, 0.0f)); @@ -75,16 +75,25 @@ inline float GetChaseRange(Unit const* owner, Unit const* target) return hitboxSum; } -ChaseMovementGenerator::ChaseMovementGenerator(Unit* target, float range, Optional angle) : AbstractPursuer(PursuingType::Chase, ASSERT_NOTNULL(target)), _range(range), _angle(angle) { } -ChaseMovementGenerator::~ChaseMovementGenerator() = default; +inline float SelectMovementSpeed(Unit const* target) +{ + UnitMoveType moveType = MOVE_RUN; + if (target->IsFlying()) + moveType = MOVE_FLIGHT; + + return target->GetSpeed(moveType); +} + +ChaseMovementGenerator::ChaseMovementGenerator(Unit* target, Optional range /*= {}*/, Optional angle /*= {}*/) : + AbstractPursuer(PursuingType::Chase, ASSERT_NOTNULL(target)), + _range(range), _angle(angle), _previousChaseSplineId(0), _moveTimer(0), _positionCheckTimer(0) { } void ChaseMovementGenerator::Initialize(Unit* owner) { owner->AddUnitState(UNIT_STATE_CHASE); owner->SetWalk(false); - _lastTargetPosition.reset(); - _nextMovementTimer.Reset(0); - _nextRepositioningTimer.Reset(0); + _pathGenerator = std::make_unique(owner); + _moveTimer.Reset(0); } bool ChaseMovementGenerator::Update(Unit* owner, uint32 diff) @@ -93,40 +102,40 @@ bool ChaseMovementGenerator::Update(Unit* owner, uint32 diff) if (!owner || !owner->IsAlive()) return false; + // validate our target Unit* target = GetTarget(); - - // our target might have gone away - if (!target || !target->IsInWorld()) + if (!target || !target->IsInWorld() || owner->GetVictim() != target) return false; - // the owner might've selected a different target (feels like we shouldn't check this here...) - if (owner->GetVictim() != target) - return false; + _moveTimer.Update(diff); + _positionCheckTimer.Update(diff); + + // ------------------------ This segment will handle the clearing of the chase state and the movement inform of the previous updtae cycle + + // Make sure that the spline that we are moving along right now is our current chase spline + if (owner->movespline->Finalized() || owner->movespline->GetId() != _previousChaseSplineId) + { + if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE)) + { + owner->ClearUnitState(UNIT_STATE_CHASE_MOVE); + // trigger movement inform if we have just reached our target + if (owner->movespline->GetId() == _previousChaseSplineId) + DoMovementInform(owner, target); + } + } + + // ------------------------ Next up we will perform somce basic checks that prevent the chasing unit to move // the owner might be unable to move (rooted or casting), pause movement if (owner->HasUnitState(UNIT_STATE_NOT_MOVE) || owner->IsMovementPreventedByCasting()) { owner->StopMoving(); - _lastTargetPosition.reset(); - if (Creature* cOwner = owner->ToCreature()) - cOwner->SetCannotReachTarget(false); return true; } - // We are done moving. Trigger movement inform hook and clear chase move state - if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && owner->movespline->Finalized()) - { - if (Creature* cOwner = owner->ToCreature()) - cOwner->SetCannotReachTarget(false); - - owner->ClearUnitState(UNIT_STATE_CHASE_MOVE); - DoMovementInform(owner, target); - return true; - } - - // Owner cannot reach target (example: target is in water and owner cannot swim) Creature* creature = owner->ToCreature(); - if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && creature) + // check if the chase target is in an accessible place + if (creature) { if (!target->isInAccessiblePlaceFor(creature)) { @@ -135,58 +144,50 @@ bool ChaseMovementGenerator::Update(Unit* owner, uint32 diff) return true; } } - else if (creature && !target->isInAccessiblePlaceFor(creature)) + + // ------------------------ Now we can finally perform the movement actions + + if (_positionCheckTimer.Passed()) { - creature->SetCannotReachTarget(true); - return true; - } + _positionCheckTimer.Reset(POSITION_CHECK_INTERVAL); - bool const mutualChase = IsMutualChase(owner, target); - float const chaseRange = GetChaseRange(owner, target); - float const rangeTolerance = _range > 0.f ? _range : chaseRange; - Optional chaseAngle = mutualChase ? Optional() : _angle; + // Check the target angle and update the destination + Position destination = target->GetVehicle() ? target->GetVehicle()->GetBase()->GetPosition() : target->GetPosition(); + uint8 result = checkPosition(CHASE_POSITION_CHECK_ANGLE, owner, target); - // Update Movement - _nextMovementTimer.Update(diff); - _nextRepositioningTimer.Update(diff); - - // Handle repositioning and scattering arround target - if (_nextRepositioningTimer.Passed()) - { - _nextRepositioningTimer.Reset(REPOSITION_MOVEMENT_INTERVAL); - - if (!owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && owner->IsCreature() && !target->isMoving()) + // The target is still moving so we move our chase destination ahead of the target to keep up + if (target->isMoving()) { - // Owner is too close to its target. Step back. - if (owner->GetBoundaryRadius() <= 5.f && owner->GetExactDist2d(target) < owner->GetBoundaryRadius()) - { - LaunchMovement(owner, chaseRange, true, mutualChase); - return true; - } + float angle = target->IsPlayer() ? target->m_movementInfo.GetMovementDirection() : 0.f; + if (result & CHASE_POSITION_CHECK_RESULT_ANGLE_INVALID) + angle = Position::NormalizeOrientation(_angle->RelativeAngle + float(M_PI)); + + target->MovePositionToFirstCollision(destination, SelectMovementSpeed(owner) * 0.5f, angle); } - } - - if (_nextMovementTimer.Passed()) - { - _nextMovementTimer.Reset(CHASE_MOVEMENT_INTERVAL); - - // Target has moved since we last checked its position. Handle new cases - if (!_lastTargetPosition || target->GetPosition() != _lastTargetPosition.value()) + else { - // Create new snapshot of our target's position - _lastTargetPosition = target->GetPosition(); + float angle = target->GetRelativeAngle(owner); + if (result & CHASE_POSITION_CHECK_RESULT_ANGLE_INVALID) + angle = _angle->RelativeAngle; - if (PositionOkay(owner, target, rangeTolerance, chaseAngle)) - { - if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && !target->isMoving() && !mutualChase) - { - // Our current position is fine. Stop movement. - owner->StopMoving(); - return true; - } - } - else if (owner->GetExactDist2d(target) > rangeTolerance + 0.1f) // 0.1f here to avoid edge cases when the owner has stepped back before - LaunchMovement(owner, chaseRange, false, mutualChase); + target->MovePositionToFirstCollision(destination, GetHitboxSum(owner, target), angle); + } + + owner->UpdateAllowedPositionZ(destination.GetPositionX(), destination.GetPositionY(), destination.m_positionZ); + + // Destination set and ready. Now the usual checks + result |= checkPosition(ChasePositionCheckOptions(CHASE_POSITION_CHECK_DISTANCE | CHASE_POSITION_CHECK_LOS), owner, target, &destination); + + if (result == CHASE_POSITION_CHECK_RESULT_VALID) + { + owner->StopMoving(); + return true; + } + + if (_moveTimer.Passed()) + { + _moveTimer.Reset(CHASE_MOVEMENT_INTERVAL); + launchSpline(owner, target, destination); } } @@ -200,7 +201,82 @@ void ChaseMovementGenerator::Finalize(Unit* owner) cOwner->SetCannotReachTarget(false); } -void ChaseMovementGenerator::LaunchMovement(Unit* owner, float chaseRange, bool backward /*= false*/, bool mutualChase /*= false*/) +ChaseMovementPositionCheckResult ChaseMovementGenerator::checkPosition(ChasePositionCheckOptions checkOptions, Unit* owner, Unit* target, Position const* destination /*nullptr*/) const +{ + uint8 result = CHASE_POSITION_CHECK_RESULT_VALID; + if (checkOptions & CHASE_POSITION_CHECK_DISTANCE) + { + float range = _range.has_value() ? *_range : (GetHitboxSum(owner, target) + float(CONTACT_DISTANCE)); + + // We have yet to reach the destination of the target so keep moving + if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && target->isMoving() && destination) + { + if (owner->GetExactDist(destination) >= range) + result |= CHASE_POSITION_CHECK_RESULT_TOO_FAR; + } + else if (owner->GetExactDist(target) >= range) + result |= CHASE_POSITION_CHECK_RESULT_TOO_FAR; + } + + //if (checkOptions & CHASE_POSITION_CHECK_BOUNDARY) + { + } + + if (checkOptions & CHASE_POSITION_CHECK_ANGLE) + { + // owner's relative angle to its target is not within boundaries + if (_angle.has_value() && !_angle->IsAngleOkay(target->GetRelativeAngle(owner))) + result |= CHASE_POSITION_CHECK_RESULT_ANGLE_INVALID; + } + + if (checkOptions & CHASE_POSITION_CHECK_LOS) + { + // check if the target is in line of sight + if (!owner->IsWithinLOSInMap(target)) + result |= CHASE_POSITION_CHECK_RESULT_NOT_IN_LOS; + } + + return static_cast(result); +} + +void ChaseMovementGenerator::launchSpline(Unit* owner, Unit* target, Position& destination) +{ + bool success = _pathGenerator->CalculatePath(destination.GetPositionX(), destination.GetPositionY(), destination.GetPositionZ(), owner->IsFlying()); + uint32 deniedPathResultTypes = PATHFIND_NOPATH | PATHFIND_INCOMPLETE; + if (!owner->IsFlying() || (target->IsInWater() && !owner->CanEnterWater())) // only flying and swimming units may use shortcuts + deniedPathResultTypes |= PATHFIND_SHORTCUT; + + bool cantReachTarget = !success || (_pathGenerator->GetPathType() & deniedPathResultTypes); + if (cantReachTarget) + { + owner->ToCreature()->SetCannotReachTarget(true); + owner->StopMoving(); + return; + } + else + owner->ToCreature()->SetCannotReachTarget(false); + + Movement::MoveSplineInit init(owner); + init.MovebyPath(_pathGenerator->GetPath()); + init.SetWalk(false); + init.SetFacing(target); + + if (target->isMoving()) + { + // When the target is still moving but the chaser is already within the combat reach of the target, it adapts its speed to the target's one + float targetSpeed = SelectMovementSpeed(target); + if (owner->GetExactDist(target) <= (GetHitboxSum(owner, target) + CONTACT_DISTANCE) && SelectMovementSpeed(owner) > targetSpeed) + init.SetVelocity(targetSpeed); + } + + if (init.Launch()) + { + _previousChaseSplineId = owner->movespline->GetId(); + owner->AddUnitState(UNIT_STATE_CHASE_MOVE); + } +} + +/* { Unit* target = GetTarget(); @@ -241,7 +317,7 @@ void ChaseMovementGenerator::LaunchMovement(Unit* owner, float chaseRange, bool PathGenerator path(owner); bool success = path.CalculatePath(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), owner->CanFly()); - if (!success || (path.GetPathType() & (PATHFIND_NOPATH /*| PATHFIND_INCOMPLETE*/))) + if (!success || (path.GetPathType() & (PATHFIND_NOPATH /*| PATHFIND_INCOMPLETE))) { if (creature) creature->SetCannotReachTarget(true); @@ -265,3 +341,4 @@ void ChaseMovementGenerator::LaunchMovement(Unit* owner, float chaseRange, bool if (creature) creature->SetCannotReachTarget(false); } +*/ diff --git a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h index 221ea080a5..9fbcb58370 100644 --- a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h @@ -24,34 +24,38 @@ #include "Timer.h" class Unit; +enum ChaseMovementPositionCheckResult : uint8; +enum ChasePositionCheckOptions : uint8; + +static constexpr uint32 CHASE_MOVEMENT_INTERVAL = 400; // sniffed value (1 batch update cyclice) +static constexpr uint32 POSITION_CHECK_INTERVAL = 100; // the interval at which we check if we already have reached the target +static constexpr uint32 REPOSITION_MOVEMENT_INTERVAL = 1200; // (3 batch update cycles) TODO: verify class ChaseMovementGenerator : public MovementGenerator, public AbstractPursuer { public: MovementGeneratorType GetMovementGeneratorType() const override { return CHASE_MOTION_TYPE; } - ChaseMovementGenerator(Unit* target, float range, Optional angle = {}); - ~ChaseMovementGenerator(); + explicit ChaseMovementGenerator(Unit* target, Optional range = {}, Optional angle = {}); + ~ChaseMovementGenerator() = default; void Initialize(Unit* owner) override; void Reset(Unit* owner) override { Initialize(owner); } bool Update(Unit* owner, uint32 diff) override; void Finalize(Unit* owner) override; - void UnitSpeedChanged() override { _lastTargetPosition.reset(); } + void UnitSpeedChanged() override { } private: - void LaunchMovement(Unit* owner, float chaseRange, bool backward = false, bool mutualChase = false); + Optional _range; + Optional _angle; + uint32 _previousChaseSplineId; + TimeTrackerSmall _moveTimer; + TimeTracker _positionCheckTimer; + std::unique_ptr _pathGenerator; - static constexpr uint32 CHASE_MOVEMENT_INTERVAL = 400; // sniffed value (1 batch update cyclice) - static constexpr uint32 REPOSITION_MOVEMENT_INTERVAL = 1200; // (3 batch update cycles) TODO: verify - - TimeTrackerSmall _nextMovementTimer; - TimeTrackerSmall _nextRepositioningTimer; - - Optional _lastTargetPosition; - float const _range; - Optional const _angle; + ChaseMovementPositionCheckResult checkPosition(ChasePositionCheckOptions checkOptions, Unit* owner, Unit* target, Position const* destination = nullptr) const; + void launchSpline(Unit* owner, Unit* target, Position& destination); }; #endif diff --git a/src/server/scripts/EasternKingdoms/GrimBatol/boss_drahga_shadowburner.cpp b/src/server/scripts/EasternKingdoms/GrimBatol/boss_drahga_shadowburner.cpp index 104d9aa4c4..014f07ba4b 100644 --- a/src/server/scripts/EasternKingdoms/GrimBatol/boss_drahga_shadowburner.cpp +++ b/src/server/scripts/EasternKingdoms/GrimBatol/boss_drahga_shadowburner.cpp @@ -490,7 +490,7 @@ struct npc_drahga_invoked_flaming_spirit : public ScriptedAI AddThreat(target, 1500000.0f); // Todo: this should be handled by serverside spell 95207 me->GetThreatManager().FixateTarget(target); me->ClearUnitState(UNIT_STATE_CASTING); - me->GetMotionMaster()->MoveChase(target, 0.f, 0.f); + me->GetMotionMaster()->MoveChase(target); } break; default: