From 1becd73f09764e9e6ae27f35efdb514e860b12bf Mon Sep 17 00:00:00 2001 From: UltraNix <80540499+UltraNix@users.noreply.github.com> Date: Tue, 24 Aug 2021 11:08:50 +0200 Subject: [PATCH] fix(Core/Movement): Rewritten follow movement generator for pets (#7324) - Closes #7296 --- src/server/game/AI/CoreAI/PetAI.cpp | 5 - src/server/game/Entities/Object/Object.cpp | 22 +- src/server/game/Entities/Object/Object.h | 12 +- src/server/game/Entities/Unit/Unit.cpp | 24 +- .../TargetedMovementGenerator.cpp | 271 +++++++++++------- .../TargetedMovementGenerator.h | 25 +- 6 files changed, 209 insertions(+), 150 deletions(-) diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index baa253264..87ed9fe6a 100644 --- a/src/server/game/AI/CoreAI/PetAI.cpp +++ b/src/server/game/AI/CoreAI/PetAI.cpp @@ -328,11 +328,6 @@ void PetAI::UpdateAI(uint32 diff) for (TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr) delete itr->second; } - - // Update speed as needed to prevent dropping too far behind and despawning - me->UpdateSpeed(MOVE_RUN, true); - me->UpdateSpeed(MOVE_WALK, true); - me->UpdateSpeed(MOVE_FLIGHT, true); } void PetAI::UpdateAllies() diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index dba56ac80..e46e7257c 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2539,7 +2539,7 @@ namespace Acore //=================================================================================================== -void WorldObject::GetNearPoint2D(WorldObject const* searcher, float& x, float& y, float distance2d, float absAngle) const +void WorldObject::GetNearPoint2D(WorldObject const* searcher, float& x, float& y, float distance2d, float absAngle, Position const* startPos) const { float effectiveReach = GetCombatReach(); @@ -2561,24 +2561,28 @@ void WorldObject::GetNearPoint2D(WorldObject const* searcher, float& x, float& y } } - x = GetPositionX() + (effectiveReach + distance2d) * std::cos(absAngle); - y = GetPositionY() + (effectiveReach + distance2d) * std::sin(absAngle); + float positionX = startPos ? startPos->GetPositionX() : GetPositionX(); + float positionY = startPos ? startPos->GetPositionY() : GetPositionY(); + + x = positionX + (effectiveReach + distance2d) * std::cos(absAngle); + y = positionY + (effectiveReach + distance2d) * std::sin(absAngle); Acore::NormalizeMapCoord(x); Acore::NormalizeMapCoord(y); } -void WorldObject::GetNearPoint2D(float& x, float& y, float distance2d, float absAngle) const +void WorldObject::GetNearPoint2D(float& x, float& y, float distance2d, float absAngle, Position const* startPos) const { - GetNearPoint2D(nullptr, x, y, distance2d, absAngle); + GetNearPoint2D(nullptr, x, y, distance2d, absAngle, startPos); } -void WorldObject::GetNearPoint(WorldObject const* searcher, float& x, float& y, float& z, float searcher_size, float distance2d, float absAngle, float controlZ) const +void WorldObject::GetNearPoint(WorldObject const* searcher, float& x, float& y, float& z, float searcher_size, float distance2d, float absAngle, float controlZ, Position const* startPos) const { - GetNearPoint2D(x, y, distance2d + searcher_size, absAngle); + GetNearPoint2D(x, y, distance2d + searcher_size, absAngle, startPos); z = GetPositionZ(); - if (searcher) { + if (searcher) + { if (Unit const* unit = searcher->ToUnit(); Unit const* target = ToUnit()) { if (unit && target && unit->IsInWater() && target->IsInWater()) @@ -2614,7 +2618,7 @@ void WorldObject::GetNearPoint(WorldObject const* searcher, float& x, float& y, // loop in a circle to look for a point in LoS using small steps for (float angle = float(M_PI) / 8; angle < float(M_PI) * 2; angle += float(M_PI) / 8) { - GetNearPoint2D(x, y, distance2d + searcher_size, absAngle + angle); + GetNearPoint2D(x, y, distance2d + searcher_size, absAngle + angle, startPos); z = GetPositionZ(); UpdateAllowedPositionZ(x, y, z); if (controlZ && fabsf(GetPositionZ() - z) > controlZ) diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 1cbfb4c54..5e5f51adc 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -19,6 +19,7 @@ #include #include #include +#include "G3D/Vector3.h" #ifdef ELUNA class ElunaEventProcessor; @@ -376,6 +377,11 @@ struct Position return !(operator==(a)); } + operator G3D::Vector3() const + { + return { m_positionX, m_positionY, m_positionZ }; + } + void Relocate(float x, float y) { m_positionX = x; @@ -758,9 +764,9 @@ public: ElunaEventProcessor* elunaEvents; #endif - void GetNearPoint2D(WorldObject const* searcher, float& x, float& y, float distance, float absAngle) const; - void GetNearPoint2D(float& x, float& y, float distance, float absAngle) const; - void GetNearPoint(WorldObject const* searcher, float& x, float& y, float& z, float searcher_size, float distance2d, float absAngle, float controlZ = 0) const; + void GetNearPoint2D(WorldObject const* searcher, float& x, float& y, float distance, float absAngle, Position const* startPos = nullptr) const; + void GetNearPoint2D(float& x, float& y, float distance, float absAngle, Position const* startPos = nullptr) const; + void GetNearPoint(WorldObject const* searcher, float& x, float& y, float& z, float searcher_size, float distance2d, float absAngle, float controlZ = 0, Position const* startPos = nullptr) const; void GetVoidClosePoint(float& x, float& y, float& z, float size, float distance2d = 0, float relAngle = 0, float controlZ = 0) const; bool GetClosePoint(float& x, float& y, float& z, float size, float distance2d = 0, float angle = 0, const WorldObject* forWho = nullptr, bool force = false) const; void MovePosition(Position& pos, float dist, float angle); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index ef9f1afa9..22c426487 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -13579,29 +13579,7 @@ void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) { if (GetTypeId() == TYPEID_UNIT) { - Unit* followed = nullptr; - if (GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) - { - followed = static_castconst*>(GetMotionMaster()->top())->GetTarget(); - } - - if (followed && !IsInCombat() && !IsVehicle() && !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED) && (IsPet() || IsGuardian() || GetGUID() == followed->GetCritterGUID() || GetCharmerOrOwnerGUID() == followed->GetGUID())) - { - if (followed->GetTypeId() != TYPEID_PLAYER) - { - if (speed < followed->GetSpeedRate(mtype) + 0.1f) - speed = followed->GetSpeedRate(mtype) + 0.1f; // pets derive speed from owner when not in combat - } - else - { - float ownerSpeed = followed->GetSpeedRate(mtype); - if (speed < ownerSpeed || IsWithinDist3d(followed, 10.0f)) - speed = ownerSpeed; - speed *= std::min(std::max(1.0f, 0.75f + (GetDistance(followed) - PET_FOLLOW_DIST) * 0.05f), 1.3f); - } - } - else - speed *= ToCreature()->GetCreatureTemplate()->speed_run; // at this point, MOVE_WALK is never reached + speed *= ToCreature()->GetCreatureTemplate()->speed_run; // at this point, MOVE_WALK is never reached } // Normalize speed by 191 aura SPELL_AURA_USE_NORMAL_MOVEMENT_SPEED if need diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp index d74acbc4b..c9feb3b58 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp @@ -22,7 +22,7 @@ static bool IsMutualChase(Unit* owner, Unit* target) } template -bool ChaseMovementGenerator::PositionOkay(T* owner, Unit* target, std::optional maxDistance, std::optional angle) +bool ChaseMovementGenerator::PositionOkay(T* owner, Unit* target, Optional maxDistance, Optional angle) { float const distSq = owner->GetExactDistSq(target); if (maxDistance && distSq > G3D::square(*maxDistance)) @@ -71,14 +71,14 @@ bool ChaseMovementGenerator::DoUpdate(T* owner, uint32 time_diff) 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; - std::optional angle = mutualChase ? std::optional() : _angle; + Optional angle = mutualChase ? Optional() : _angle; i_recheckDistance.Update(time_diff); if (i_recheckDistance.Passed()) { i_recheckDistance.Reset(100); - if (i_recalculateTravel && PositionOkay(owner, target, _movingTowards ? maxTarget : std::optional(), angle)) + if (i_recalculateTravel && PositionOkay(owner, target, _movingTowards ? maxTarget : Optional(), angle)) { i_recalculateTravel = false; i_path = nullptr; @@ -231,13 +231,118 @@ void ChaseMovementGenerator::MovementInform(T* owner) //-----------------------------------------------// -template -bool FollowMovementGenerator::PositionOkay(T* owner, Unit* target, float range, std::optional angle) +static Optional GetVelocity(Unit* owner, Unit* target, G3D::Vector3 const& dest, bool playerPet) { - if (owner->GetExactDistSq(target) > G3D::square(owner->GetCombatReach() + target->GetCombatReach() + range)) + Optional speed = {}; + if (!owner->IsInCombat() && !owner->IsVehicle() && !owner->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED) && + (owner->IsPet() || owner->IsGuardian() || owner->GetGUID() == target->GetCritterGUID() || owner->GetCharmerOrOwnerGUID() == target->GetGUID())) + { + UnitMoveType moveType = Movement::SelectSpeedType(target->GetUnitMovementFlags()); + speed = std::max(target->GetSpeed(moveType), owner->GetSpeed(moveType)); + + if (playerPet) + { + float distance = owner->GetDistance2d(dest.x, dest.y) - (*speed / 2.f); + if (distance > 0.f) + { + float multiplier = 1.f + (distance / 10.f); + *speed *= multiplier; + } + else + { + switch (moveType) + { + case MOVE_RUN_BACK: + case MOVE_SWIM_BACK: + case MOVE_FLIGHT_BACK: + break; + default: + *speed *= 0.9f; + break; + } + } + } + } + + return speed; +} + +static Position const PredictPosition(Unit* target) +{ + 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 + float speed = target->GetSpeed(Movement::SelectSpeedType(target->GetUnitMovementFlags())) * 0.5f; + float orientation = target->GetOrientation(); + + if (target->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FORWARD)) + { + pos.m_positionX += cos(orientation) * speed; + pos.m_positionY += sin(orientation) * speed; + } + else if (target->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_BACKWARD)) + { + pos.m_positionX -= cos(orientation) * speed; + pos.m_positionY -= sin(orientation) * speed; + } + + if (target->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_STRAFE_LEFT)) + { + pos.m_positionX += cos(orientation + M_PI / 2.f) * speed; + pos.m_positionY += sin(orientation + M_PI / 2.f) * speed; + } + else if (target->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_STRAFE_RIGHT)) + { + pos.m_positionX += cos(orientation - M_PI / 2.f) * speed; + pos.m_positionY += sin(orientation - M_PI / 2.f) * speed; + } + + return pos; +} + +template +bool FollowMovementGenerator::PositionOkay(Unit* target, bool isPlayerPet, bool& targetIsMoving, uint32 diff) +{ + if (!_lastTargetPosition) return false; - return !angle || angle->IsAngleOkay(target->GetRelativeAngle(owner)); + float exactDistSq = target->GetExactDistSq(_lastTargetPosition->GetPositionX(), _lastTargetPosition->GetPositionY(), _lastTargetPosition->GetPositionZ()); + float distanceTolerance = 0.25f; + // For creatures, increase tolerance + if (target->GetTypeId() == TYPEID_UNIT) + { + distanceTolerance += _range + _range; + } + + if (isPlayerPet) + { + targetIsMoving = target->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_BACKWARD | MOVEMENTFLAG_STRAFE_LEFT | MOVEMENTFLAG_STRAFE_RIGHT); + } + + if (exactDistSq > distanceTolerance) + return false; + + if (isPlayerPet) + { + if (!targetIsMoving) + { + if (i_recheckPredictedDistanceTimer.GetExpiry()) + { + i_recheckPredictedDistanceTimer.Update(diff); + if (i_recheckPredictedDistanceTimer.Passed()) + { + i_recheckPredictedDistanceTimer = 0; + return false; + } + } + + return true; + } + + return false; + } + + return true; } template @@ -274,123 +379,93 @@ bool FollowMovementGenerator::DoUpdate(T* owner, uint32 time_diff) (i_target->GetTypeId() == TYPEID_PLAYER && i_target->ToPlayer()->IsGameMaster()) // for .npc follow ; // closes "bool forceDest", that way it is more appropriate, so we can comment out crap whenever we need to - i_recheckDistance.Update(time_diff); - if (i_recheckDistance.Passed()) + bool targetIsMoving = false; + if (PositionOkay(target, owner->IsGuardian() && target->GetTypeId() == TYPEID_PLAYER, targetIsMoving, time_diff)) { - i_recheckDistance.Reset(100); - - if (i_recalculateTravel && PositionOkay(owner, target, _range, _angle)) + if (owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE) && owner->movespline->Finalized()) { - i_recalculateTravel = false; + owner->ClearUnitState(UNIT_STATE_FOLLOW_MOVE); i_path = nullptr; - owner->StopMoving(); - _lastTargetPosition.reset(); MovementInform(owner); - return true; + + if (i_recheckPredictedDistance) + { + i_recheckPredictedDistanceTimer.Reset(1000); + } + + owner->SetFacingTo(target->GetOrientation()); } } - - if (owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE) && owner->movespline->Finalized()) - { - i_recalculateTravel = false; - i_path = nullptr; - owner->ClearUnitState(UNIT_STATE_FOLLOW_MOVE); - MovementInform(owner); - } - - Position targetPosition = i_target->GetPosition(); - - if (_lastTargetPosition && _lastTargetPosition->GetExactDistSq(&targetPosition) == 0.0f) - return true; - - _lastTargetPosition = targetPosition; - - if (PositionOkay(owner, target, _range + PET_FOLLOW_DIST) && !owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE)) - return true; - - if (!i_path) - i_path = std::make_unique(owner); - - float x, y, z; - // select angle - float tAngle; - float const curAngle = target->GetRelativeAngle(owner); - if (!oPet) - { - // for non pets, keep the relative angle - // decided during the summon - tAngle = _angle.RelativeAngle; - } - else if (_angle.IsAngleOkay(curAngle)) - { - tAngle = curAngle; - } else { - float const diffUpper = Position::NormalizeOrientation(curAngle - _angle.UpperBound()); - float const diffLower = Position::NormalizeOrientation(_angle.LowerBound() - curAngle); - if (diffUpper < diffLower) - tAngle = _angle.UpperBound(); + Position targetPosition = target->GetPosition(); + _lastTargetPosition = targetPosition; + + // If player is moving and their position is not updated, we need to predict position + if (targetIsMoving) + { + Position predictedPosition = PredictPosition(target); + if (_lastPredictedPosition && _lastPredictedPosition->GetExactDistSq(&predictedPosition) < 0.25f) + return true; + + _lastPredictedPosition = predictedPosition; + targetPosition = predictedPosition; + i_recheckPredictedDistance = true; + } else - tAngle = _angle.LowerBound(); + { + i_recheckPredictedDistance = false; + i_recheckPredictedDistanceTimer.Reset(0); + } + + if (!i_path) + i_path = std::make_unique(owner); + else + i_path->Clear(); + + float distance = _range - target->GetCombatReach(); + + float relAngle = _angle.RelativeAngle; + float x, y, z; + target->GetNearPoint(owner, x, y, z, owner->GetCombatReach(), distance, target->ToAbsoluteAngle(relAngle), 0.f, &targetPosition); + + if (owner->IsHovering()) + owner->UpdateAllowedPositionZ(x, y, z); + + bool success = i_path->CalculatePath(x, y, z, forceDest); + if (!success || i_path->GetPathType() & PATHFIND_NOPATH) + { + if (!owner->IsStopped()) + owner->StopMoving(); + + return true; + } + + owner->AddUnitState(UNIT_STATE_FOLLOW_MOVE); + + Movement::MoveSplineInit init(owner); + init.MovebyPath(i_path->GetPath()); + init.SetWalk(target->IsWalking()); + if (Optional velocity = GetVelocity(owner, target, i_path->GetActualEndPosition(), owner->IsGuardian() && target->GetTypeId() == TYPEID_PLAYER)) + init.SetVelocity(*velocity); + init.Launch(); } - target->GetNearPoint(owner, x, y, z, _range, 0.f, target->ToAbsoluteAngle(tAngle)); - - i_recalculateTravel = true; - - bool success = i_path->CalculatePath(x, y, z, forceDest); - if (!success || i_path->GetPathType() & PATHFIND_NOPATH) - { - if (cOwner) - cOwner->SetCannotReachTarget(true); - return true; - } - - owner->AddUnitState(UNIT_STATE_FOLLOW_MOVE); - - Movement::MoveSplineInit init(owner); - init.MovebyPath(i_path->GetPath()); - init.SetFacing(target->GetOrientation()); - init.SetWalk(target->IsWalking()); - init.Launch(); - return true; } -template<> -void FollowMovementGenerator::_updateSpeed(Player* /*owner*/) -{ - // nothing to do for Player -} - -template<> -void FollowMovementGenerator::_updateSpeed(Creature* owner) -{ - // pet only sync speed with owner - /// Make sure we are not in the process of a map change (IsInWorld) - if (!owner->GetOwnerGUID().IsPlayer() || !owner->IsInWorld() || !i_target.isValid() || i_target->GetGUID() != owner->GetOwnerGUID()) - return; - - owner->UpdateSpeed(MOVE_RUN, true); - owner->UpdateSpeed(MOVE_WALK, true); - owner->UpdateSpeed(MOVE_SWIM, true); -} - template void FollowMovementGenerator::DoInitialize(T* owner) { i_path = nullptr; _lastTargetPosition.reset(); owner->AddUnitState(UNIT_STATE_FOLLOW); - _updateSpeed(owner); } template void FollowMovementGenerator::DoFinalize(T* owner) { owner->ClearUnitState(UNIT_STATE_FOLLOW | UNIT_STATE_FOLLOW_MOVE); - _updateSpeed(owner); } template diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h index 8266fc1c3..e5d9d9152 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h @@ -9,10 +9,10 @@ #include "FollowerReference.h" #include "MovementGenerator.h" +#include "Optional.h" #include "PathGenerator.h" #include "Timer.h" #include "Unit.h" -#include class TargetedMovementGeneratorBase { @@ -27,7 +27,7 @@ template class ChaseMovementGenerator : public MovementGeneratorMedium>, public TargetedMovementGeneratorBase { public: - ChaseMovementGenerator(Unit* target, std::optional range = {}, std::optional angle = {}) + ChaseMovementGenerator(Unit* target, Optional range = {}, Optional angle = {}) : TargetedMovementGeneratorBase(target), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {} ~ChaseMovementGenerator() { } @@ -39,7 +39,7 @@ public: void DoReset(T*); void MovementInform(T*); - bool PositionOkay(T* owner, Unit* target, std::optional maxDistance, std::optional angle); + bool PositionOkay(T* owner, Unit* target, Optional maxDistance, Optional angle); void unitSpeedChanged() { _lastTargetPosition.reset(); } Unit* GetTarget() const { return i_target.getTarget(); } @@ -52,9 +52,9 @@ private: TimeTrackerSmall i_recheckDistance; bool i_recalculateTravel; - std::optional _lastTargetPosition; - std::optional const _range; - std::optional const _angle; + Optional _lastTargetPosition; + Optional const _range; + Optional const _angle; bool _movingTowards = true; bool _mutualChase = true; }; @@ -64,7 +64,7 @@ class FollowMovementGenerator : public MovementGeneratorMedium angle = {}); + bool PositionOkay(Unit* target, bool isPlayerPet, bool& targetIsMoving, uint32 diff); static void _clearUnitStateMove(T* u) { u->ClearUnitState(UNIT_STATE_FOLLOW_MOVE); } static void _addUnitStateMove(T* u) { u->AddUnitState(UNIT_STATE_FOLLOW_MOVE); } - void _updateSpeed(T* owner); + float GetFollowRange() const { return _range; } private: std::unique_ptr i_path; - TimeTrackerSmall i_recheckDistance; - bool i_recalculateTravel; + TimeTrackerSmall i_recheckPredictedDistanceTimer; + bool i_recheckPredictedDistance; - std::optional _lastTargetPosition; + Optional _lastTargetPosition; + Optional _lastPredictedPosition; float _range; ChaseAngle _angle; };