diff --git a/src/server/game/AI/CoreAI/CombatAI.cpp b/src/server/game/AI/CoreAI/CombatAI.cpp index b5b9b02bb03..572d58da611 100644 --- a/src/server/game/AI/CoreAI/CombatAI.cpp +++ b/src/server/game/AI/CoreAI/CombatAI.cpp @@ -198,7 +198,7 @@ void ArcherAI::AttackStart(Unit* who) if (me->IsWithinCombatRange(who, m_minRange)) { if (me->Attack(who, true) && !who->IsFlying()) - me->GetMotionMaster()->MoveChase(who); + me->GetMotionMaster()->MoveChase(who, 0.f); } else { diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index 7d05c738b83..c6162e36a5c 100644 --- a/src/server/game/AI/CoreAI/PetAI.cpp +++ b/src/server/game/AI/CoreAI/PetAI.cpp @@ -491,7 +491,7 @@ void PetAI::DoAttack(Unit* target, bool chase) // 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, ChaseRange(0.f, chaseDistance), angle); + me->GetMotionMaster()->MoveChase(target, chaseDistance, angle); } else // (Stay && ((Aggressive || Defensive) && In Melee Range))) { diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index 27b3ad76397..ab168ef9334 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -263,7 +263,7 @@ void MotionMaster::MoveFollow(Unit* target, float dist, ChaseAngle angle, bool a Mutate(new FollowMovementGenerator(target, dist, angle, allignToTargetSpeed), slot); } -void MotionMaster::MoveChase(Unit* target, Optional dist, Optional angle) +void MotionMaster::MoveChase(Unit* target, float dist, Optional angle) { // 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 4b1df92d78d..bd3c2dcb4fe 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -89,19 +89,6 @@ enum RotateDirection ROTATE_DIRECTION_RIGHT }; -struct ChaseRange -{ - ChaseRange(float range) : MinRange(range > CONTACT_DISTANCE ? 0 : range - CONTACT_DISTANCE), MinTolerance(range), MaxRange(range + CONTACT_DISTANCE), MaxTolerance(range) {} - ChaseRange(float min, float max) : MinRange(min), MinTolerance(std::min(min + CONTACT_DISTANCE, (min + max) / 2)), MaxRange(max), MaxTolerance(std::max(max - CONTACT_DISTANCE, MinTolerance)) {} - ChaseRange(float min, float tMin, float tMax, float max) : MinRange(min), MinTolerance(tMin), MaxRange(max), MaxTolerance(tMax) {} - - // this contains info that informs how we should path! - float MinRange; // we have to move if we are within this range... (min. attack range) - float MinTolerance; // ...and if we are, we will move this far away - float MaxRange; // we have to move if we are outside this range... (max. attack range) - float MaxTolerance; // ...and if we are, we will move into this range -}; - struct ChaseAngle { ChaseAngle(float angle, float tol = M_PI_4) : RelativeAngle(Position::NormalizeOrientation(angle)), Tolerance(tol) {} @@ -161,9 +148,10 @@ class TC_GAME_API MotionMaster void MoveTargetedHome(); void MoveRandom(float spawndist = 0.0f); void MoveFollow(Unit* target, float dist, ChaseAngle angle, bool allignToTargetSpeed = false, MovementSlot slot = MOTION_SLOT_ACTIVE); - void MoveChase(Unit* target, Optional dist = {}, Optional angle = {}); - void MoveChase(Unit* target, float dist, float angle) { MoveChase(target, ChaseRange(dist), ChaseAngle(angle)); } - void MoveChase(Unit* target, float dist) { MoveChase(target, ChaseRange(dist)); } + + void MoveChase(Unit* target, float dist = 0.f, Optional angle = {}); + void MoveChase(Unit* target, float dist, float angle) { MoveChase(target, dist, ChaseAngle(angle)); } + void MoveConfused(); void MoveFleeing(Unit* enemy, uint32 time = 0); void MovePoint(uint32 id, Position const& pos, bool generatePath = true) diff --git a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator .cpp b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator .cpp index f1dd599c4e9..310be5b220b 100644 --- a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator .cpp +++ b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator .cpp @@ -24,6 +24,7 @@ #include "PathGenerator.h" #include "Unit.h" #include "Util.h" +#include "Vehicle.h" static bool IsMutualChase(Unit* owner, Unit* target) { @@ -41,22 +42,26 @@ static void DoMovementInform(Unit* owner, Unit* target) ai->MovementInform(CHASE_MOTION_TYPE, target->GetGUID().GetCounter()); } -static bool PositionOkay(Unit* owner, Unit* target, Optional minDistance, Optional maxDistance, Optional angle) +static bool PositionOkay(Unit* owner, Unit* target, float distance, Optional angle) { - float const distSq = owner->GetExactDistSq(target); - if (minDistance && distSq < square(*minDistance)) - return false; - if (maxDistance && distSq > square(*maxDistance)) + 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; } -ChaseMovementGenerator::ChaseMovementGenerator(Unit* target, Optional range, Optional angle) : AbstractFollower(ASSERT_NOTNULL(target)), _range(range), _angle(angle) {} +ChaseMovementGenerator::ChaseMovementGenerator(Unit* target, float range, Optional angle) : AbstractFollower(ASSERT_NOTNULL(target)), _range(range), _angle(angle) { } ChaseMovementGenerator::~ChaseMovementGenerator() = default; void ChaseMovementGenerator::Initialize(Unit* owner) @@ -64,6 +69,8 @@ void ChaseMovementGenerator::Initialize(Unit* owner) owner->AddUnitState(UNIT_STATE_CHASE); owner->SetWalk(false); _lastTargetPosition.reset(); + _nextMovementTimer.Reset(0); + _nextRepositioningTimer.Reset(0); } bool ChaseMovementGenerator::Update(Unit* owner, uint32 diff) @@ -92,107 +99,81 @@ bool ChaseMovementGenerator::Update(Unit* owner, uint32 diff) } bool const mutualChase = IsMutualChase(owner, target); - float const hitboxSum = owner->GetCombatReach() + target->GetCombatReach(); - float const minRange = _range ? _range->MinRange + hitboxSum : CONTACT_DISTANCE; - float const minTarget = (_range ? _range->MinTolerance : 0.0f) + hitboxSum; - float const maxRange = _range ? _range->MaxRange + hitboxSum : owner->GetMeleeRange(target); // melee range already includes hitboxes - float const maxTarget = _range ? _range->MaxTolerance + hitboxSum : CONTACT_DISTANCE + hitboxSum; - Optional angle = mutualChase ? Optional() : _angle; + //float const hitboxSum = owner->GetCombatReach() + target->GetCombatReach(); + float const chaseRange = owner->GetCombatReach() + target->GetCombatReach(); + float const rangeTolerance = _range > 0.f ? _range : chaseRange; + Optional chaseAngle = mutualChase ? Optional() : _angle; - // if we're already moving, periodically check if we're already in the expected range... - if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE)) - { - if (_rangeCheckTimer > diff) - _rangeCheckTimer -= diff; - else - { - _rangeCheckTimer = RANGE_CHECK_INTERVAL; - if (PositionOkay(owner, target, _movingTowards ? Optional() : minTarget, _movingTowards ? maxTarget : Optional(), angle)) - { - _path = nullptr; - owner->StopMoving(); - owner->SetInFront(target); - DoMovementInform(owner, target); - return true; - } - } - } - - // if we're done moving, we want to clean up + // We are done moving. Trigger movement inform hook and clear chase move state if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && owner->movespline->Finalized()) { - _path = nullptr; if (Creature* cOwner = owner->ToCreature()) cOwner->SetCannotReachTarget(false); + owner->ClearUnitState(UNIT_STATE_CHASE_MOVE); owner->SetInFront(target); DoMovementInform(owner, target); } - // if the target moved, we have to consider whether to adjust - if (!_lastTargetPosition || target->GetPosition() != _lastTargetPosition.get() || mutualChase != _mutualChase) + // Update Movement + _nextMovementTimer.Update(diff); + _nextRepositioningTimer.Update(diff); + + // Handle repositioning and scattering arround target + if (_nextRepositioningTimer.Passed()) { - _lastTargetPosition = target->GetPosition(); - _mutualChase = mutualChase; - if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE) || !PositionOkay(owner, target, minRange, maxRange, angle)) + _nextRepositioningTimer.Reset(REPOSITION_MOVEMENT_INTERVAL); + + if (!owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && owner->IsCreature()) { - Creature* const cOwner = owner->ToCreature(); - // can we get to the target? - if (cOwner && !target->isInAccessiblePlaceFor(cOwner)) + // Owner is too close to its target. Step back. + if (owner->GetExactDist2d(target) < owner->GetBoundaryRadius()) { - cOwner->SetCannotReachTarget(true); - cOwner->StopMoving(); - _path = nullptr; + LaunchMovement(owner, target, chaseRange, true); + return true; + } + } + } + + 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.get()) + { + // Create new snapshot of our target's position + _lastTargetPosition = target->GetPosition(); + + Creature* creature = owner->ToCreature(); + // Owner cannot reach target (example: target is in water and owner cannot swim) + if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && creature) + { + if (!target->isInAccessiblePlaceFor(creature)) + { + creature->SetCannotReachTarget(true); + creature->StopMoving(); + return true; + } + } + else if (creature && !target->isInAccessiblePlaceFor(creature)) + { + creature->SetCannotReachTarget(true); return true; } - // figure out which way we want to move - bool const moveToward = !owner->IsInDist(target, maxRange); - - // make a new path if we have to... - if (!_path || moveToward != _movingTowards) - _path = std::make_unique(owner); - - float x, y, z; - bool shortenPath; - // if we want to move toward the target and there's no fixed angle... - if (moveToward && !angle) + bool isMoving = target->HasUnitMovementFlag(MOVEMENTFLAG_STRAFE_LEFT | MOVEMENTFLAG_STRAFE_RIGHT | MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_BACKWARD) || !target->movespline->Finalized(); + if (PositionOkay(owner, target, rangeTolerance, chaseAngle) && !isMoving) { - // ...we'll pathfind to the center, then shorten the path - target->GetPosition(x, y, z); - shortenPath = true; + if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE)) + { + // Just stopping our movement, movementinform and cleanups will be executed in the code above on the next update tick + owner->StopMoving(); + return true; + } } - else - { - // otherwise, we fall back to nearpoint finding - target->GetNearPoint(owner, x, y, z, (moveToward ? maxTarget : minTarget) - hitboxSum, angle ? target->NormalizeOrientation(target->GetOrientation() - angle->RelativeAngle) : target->GetAngle(owner)); - shortenPath = false; - } - - if (owner->IsHovering()) - owner->UpdateAllowedPositionZ(x, y, z); - - bool success = _path->CalculatePath(x, y, z, owner->CanFly()); - if (!success || (_path->GetPathType() & (PATHFIND_NOPATH /* | PATHFIND_INCOMPLETE*/))) - { - if (cOwner) - cOwner->SetCannotReachTarget(true); - owner->StopMoving(); - return true; - } - - if (shortenPath) - _path->ShortenPathUntilDist(PositionToVector3(target), maxTarget); - - if (cOwner) - cOwner->SetCannotReachTarget(false); - owner->AddUnitState(UNIT_STATE_CHASE_MOVE); - - Movement::MoveSplineInit init(owner); - init.MovebyPath(_path->GetPath()); - init.SetWalk(false); - init.SetFacing(target); - init.Launch(); + else if (owner->GetExactDist2d(target) > rangeTolerance + 0.1f) // 0.1f here to avoid edge cases when the owner has stepped back before + LaunchMovement(owner, target, chaseRange); } } @@ -206,3 +187,63 @@ void ChaseMovementGenerator::Finalize(Unit* owner) if (Creature* cOwner = owner->ToCreature()) cOwner->SetCannotReachTarget(false); } + +void ChaseMovementGenerator::LaunchMovement(Unit* owner, Unit* target, float chaseRange, bool backward /*= false*/) +{ + // Owner may launch a new spline + Position dest = target->GetVehicle() ? target->GetVehicle()->GetBase()->GetPosition() : target->GetPosition(); + + // Player chase target is currently moving + bool predictDestination = target->HasUnitMovementFlag(MOVEMENTFLAG_STRAFE_LEFT | MOVEMENTFLAG_STRAFE_RIGHT | MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_BACKWARD); + + if (!backward && predictDestination) + { + UnitMoveType moveType = MOVE_RUN; + if (target->CanFly()) + moveType = target->HasUnitMovementFlag(MOVEMENTFLAG_BACKWARD) ? MOVE_FLIGHT_BACK : MOVE_FLIGHT; + else + { + if (target->IsWalking()) + moveType = MOVE_WALK; + else + moveType = target->HasUnitMovementFlag(MOVEMENTFLAG_BACKWARD) ? MOVE_RUN_BACK : MOVE_RUN; + } + + float additionalRange = target->GetSpeed(moveType) * 0.5f; + + target->MovePositionToFirstCollision(dest, additionalRange, _angle ? _angle->RelativeAngle : target->GetRelativeAngle(owner) + float(M_PI)); + } + else + target->MovePositionToFirstCollision(dest, chaseRange, _angle ? _angle->RelativeAngle : target->GetRelativeAngle(owner)); + + owner->UpdateAllowedPositionZ(dest.GetPositionX(), dest.GetPositionY(), dest.m_positionZ); + + Creature* creature = owner->ToCreature(); + + PathGenerator path(owner); + bool success = path.CalculatePath(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), owner->CanFly()); + if (!success || (path.GetPathType() & (PATHFIND_NOPATH /*| PATHFIND_INCOMPLETE*/))) + { + if (creature) + creature->SetCannotReachTarget(true); + + owner->StopMoving(); + return; + } + + Movement::MoveSplineInit init(owner); + init.MovebyPath(path.GetPath()); + init.SetWalk(false); + if (backward) + init.SetBackward(); + else + init.SetFacing(target); + + init.Launch(); + + if (!backward) + owner->AddUnitState(UNIT_STATE_CHASE_MOVE); + + if (creature) + creature->SetCannotReachTarget(false); +} diff --git a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h index 57afa4e1369..c8c245bdff2 100644 --- a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h @@ -21,6 +21,7 @@ #include "AbstractFollower.h" #include "MovementGenerator.h" #include "Optional.h" +#include "Timer.h" class PathGenerator; class Unit; @@ -30,7 +31,7 @@ class ChaseMovementGenerator : public MovementGenerator, public AbstractFollower public: MovementGeneratorType GetMovementGeneratorType() const override { return CHASE_MOTION_TYPE; } - ChaseMovementGenerator(Unit* target, Optional range = {}, Optional angle = {}); + ChaseMovementGenerator(Unit* target, float range, Optional angle = {}); ~ChaseMovementGenerator(); void Initialize(Unit* owner) override; @@ -41,16 +42,17 @@ class ChaseMovementGenerator : public MovementGenerator, public AbstractFollower void UnitSpeedChanged() override { _lastTargetPosition.reset(); } private: - static constexpr uint32 RANGE_CHECK_INTERVAL = 100; // time (ms) until we attempt to recalculate + void LaunchMovement(Unit* owner, Unit* target, float chaseRange, bool backward = false); - Optional const _range; - Optional const _angle; + 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; - std::unique_ptr _path; Optional _lastTargetPosition; - uint32 _rangeCheckTimer = RANGE_CHECK_INTERVAL; - bool _movingTowards = true; - bool _mutualChase = true; + float const _range; + Optional const _angle; }; #endif