diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index f3401d8398..06ce2c3618 100644 --- a/src/server/game/AI/CoreAI/PetAI.cpp +++ b/src/server/game/AI/CoreAI/PetAI.cpp @@ -456,7 +456,7 @@ void PetAI::HandleReturnMovement() ClearCharmInfoFlags(); me->GetCharmInfo()->SetIsReturning(true); me->GetMotionMaster()->Clear(); - me->GetMotionMaster()->MoveFollow(me->GetCharmerOrOwner(), PET_FOLLOW_DIST, ChaseAngle(me->GetFollowAngle(), 0.f), true); + me->FollowTarget(me->GetCharmerOrOwner()); } } @@ -485,13 +485,13 @@ void PetAI::DoAttack(Unit* target, bool chase) bool oldCmdAttack = me->GetCharmInfo()->IsCommandAttack(); // This needs to be reset after other flags are cleared ClearCharmInfoFlags(); me->GetCharmInfo()->SetIsCommandAttack(oldCmdAttack); // For passive pets commanded to attack so they will use spells - me->GetMotionMaster()->Clear(); 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); + me->GetMotionMaster()->Clear(); } else // (Stay && ((Aggressive || Defensive) && In Melee Range))) { diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index af4007fa6b..18caae27f9 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -235,7 +235,7 @@ void CreatureAI::JustAppeared() if (Unit* owner = summon->GetCharmerOrOwner()) { summon->GetMotionMaster()->Clear(); - summon->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, summon->GetFollowAngle(), summon->IsMinion()); + summon->FollowTarget(owner); } } } @@ -252,10 +252,7 @@ void CreatureAI::EnterEvadeMode(EvadeReason why) if (!me->GetVehicle()) // otherwise me will be in evade mode forever { if (Unit* owner = me->GetCharmerOrOwner()) - { - me->GetMotionMaster()->Clear(false); - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), me->IsMinion()); - } + me->FollowTarget(owner); else { // Required to prevent attacking creatures that are evading and cause them to reenter combat diff --git a/src/server/game/AI/PlayerAI/PlayerAI.cpp b/src/server/game/AI/PlayerAI/PlayerAI.cpp index 71f6458afa..905a3f5fec 100644 --- a/src/server/game/AI/PlayerAI/PlayerAI.cpp +++ b/src/server/game/AI/PlayerAI/PlayerAI.cpp @@ -1307,7 +1307,7 @@ void SimpleCharmedPlayerAI::UpdateAI(const uint32 diff) me->CastStop(); me->StopMoving(); me->GetMotionMaster()->Clear(); - me->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + me->FollowTarget(charmer); } } diff --git a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp index f58e263a5c..a52d45ac06 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp @@ -77,7 +77,7 @@ void FollowerAI::JustReachedHome() { if (HasFollowState(STATE_FOLLOW_PAUSED)) return; - me->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE, true); + me->FollowTarget(player); } else me->DespawnOrUnsummon(); @@ -202,7 +202,7 @@ void FollowerAI::StartFollow(Player* player, uint32 factionForFollower, uint32 q AddFollowState(STATE_FOLLOW_INPROGRESS); - me->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE, true); + me->FollowTarget(player); TC_LOG_DEBUG("scripts.ai.followerai", "FollowerAI::StartFollow: start follow %s - %s (%s)", player->GetName().c_str(), _leaderGUID.ToString().c_str(), me->GetGUID().ToString().c_str()); } @@ -280,7 +280,7 @@ void FollowerAI::SetFollowPaused(bool paused) RemoveFollowState(STATE_FOLLOW_PAUSED); if (Player* leader = GetLeaderForFollower()) - me->GetMotionMaster()->MoveFollow(leader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE, true); + me->FollowTarget(leader); } } diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index ed8c7f9347..9d5e78c42c 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -507,7 +507,7 @@ void SmartAI::EnterEvadeMode(EvadeReason /*why*/) if (Unit* owner = me->GetCharmerOrOwner()) { - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + me->FollowTarget(owner); me->ClearUnitState(UNIT_STATE_EVADE); } else if (HasEscortState(SMART_ESCORT_ESCORTING)) @@ -517,7 +517,7 @@ void SmartAI::EnterEvadeMode(EvadeReason /*why*/) } else if (Unit* target = _followGuid ? ObjectAccessor::GetUnit(*me, _followGuid) : nullptr) { - me->GetMotionMaster()->MoveFollow(target, _followDist, _followAngle); + me->FollowTarget(target); // evade is not cleared in MoveFollow, so we can't keep it me->ClearUnitState(UNIT_STATE_EVADE); } @@ -889,7 +889,11 @@ void SmartAI::SetFollow(Unit* target, float dist, float angle, uint32 credit, ui _followArrivedEntry = end; _followCreditType = creditType; SetRun(_run); - me->GetMotionMaster()->MoveFollow(target, _followDist, _followAngle); + + if (dist > 0.f && angle > 0.f) + me->GetMotionMaster()->MoveFollow(target, dist, angle); + else + me->FollowTarget(target); } void SmartAI::StopFollow(bool complete) diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp index 9b57a2b071..4597457239 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.cpp +++ b/src/server/game/Entities/Creature/TemporarySummon.cpp @@ -303,10 +303,6 @@ Minion::Minion(SummonPropertiesEntry const* properties, Unit* owner, bool isWorl { ASSERT(m_owner); m_unitTypeMask |= UNIT_MASK_MINION; - m_followAngle = PET_FOLLOW_ANGLE; - - if (properties && SummonTitle(properties->Title) == SummonTitle::Companion) - m_followAngle = COMPANION_FOLLOW_ANGLE; /// @todo: Find correct way InitCharmInfo(); @@ -330,7 +326,7 @@ void Minion::InitStats(uint32 duration) totemOwner->m_Controlled.insert(this); } - if (m_Properties && m_Properties->Title == AsUnderlyingType(SummonTitle::Companion)) + if (m_Properties && m_Properties->Slot == SUMMON_SLOT_MINIPET) { SelectLevel(); // some summoned creaters have different from 1 DB data for level/hp SetUInt32Value(UNIT_NPC_FLAGS, GetCreatureTemplate()->npcflag); diff --git a/src/server/game/Entities/Creature/TemporarySummon.h b/src/server/game/Entities/Creature/TemporarySummon.h index f8b677ce78..9e1429c098 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.h +++ b/src/server/game/Entities/Creature/TemporarySummon.h @@ -106,8 +106,6 @@ class TC_GAME_API Minion : public TempSummon void InitStats(uint32 duration) override; void RemoveFromWorld() override; Unit* GetOwner() const { return m_owner; } - float GetFollowAngle() const override { return m_followAngle; } - void SetFollowAngle(float angle) { m_followAngle = angle; } bool IsPetGhoul() const { return GetEntry() == ENTRY_GHOUL; } // Ghoul may be guardian or pet bool IsRisenAlly() const { return GetEntry() == ENTRY_RISEN_ALLY; } bool IsSpiritWolf() const { return GetEntry() == ENTRY_SPIRIT_WOLF; } // Spirit wolf from feral spirits @@ -115,7 +113,6 @@ class TC_GAME_API Minion : public TempSummon bool IsWarlockMinion() const; protected: Unit* const m_owner; - float m_followAngle; }; class TC_GAME_API Guardian : public Minion diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 329d1f091c..19ef8ddc9b 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -173,9 +173,9 @@ bool Pet::LoadPetData(Player* owner, uint32 petEntry, uint32 petnumber, bool cur if (IsCritter()) { - float px, py, pz; - owner->GetClosePoint(px, py, pz, GetCombatReach(), PET_FOLLOW_DIST, GetFollowAngle()); - Relocate(px, py, pz, owner->GetOrientation()); + Position pos = owner->GetPosition(); + owner->MovePositionToFirstCollision(pos, DEFAULT_FOLLOW_DISTANCE_PET, DEFAULT_FOLLOW_ANGLE); + Relocate(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), owner->GetOrientation()); if (!IsPositionValid()) { @@ -235,10 +235,10 @@ bool Pet::LoadPetData(Player* owner, uint32 petEntry, uint32 petnumber, bool cur SynchronizeLevelWithOwner(); - // Set pet's position after setting level, its size depends on it - float px, py, pz; - owner->GetClosePoint(px, py, pz, GetCombatReach(), PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); - Relocate(px, py, pz, owner->GetOrientation()); + Position pos = owner->GetPosition(); + owner->MovePositionToFirstCollision(pos, DEFAULT_FOLLOW_DISTANCE_PET, float(M_PI_2)); + Relocate(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), owner->GetOrientation()); + if (!IsPositionValid()) { TC_LOG_ERROR("entities.pet", "Pet (guidlow %d, entry %d) not loaded. Suggested coordinates isn't valid (X: %f Y: %f)", diff --git a/src/server/game/Entities/Pet/PetDefines.h b/src/server/game/Entities/Pet/PetDefines.h index a8a440b2e3..8eba4649f3 100644 --- a/src/server/game/Entities/Pet/PetDefines.h +++ b/src/server/game/Entities/Pet/PetDefines.h @@ -78,8 +78,9 @@ enum PetTalk PET_TALK_ATTACK = 1 }; -#define PET_FOLLOW_DIST 1.0f -#define PET_FOLLOW_ANGLE float(M_PI / 2) -#define COMPANION_FOLLOW_ANGLE float(M_PI) +// Used by companions (minipets) and quest slot summons +constexpr float DEFAULT_FOLLOW_DISTANCE = 2.5f; +constexpr float DEFAULT_FOLLOW_DISTANCE_PET = 3.f; +constexpr float DEFAULT_FOLLOW_ANGLE = float(M_PI); #endif diff --git a/src/server/game/Entities/Totem/Totem.cpp b/src/server/game/Entities/Totem/Totem.cpp index 5cbbb88bf6..e3ee9042f3 100644 --- a/src/server/game/Entities/Totem/Totem.cpp +++ b/src/server/game/Entities/Totem/Totem.cpp @@ -54,7 +54,7 @@ void Totem::InitStats(uint32 duration) { // client requires SMSG_TOTEM_CREATED to be sent before adding to world and before removing old totem if (GetOwner()->GetTypeId() == TYPEID_PLAYER - && m_Properties->Slot >= SUMMON_SLOT_TOTEM + && m_Properties->Slot >= SUMMON_SLOT_TOTEM_FIRE && m_Properties->Slot < MAX_TOTEM_SLOT) { WorldPacket data(SMSG_TOTEM_CREATED, 1 + 8 + 4 + 4); @@ -105,7 +105,7 @@ void Totem::UnSummon(uint32 msTime) RemoveAurasDueToSpell(GetSpell(), GetGUID()); // clear owner's totem slot - for (uint8 i = SUMMON_SLOT_TOTEM; i < MAX_TOTEM_SLOT; ++i) + for (uint8 i = SUMMON_SLOT_TOTEM_FIRE; i < MAX_TOTEM_SLOT; ++i) { if (GetOwner()->m_SummonSlot[i] == GetGUID()) { diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 2a5588d03c..bc5ac1ec68 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -16,7 +16,6 @@ */ #include "Unit.h" -#include "AbstractFollower.h" #include "Battlefield.h" #include "BattlefieldMgr.h" #include "Battleground.h" @@ -325,8 +324,9 @@ Unit::Unit(bool isWorldObject) : m_ControlledByPlayer(false), movespline(new Movement::MoveSpline()), i_AI(nullptr), i_disabledAI(nullptr), m_AutoRepeatFirstCast(false), m_procDeep(0), m_removedAurasCount(0), i_motionMaster(new MotionMaster(this)), m_ThreatManager(this), - m_vehicle(nullptr), m_vehicleKit(nullptr), m_unitTypeMask(UNIT_MASK_NONE), m_Diminishing(), - m_HostileRefManager(this), _lastDamagedTime(0), m_spellHistory(new SpellHistory(this)) + m_vehicle(nullptr), m_vehicleKit(nullptr), m_unitTypeMask(UNIT_MASK_NONE), + m_Diminishing(), m_HostileRefManager(this), _lastDamagedTime(0), + m_spellHistory(new SpellHistory(this)) { m_objectType |= TYPEMASK_UNIT; m_objectTypeId = TYPEID_UNIT; @@ -9179,10 +9179,75 @@ void Unit::SetSpeedRate(UnitMoveType mtype, float rate) Movement::PacketSender(this, moveTypeToOpcode[mtype][0], moveTypeToOpcode[mtype][1], NULL_OPCODE, &extra).Send(); } -void Unit::RemoveAllFollowers() +void Unit::FollowTarget(Unit* target) { - while (!m_followingMe.empty()) - (*m_followingMe.begin())->SetTarget(nullptr); + if (!target) + { + TC_LOG_ERROR("entities.unit", "Unit::FollowTarget: Unit (%s) tried to follow a non-existant target.", GetGUID().ToString().c_str()); + return; + } + + // Determine follow configuration + bool joinFormation = false; // unit will follow its target in a generated formation shape and catches up to its target + bool catchUpToTarget = false; // unit will allign to the target speed and catches up to the target automatically + float distance = DEFAULT_FOLLOW_DISTANCE_PET; + + if (TempSummon* summon = ToTempSummon()) + { + if (SummonPropertiesEntry const* properties = summon->m_Properties) + { + // Allied summons, pet summons join a formation unless the following exceptions are being met. + if (properties->Control == SUMMON_CATEGORY_ALLY || properties->Control == SUMMON_CATEGORY_PET) + joinFormation = true; + + // Companion minipets will always be able to catch up to their target + if (properties->Slot == SUMMON_SLOT_MINIPET) + { + joinFormation = false; + catchUpToTarget = true; + distance = DEFAULT_FOLLOW_DISTANCE; + } + + // Quest npcs follow their target outside of formations + if (properties->Slot == SUMMON_SLOT_QUEST) + { + joinFormation = false; + distance = DEFAULT_FOLLOW_DISTANCE; + } + } + + // Pets and minions alwys move in a formation of their target + if (summon->IsPet()) + joinFormation = true; + } + + // Unit is already following its target + if (joinFormation && target->HasFormationFollower(this)) + return; + + GetMotionMaster()->MoveFollow(target, distance, DEFAULT_FOLLOW_ANGLE, joinFormation, catchUpToTarget); +} + +void Unit::RemoveFormationFollower(Unit* follower) +{ + for (FormationFollowerContainer::const_iterator itr = _formationFollowers.begin(); itr != _formationFollowers.end();) + { + Unit* follwingUnit = *itr; + // Cleaning up dead references while at it + if (!follwingUnit || follwingUnit == follower) + _formationFollowers.erase(itr); + else + itr++; + } +} + +bool Unit::HasFormationFollower(Unit* follower) const +{ + for (Unit* followingUnit : _formationFollowers) + if (followingUnit == follower) + return true; + + return false; } void Unit::setDeathState(DeathState s) @@ -10382,7 +10447,7 @@ void Unit::RemoveFromWorld() RemoveAreaAurasDueToLeaveWorld(); - RemoveAllFollowers(); + GetMotionMaster()->Clear(MOTION_SLOT_IDLE); // clear idle movement slot to finalize follow movement to unregister formation targets if (GetCharmerGUID()) { diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 110da9c64d..b912ec91e4 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -226,7 +226,6 @@ enum InventorySlot NULL_SLOT = 255 }; -struct AbstractFollower; struct FactionTemplateEntry; struct LiquidData; struct LiquidTypeEntry; @@ -927,14 +926,6 @@ enum ReactiveType }; #define MAX_REACTIVE 3 -#define SUMMON_SLOT_PET 0 -#define SUMMON_SLOT_TOTEM 1 -#define MAX_TOTEM_SLOT 5 -#define SUMMON_SLOT_MINIPET 5 -#define SUMMON_SLOT_QUEST 6 -#define MAX_SUMMON_SLOT 7 - -#define MAX_GAMEOBJECT_SLOT 4 enum PlayerTotemType { @@ -982,6 +973,7 @@ class TC_GAME_API Unit : public WorldObject typedef std::list AuraApplicationList; typedef std::vector> AuraApplicationProcContainer; + typedef std::vector FormationFollowerContainer; typedef std::map VisibleAuraMap; @@ -1792,9 +1784,13 @@ class TC_GAME_API Unit : public WorldObject void ModSpellCastTime(SpellInfo const* spellProto, int32& castTime, Spell* spell = nullptr); void ModSpellDurationTime(SpellInfo const* spellProto, int32& castTime, Spell* spell = nullptr); - void FollowerAdded(AbstractFollower* f) { m_followingMe.insert(f); } - void FollowerRemoved(AbstractFollower* f) { m_followingMe.erase(f); } - void RemoveAllFollowers(); + // Makes the unit follow the given target. Use this function above using the MotionMaster::MoveFollow for default follow behaivior. + void FollowTarget(Unit* target); + + FormationFollowerContainer GetFormationFollowers() { return _formationFollowers; } + void AddFormationFollower(Unit* follower) { _formationFollowers.push_back(follower); } + void RemoveFormationFollower(Unit* follower); + bool HasFormationFollower(Unit* follower) const; MotionMaster* GetMotionMaster() { return i_motionMaster; } MotionMaster const* GetMotionMaster() const { return i_motionMaster; } @@ -1896,8 +1892,6 @@ class TC_GAME_API Unit : public WorldObject void RewardRage(uint32 baseRage, bool attacker); - virtual float GetFollowAngle() const { return static_cast(M_PI/2); } - void OutDebugInfo() const; virtual bool IsLoading() const { return false; } bool IsDuringRemoveFromWorld() const {return m_duringRemoveFromWorld;} @@ -2023,7 +2017,6 @@ class TC_GAME_API Unit : public WorldObject virtual void ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional const& liquidData); void InterruptMovementBasedAuras(); - private: void UpdateSplineMovement(uint32 t_diff); @@ -2055,8 +2048,6 @@ class TC_GAME_API Unit : public WorldObject // Manage all Units that are threatened by us HostileRefManager m_HostileRefManager; - std::unordered_set m_followingMe; - GuidSet m_ComboPointHolders; RedirectThreatInfo _redirectThreadInfo; @@ -2072,6 +2063,8 @@ class TC_GAME_API Unit : public WorldObject SpellHistory* m_spellHistory; PositionUpdateInfo _positionUpdateInfo; + + FormationFollowerContainer _formationFollowers; }; namespace Trinity diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index 32241b5cb4..71eb077a5b 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -163,8 +163,11 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe switch (spellid) { case COMMAND_STAY: //flat=1792 //STAY - pet->StopMoving(); - pet->GetMotionMaster()->Clear(false); + if (pet->GetMotionMaster()->GetCurrentSlot() != MOTION_SLOT_CONTROLLED) + pet->StopMoving(); + + pet->GetMotionMaster()->Clear(MOTION_SLOT_IDLE); + pet->GetMotionMaster()->Clear(MOTION_SLOT_ACTIVE); pet->GetMotionMaster()->MoveIdle(); charmInfo->SetCommandState(COMMAND_STAY); @@ -179,7 +182,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe pet->AttackStop(); pet->InterruptNonMeleeSpells(false); pet->ClearInPetCombat(); - pet->GetMotionMaster()->MoveFollow(_player, PET_FOLLOW_DIST, ChaseAngle(pet->GetFollowAngle(), 0.f), true); + pet->FollowTarget(_player); charmInfo->SetCommandState(COMMAND_FOLLOW); charmInfo->SetIsCommandAttack(false); @@ -269,11 +272,10 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe } break; case COMMAND_MOVE_TO: - pet->StopMoving(); - pet->GetMotionMaster()->Clear(false); + pet->GetMotionMaster()->Clear(MOTION_SLOT_IDLE); + pet->GetMotionMaster()->MoveIdle(); pet->GetMotionMaster()->MovePoint(0, x, y, z); charmInfo->SetCommandState(COMMAND_MOVE_TO); - charmInfo->SetIsCommandAttack(false); charmInfo->SetIsAtStay(true); charmInfo->SetIsCommandFollow(false); diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 12394255f3..490a0d71ed 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -3798,6 +3798,22 @@ enum class SummonTitle : int32 aka = 13 }; +enum SummonSlot +{ + SUMMON_SLOT_PET = 0, + SUMMON_SLOT_TOTEM_FIRE = 1, + SUMMON_SLOT_TOTEM_EARTH = 2, + SUMMON_SLOT_TOTEM_WATER = 3, + SUMMON_SLOT_TOTEM_AIR = 4, + SUMMON_SLOT_MINIPET = 5, + SUMMON_SLOT_QUEST = 6, + + MAX_SUMMON_SLOT +}; + +#define MAX_TOTEM_SLOT 5 +#define MAX_GAMEOBJECT_SLOT 4 + enum EventId { EVENT_CHARGE = 1003, diff --git a/src/server/game/Movement/AbstractFollower.cpp b/src/server/game/Movement/AbstractFollower.cpp deleted file mode 100644 index 26cc3d5935..0000000000 --- a/src/server/game/Movement/AbstractFollower.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#include "AbstractFollower.h" -#include "Unit.h" - -void AbstractFollower::SetTarget(Unit* unit) -{ - if (unit == _target) - return; - - if (_target) - _target->FollowerRemoved(this); - _target = unit; - if (_target) - _target->FollowerAdded(this); -} diff --git a/src/server/game/Movement/AbstractFollower.h b/src/server/game/Movement/AbstractFollower.h deleted file mode 100644 index bd3930b89b..0000000000 --- a/src/server/game/Movement/AbstractFollower.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITY_ABSTRACTFOLLOWER_H -#define TRINITY_ABSTRACTFOLLOWER_H - -class Unit; - -struct AbstractFollower -{ - public: - AbstractFollower(Unit* target = nullptr) { SetTarget(target); } - ~AbstractFollower() { SetTarget(nullptr); } - - void SetTarget(Unit* unit); - Unit* GetTarget() const { return _target; } - - private: - Unit* _target = nullptr; -}; - -#endif diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index d3ce768784..7046f3d205 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -158,6 +158,9 @@ MovementSlot MotionMaster::GetCurrentSlot() const if (_slot[MOTION_SLOT_ACTIVE]) return MOTION_SLOT_ACTIVE; + if (_slot[MOTION_SLOT_CONTROLLED]) + return MOTION_SLOT_CONTROLLED; + return MAX_MOTION_SLOT; } @@ -239,7 +242,7 @@ void MotionMaster::MoveTargetedHome() else { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveTargetedHome: '%s', starts following '%s'", _owner->GetGUID().ToString().c_str(), target->GetGUID().ToString().c_str()); - Mutate(new FollowMovementGenerator(target, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE), MOTION_SLOT_ACTIVE); + owner->FollowTarget(target); } } @@ -252,7 +255,7 @@ void MotionMaster::MoveRandom(float spawndist) } } -void MotionMaster::MoveFollow(Unit* target, float dist, ChaseAngle angle, bool allignToTargetSpeed, MovementSlot slot) +void MotionMaster::MoveFollow(Unit* target, float dist, float angle, bool joinFormation /*= false*/, bool catchUpToTarget /*= false*/, MovementSlot slot /*= MOTION_SLOT_IDLE*/) { // ignore movement request if target not exist if (!target || target == _owner) @@ -260,7 +263,7 @@ void MotionMaster::MoveFollow(Unit* target, float dist, ChaseAngle angle, bool a TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveFollow: '%s', starts following '%s'", _owner->GetGUID().ToString().c_str(), target->GetGUID().ToString().c_str()); - Mutate(new FollowMovementGenerator(target, dist, angle, allignToTargetSpeed), slot); + Mutate(new FollowMovementGenerator(target, dist, angle, joinFormation, catchUpToTarget), slot); } void MotionMaster::MoveChase(Unit* target, float dist, Optional angle) diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h index 38cbdb64c3..cd556cc674 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -147,7 +147,7 @@ class TC_GAME_API MotionMaster void MoveIdle(); 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 MoveFollow(Unit* target, float dist, float angle, bool joinFormation = false, bool catchUpToTarget = 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)); } diff --git a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator .cpp b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.cpp similarity index 67% rename from src/server/game/Movement/MovementGenerators/ChaseMovementGenerator .cpp rename to src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.cpp index 3339f680e6..cefa465526 100644 --- a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator .cpp +++ b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.cpp @@ -26,23 +26,23 @@ #include "Util.h" #include "Vehicle.h" -static bool IsMutualChase(Unit* owner, Unit* target) +static bool IsMutualChase(Unit const* owner, Unit const* target) { - MovementGenerator const* gen = target->GetMotionMaster()->topOrNull(); + MovementGenerator* gen = target->GetMotionMaster()->topOrNull(); if (!gen || gen->GetMovementGeneratorType() != CHASE_MOTION_TYPE) return false; - return (static_cast(gen)->GetTarget() == owner); + return (static_cast(gen)->GetTarget() == owner); } -static void DoMovementInform(Unit* owner, Unit* target) +static void DoMovementInform(Unit* owner, Unit const* target) { if (Creature* cOwner = owner->ToCreature()) if (CreatureAI* ai = cOwner->AI()) ai->MovementInform(CHASE_MOTION_TYPE, target->GetGUID().GetCounter()); } -static bool PositionOkay(Unit* owner, Unit* target, float distance, Optional angle) +static bool PositionOkay(Unit const* owner, Unit const* target, float distance, Optional angle) { float const dist = owner->GetExactDist2d(target); @@ -72,7 +72,7 @@ inline float GetChaseRange(Unit const* owner, Unit const* target) return hitboxSum; } -ChaseMovementGenerator::ChaseMovementGenerator(Unit* target, float range, Optional angle) : AbstractFollower(ASSERT_NOTNULL(target)), _range(range), _angle(angle) { } +ChaseMovementGenerator::ChaseMovementGenerator(Unit* target, float range, Optional angle) : _target(target), _range(range), _angle(angle) { } ChaseMovementGenerator::~ChaseMovementGenerator() = default; void ChaseMovementGenerator::Initialize(Unit* owner) @@ -91,12 +91,11 @@ bool ChaseMovementGenerator::Update(Unit* owner, uint32 diff) return false; // our target might have gone away - Unit* const target = GetTarget(); - if (!target || !target->IsInWorld()) + if (!_target || !_target->IsInWorld()) return false; // the owner might've selected a different target (feels like we shouldn't check this here...) - if (owner->GetVictim() != target) + if (owner->GetVictim() != _target) return false; // the owner might be unable to move (rooted or casting), pause movement @@ -115,9 +114,9 @@ bool ChaseMovementGenerator::Update(Unit* owner, uint32 diff) if (Creature* cOwner = owner->ToCreature()) cOwner->SetCannotReachTarget(false); - owner->SetInFront(target); + owner->SetInFront(_target); owner->ClearUnitState(UNIT_STATE_CHASE_MOVE); - DoMovementInform(owner, target); + DoMovementInform(owner, _target); return true; } @@ -125,21 +124,21 @@ bool ChaseMovementGenerator::Update(Unit* owner, uint32 diff) Creature* creature = owner->ToCreature(); if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && creature) { - if (!target->isInAccessiblePlaceFor(creature)) + if (!_target->isInAccessiblePlaceFor(creature)) { creature->SetCannotReachTarget(true); creature->StopMoving(); return true; } } - else if (creature && !target->isInAccessiblePlaceFor(creature)) + else if (creature && !_target->isInAccessiblePlaceFor(creature)) { creature->SetCannotReachTarget(true); return true; } - bool const mutualChase = IsMutualChase(owner, target); - float const chaseRange = GetChaseRange(owner, target); + 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; @@ -152,12 +151,12 @@ bool ChaseMovementGenerator::Update(Unit* owner, uint32 diff) { _nextRepositioningTimer.Reset(REPOSITION_MOVEMENT_INTERVAL); - if (!owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && owner->IsCreature() && !target->isMoving()) + if (!owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && owner->IsCreature() && !_target->isMoving()) { // Owner is too close to its target. Step back. - if (owner->GetBoundaryRadius() <= 5.f && owner->GetExactDist2d(target) < owner->GetBoundaryRadius()) + if (owner->GetBoundaryRadius() <= 5.f && owner->GetExactDist2d(_target) < owner->GetBoundaryRadius()) { - LaunchMovement(owner, target, chaseRange, true, mutualChase); + LaunchMovement(owner, chaseRange, true, mutualChase); return true; } } @@ -168,22 +167,22 @@ bool ChaseMovementGenerator::Update(Unit* owner, uint32 diff) _nextMovementTimer.Reset(CHASE_MOVEMENT_INTERVAL); // Target has moved since we last checked its position. Handle new cases - if (!_lastTargetPosition || target->GetPosition() != _lastTargetPosition.get()) + if (!_lastTargetPosition || _target->GetPosition() != _lastTargetPosition.get()) { // Create new snapshot of our target's position - _lastTargetPosition = target->GetPosition(); + _lastTargetPosition = _target->GetPosition(); - if (PositionOkay(owner, target, rangeTolerance, chaseAngle)) + if (PositionOkay(owner, _target, rangeTolerance, chaseAngle)) { - if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE) && !target->isMoving() && !mutualChase) + 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, target, chaseRange, false, mutualChase); + 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); } } @@ -197,37 +196,37 @@ void ChaseMovementGenerator::Finalize(Unit* owner) cOwner->SetCannotReachTarget(false); } -void ChaseMovementGenerator::LaunchMovement(Unit* owner, Unit* target, float chaseRange, bool backward /*= false*/, bool mutualChase /*= false*/) +void ChaseMovementGenerator::LaunchMovement(Unit* owner, float chaseRange, bool backward /*= false*/, bool mutualChase /*= false*/) { // Owner may launch a new spline - Position dest = target->GetVehicle() ? target->GetVehicle()->GetBase()->GetPosition() : target->GetPosition(); + Position dest = _target->GetVehicle() ? _target->GetVehicle()->GetBase()->GetPosition() : _target->GetPosition(); // Predict chase destination to keep up with chase target - bool predictDestination = !backward && !mutualChase && target->isMoving(); + bool predictDestination = !backward && !mutualChase && _target->isMoving(); if (predictDestination) { UnitMoveType moveType = MOVE_RUN; - if (target->CanFly()) - moveType = target->HasUnitMovementFlag(MOVEMENTFLAG_BACKWARD) ? MOVE_FLIGHT_BACK : MOVE_FLIGHT; + if (_target->CanFly()) + moveType = _target->HasUnitMovementFlag(MOVEMENTFLAG_BACKWARD) ? MOVE_FLIGHT_BACK : MOVE_FLIGHT; else { - if (target->IsWalking()) + if (_target->IsWalking()) moveType = MOVE_WALK; else - moveType = target->HasUnitMovementFlag(MOVEMENTFLAG_BACKWARD) ? MOVE_RUN_BACK : MOVE_RUN; + moveType = _target->HasUnitMovementFlag(MOVEMENTFLAG_BACKWARD) ? MOVE_RUN_BACK : MOVE_RUN; } - float additionalRange = target->GetSpeed(moveType) * 0.5f; + float additionalRange = _target->GetSpeed(moveType) * 0.5f; if (!owner->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING)) - target->MovePositionToFirstCollision(dest, additionalRange, _angle ? Position::NormalizeOrientation(target->GetOrientation() - _angle->RelativeAngle + float(M_PI)) : target->GetRelativeAngle(owner) + float(M_PI)); + _target->MovePositionToFirstCollision(dest, additionalRange, _angle ? Position::NormalizeOrientation(_target->GetOrientation() - _angle->RelativeAngle + float(M_PI)) : _target->GetRelativeAngle(owner) + float(M_PI)); else - target->GetNearPoint(owner, dest.m_positionX, dest.m_positionY, dest.m_positionZ, additionalRange - target->GetCombatReach() - owner->GetCombatReach(), Position::NormalizeOrientation(target->GetOrientation() + float(M_PI) + (_angle ? _angle->RelativeAngle : 0.f))); + _target->GetNearPoint(owner, dest.m_positionX, dest.m_positionY, dest.m_positionZ, additionalRange - _target->GetCombatReach() - owner->GetCombatReach(), Position::NormalizeOrientation(_target->GetOrientation() + float(M_PI) + (_angle ? _angle->RelativeAngle : 0.f))); } else if (!owner->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING)) - target->MovePositionToFirstCollision(dest, chaseRange, _angle ? target->NormalizeOrientation(target->GetOrientation() - _angle->RelativeAngle) : target->GetRelativeAngle(owner)); + _target->MovePositionToFirstCollision(dest, chaseRange, _angle ? _target->NormalizeOrientation(_target->GetOrientation() - _angle->RelativeAngle) : _target->GetRelativeAngle(owner)); else - target->GetNearPoint(owner, dest.m_positionX, dest.m_positionY, dest.m_positionZ, chaseRange - target->GetCombatReach() - owner->GetCombatReach(), Position::NormalizeOrientation(target->GetOrientation() + (_angle ? _angle->RelativeAngle : 0.f))); + _target->GetNearPoint(owner, dest.m_positionX, dest.m_positionY, dest.m_positionZ, chaseRange - _target->GetCombatReach() - owner->GetCombatReach(), Position::NormalizeOrientation(_target->GetOrientation() + (_angle ? _angle->RelativeAngle : 0.f))); owner->UpdateAllowedPositionZ(dest.GetPositionX(), dest.GetPositionY(), dest.m_positionZ); @@ -250,7 +249,7 @@ void ChaseMovementGenerator::LaunchMovement(Unit* owner, Unit* target, float cha if (backward) init.SetBackward(); else - init.SetFacing(target); + init.SetFacing(_target); init.Launch(); diff --git a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h index 2fd7891a50..bc5f40eb07 100644 --- a/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/ChaseMovementGenerator.h @@ -18,7 +18,6 @@ #ifndef TRINITY_CHASEMOVEMENTGENERATOR_H #define TRINITY_CHASEMOVEMENTGENERATOR_H -#include "AbstractFollower.h" #include "MovementGenerator.h" #include "Optional.h" #include "Timer.h" @@ -26,7 +25,7 @@ class PathGenerator; class Unit; -class ChaseMovementGenerator : public MovementGenerator, public AbstractFollower +class ChaseMovementGenerator : public MovementGenerator { public: MovementGeneratorType GetMovementGeneratorType() const override { return CHASE_MOTION_TYPE; } @@ -41,8 +40,9 @@ class ChaseMovementGenerator : public MovementGenerator, public AbstractFollower void UnitSpeedChanged() override { _lastTargetPosition.reset(); } + Unit const* GetTarget() { return _target; } private: - void LaunchMovement(Unit* owner, Unit* target, float chaseRange, bool backward = false, bool mutualChase = false); + void LaunchMovement(Unit* owner, float chaseRange, bool backward = false, bool mutualChase = false); 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 @@ -51,6 +51,7 @@ class ChaseMovementGenerator : public MovementGenerator, public AbstractFollower TimeTrackerSmall _nextRepositioningTimer; Optional _lastTargetPosition; + Unit* _target; float const _range; Optional const _angle; }; diff --git a/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.cpp index 9c7d6c80b1..33fbfda86a 100644 --- a/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.cpp @@ -17,6 +17,7 @@ #include "FollowMovementGenerator.h" #include "CreatureAI.h" +#include "EventProcessor.h" #include "MoveSpline.h" #include "MoveSplineInit.h" #include "PathGenerator.h" @@ -24,215 +25,385 @@ #include "TemporarySummon.h" #include "Unit.h" #include "Util.h" +#include -FollowMovementGenerator::FollowMovementGenerator(Unit* target, float range, ChaseAngle angle, bool allignToTargetSpeed) : AbstractFollower(ASSERT_NOTNULL(target)), - _range(range), _useTargetSpeed(allignToTargetSpeed), _hasStopped(false), _angle(angle) { } - -FollowMovementGenerator::~FollowMovementGenerator() { } - +// Helpers inline UnitMoveType SelectSpeedType(uint32 moveFlags) { if (moveFlags & MOVEMENTFLAG_FLYING) { - if (moveFlags & MOVEMENTFLAG_BACKWARD /*&& speed_obj.flight >= speed_obj.flight_back*/) + if (moveFlags & MOVEMENTFLAG_BACKWARD) return MOVE_FLIGHT_BACK; else return MOVE_FLIGHT; } else if (moveFlags & MOVEMENTFLAG_SWIMMING) { - if (moveFlags & MOVEMENTFLAG_BACKWARD /*&& speed_obj.swim >= speed_obj.swim_back*/) + if (moveFlags & MOVEMENTFLAG_BACKWARD) return MOVE_SWIM_BACK; else return MOVE_SWIM; } else if (moveFlags & MOVEMENTFLAG_WALKING) - { - //if (speed_obj.run > speed_obj.walk) return MOVE_WALK; - } - else if (moveFlags & MOVEMENTFLAG_BACKWARD /*&& speed_obj.run >= speed_obj.run_back*/) + else if (moveFlags & MOVEMENTFLAG_BACKWARD) return MOVE_RUN_BACK; - // Flying creatures use MOVEMENTFLAG_CAN_FLY or MOVEMENTFLAG_DISABLE_GRAVITY - // Run speed is their default flight speed. return MOVE_RUN; } +inline bool IsTargetMoving(Unit const* target) +{ + return target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_BACKWARD | MOVEMENTFLAG_STRAFE_LEFT | MOVEMENTFLAG_STRAFE_RIGHT) || !target->movespline->Finalized(); +} + +inline float GetVelocity(Unit* owner, Unit* target, bool catchUp) +{ + float targetSpeed = 0.f; + float velocity = 0.f; + if (target->IsPlayer()) + targetSpeed = target->GetSpeed(SelectSpeedType(target->m_movementInfo.GetMovementFlags())); + else + { + if (!target->movespline->Finalized()) + targetSpeed = target->movespline->Velocity(); + else + targetSpeed = target->GetSpeed(MOVE_RUN); + } + + if (catchUp) + velocity = targetSpeed; + else + { + if (owner->IsFlying()) + velocity = owner->GetSpeed(MOVE_FLIGHT); + else if (!owner->IsWalking()) + velocity = owner->GetSpeed(MOVE_RUN); + else + velocity = owner->GetSpeed(MOVE_WALK); + + // Owner might be able to walk faster than its target so we limit the speed + //velocity = std::min(velocity, targetSpeed); + } + + return velocity; +} + +static void ApplyCatchUpMod(Unit* owner, Position dest, float& velocity) +{ + float distance = owner->GetExactDist2d(dest); + + if (dest.HasInArc(float(M_PI), owner)) // owner is beyond default destination. throttle speed. + AddPct(velocity, -((distance / velocity) * 100.f)); + else // owner is behind destination, catch up. + AddPct(velocity, ((distance / velocity) * 100.f)); +} + static void DoMovementInform(Unit* owner, Unit* target) { if (owner->GetTypeId() != TYPEID_UNIT) return; + if (UnitAI* ai = owner->GetAI()) static_cast(ai)->MovementInform(FOLLOW_MOTION_TYPE, target->GetGUID().GetCounter()); } +static void GetFollowOffsets(uint8 followerIndex, float& distance, float& relativeAngle) +{ + uint8 rowOffset = 0; + + if (followerIndex >= DEFAULT_ROW_FOLLOWERS) + rowOffset = std::floor((followerIndex - DEFAULT_ROW_FOLLOWERS) / MAX_FOLLOWERS_PER_ROW); + else + { + // Default left and right follower slots + distance = STRAIGHT_FOLLOW_DISTANCE; + relativeAngle = followerIndex == 0 ? float(M_PI_2) : float(-M_PI_2); + return; + } + + // Further follower slots. Calculating a place in further rows + uint8 rowIndex = (followerIndex - DEFAULT_ROW_FOLLOWERS) % MAX_FOLLOWERS_PER_ROW; + + // Blizzard assigns new followers in a left-right repeat pattern rather than just going from left to right + bool left = rowIndex % 2; + Position pos; + pos.RelocateOffset({ 0.f, left ? -STRAIGHT_FOLLOW_DISTANCE : STRAIGHT_FOLLOW_DISTANCE }); + + if (rowIndex <= 3) + { + pos.RelocateOffset({ -(STRAIGHT_FOLLOW_DISTANCE * rowOffset) }); + + // Inner and outer positions + float angle = float(M_PI); + if (rowIndex <= 1) + angle += left ? float(-(M_PI_4 / 2)) : float(M_PI_4 / 2); + else + angle += left ? float(M_PI_4 / 2) : float(-(M_PI_4 / 2)); + + pos.m_positionX += std::cos(angle) * SIDE_FOLLOW_DISTANCE; + pos.m_positionY += std::sin(angle) * SIDE_FOLLOW_DISTANCE; + } + else + { + // Middle positions + pos.RelocateOffset({ -(STRAIGHT_FOLLOW_DISTANCE + STRAIGHT_FOLLOW_DISTANCE * rowOffset) }); + } + + distance = Position().GetExactDist2d(pos); + relativeAngle = Position().GetRelativeAngle(pos); +} + +FollowMovementGenerator::FollowMovementGenerator(Unit* target, Optional distance, Optional angle, bool joinFormation /*= true*/, bool catchUpToTarget /*= false*/) : + _target(target), _joinFormation(joinFormation), _catchUpToTarget(catchUpToTarget) +{ + _distance = distance ? distance.get() : 0.f; + _angle = angle ? angle.get() : 0.f; +} + +FollowMovementGenerator::~FollowMovementGenerator() { } + void FollowMovementGenerator::Initialize(Unit* owner) { owner->AddUnitState(UNIT_STATE_FOLLOW); - _hasStopped = false; + + // owner joins a organized follower formation. + if (_joinFormation) + { + _target->AddFormationFollower(owner); + UpdateFollowFormation(); + } + _followMovementTimer.Reset(0); -} - -bool FollowMovementGenerator::Update(Unit* owner, uint32 diff) -{ - // owner might be dead or gone - if (!owner->IsAlive()) - return false; - - // our target might have gone away - Unit* const target = GetTarget(); - if (!target) - return false; - - // follower cannot move at the moment - if (owner->HasUnitState(UNIT_STATE_NOT_MOVE) || owner->IsMovementPreventedByCasting()) - { - owner->StopMoving(); - _hasStopped = true; - _followMovementTimer.Reset(0); - return true; - } - - _followMovementTimer.Update(diff); - if (_followMovementTimer.Passed()) - { - // Follow target is moving, launch our follow movement - if (!target->movespline->Finalized() || target->isMoving()) - { - Position dest = target->GetPosition(); - - float offset = 0.f; - - // Strafe handling for player sidewards movement - if (target->IsPlayer()) - { - if (target->HasUnitMovementFlag(MOVEMENTFLAG_STRAFE_LEFT)) - offset += float(M_PI / 2); - - if (target->HasUnitMovementFlag(MOVEMENTFLAG_STRAFE_RIGHT)) - offset -= float(M_PI / 2); - - if (target->HasUnitMovementFlag(MOVEMENTFLAG_BACKWARD)) - offset += float(M_PI); - - // Player moves forward while strafing, cut strafe offset in half - if (offset != 0.f && target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD)) - offset *= 0.5f; - } - - float angle = !target->HasUnitMovementFlag(MOVEMENTFLAG_BACKWARD) ? _angle.RelativeAngle : -_angle.RelativeAngle; - - // Let's start with a cheap base destination calculation - dest.m_positionX += std::cos(Position::NormalizeOrientation(target->GetOrientation() + offset + angle)) * (_range + target->GetBoundaryRadius() + owner->GetBoundaryRadius()); - dest.m_positionY += std::sin(Position::NormalizeOrientation(target->GetOrientation() + offset + angle)) * (_range + target->GetBoundaryRadius() + owner->GetBoundaryRadius()); - - // Determine our velocity - float velocity = 0.f; - if (_useTargetSpeed) - { - if (target->IsPlayer()) - velocity = target->GetSpeed(SelectSpeedType(target->m_movementInfo.GetMovementFlags())); - else - { - if (!target->movespline->Finalized()) - velocity = target->movespline->Velocity(); - else - velocity = target->GetSpeed(MOVE_RUN); - } - - - // Catchup / throttle speed modifier calculation - if (velocity > 0.f) - { - float distance = owner->GetExactDist2d(dest); - if (distance > 0.5f) - { - float distMod = 1.f; - if (!dest.HasInArc(float(M_PI), owner)) // Follower is in falling back, catch up - distMod += std::min(distance * 0.1f, 0.5f); - else if (distance > (velocity / 2)) // Follower is beyond destination, fall back - distMod -= std::min((distance - (velocity / 2)), 0.5f); - - velocity *= distMod; - } - } - } - - // Predicting our follow destination (roundabout velocity * 2 according to sniff data) - dest.m_positionX += std::cos(Position::NormalizeOrientation(target->GetOrientation() + offset)) * (velocity * 2); - dest.m_positionY += std::sin(Position::NormalizeOrientation(target->GetOrientation() + offset)) * (velocity * 2); - - // Now we calculate out actual destination data - if (!owner->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING)) - { - float relativeAngle = target->GetRelativeAngle(dest); - float distance = target->GetExactDist2d(dest); - dest = target->GetPosition(); - target->MovePositionToFirstCollision(dest, distance, relativeAngle); - } - - Movement::MoveSplineInit init(owner); - init.MoveTo(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()); - if (velocity > 0.f) - init.SetVelocity(velocity); - init.Launch(); - - _hasStopped = false; - owner->AddUnitState(UNIT_STATE_FOLLOW_MOVE); - _followMovementTimer.Reset(FOLLOW_MOVEMENT_INTERVAL); - } - } - - // Follow target has stopped moving, allign to current position and inform AI - // (Note: for WoD and beyond this rule only applies to regular followers. Pets, Guardians, Minions etc. stop in the next update cycle) - if (!_hasStopped && target->movespline->Finalized() && !target->isMoving()) - { - // select angle - float tAngle = 0.f; - float const curAngle = target->GetRelativeAngle(owner); - 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(); - else - tAngle = _angle.LowerBound(); - } - - Position dest = target->GetPosition(); - if (!owner->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING)) - target->MovePositionToFirstCollision(dest, _range + target->GetBoundaryRadius() + owner->GetBoundaryRadius(), tAngle); - else - { - dest.m_positionX += std::cos(Position::NormalizeOrientation(target->GetOrientation() + tAngle)) * (_range + target->GetBoundaryRadius() + owner->GetBoundaryRadius()); - dest.m_positionY += std::sin(Position::NormalizeOrientation(target->GetOrientation() + tAngle)) * (_range + target->GetBoundaryRadius() + owner->GetBoundaryRadius()); - } - - Movement::MoveSplineInit init(owner); - init.MoveTo(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()); - init.SetFacing(target->GetOrientation()); - init.Launch(); - - _followMovementTimer.Reset(FOLLOW_MOVEMENT_INTERVAL); // block excessive movement launching via stutter stepping - _hasStopped = true; - return true; - } - - if (owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE) && owner->movespline->Finalized()) - { - owner->ClearUnitState(UNIT_STATE_FOLLOW_MOVE); - owner->SetFacingTo(target->GetOrientation()); // according to sniffs we launch a extra spline just for the facing - DoMovementInform(owner, target); - } - - return true; + _events.ScheduleEvent(EVENT_ALLIGN_TO_TARGET, Milliseconds(ALLIGN_MOVEMENT_INTERVAL)); } void FollowMovementGenerator::Finalize(Unit* owner) { owner->ClearUnitState(UNIT_STATE_FOLLOW | UNIT_STATE_FOLLOW_MOVE); + + if (_joinFormation && _target) + { + _target->RemoveFormationFollower(owner); + UpdateFollowFormation(); + } +} + +void FollowMovementGenerator::Reset(Unit* /*owner*/) +{ + _followMovementTimer.Reset(0); + _events.ScheduleEvent(EVENT_ALLIGN_TO_TARGET, 1ms); +} + +bool FollowMovementGenerator::Update(Unit* owner, uint32 diff) +{ + // Owner might be dead or gone + if (!owner->IsAlive()) + return false; + + // Our target might have gone away + if (!_target || !_target->IsInWorld()) + return false; + + // Follower cannot move at the moment + if (owner->HasUnitState(UNIT_STATE_NOT_MOVE) || owner->IsMovementPreventedByCasting()) + { + owner->StopMoving(); + _followMovementTimer.Reset(0); + return true; + } + + // Default follow movement procedure when target is moving + _followMovementTimer.Update(diff); + if (_followMovementTimer.Passed()) + { + if (IsTargetMoving(_target)) + { + _events.Reset(); + LaunchMovement(owner); + return true; + } + else if (owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE)) + { + owner->StopMoving(); + owner->ClearUnitState(UNIT_STATE_FOLLOW_MOVE); + DoMovementInform(owner, _target); + _events.ScheduleEvent(EVENT_ALLIGN_TO_TARGET, Milliseconds(ALLIGN_MOVEMENT_INTERVAL)); + } + } + _followMovementTimer.Reset(FOLLOW_MOVEMENT_INTERVAL); + + _events.Update(diff); + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_ALLIGN_TO_TARGET: + { + Position pos = _target->GetPosition(); + _target->MovePositionToFirstCollision(pos, _distance, _angle); + Movement::MoveSplineInit init (owner); + init.MoveTo(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); + uint32 duration = std::max(1, init.Launch()); + _events.ScheduleEvent(EVENT_ALLIGN_TO_FACING_DIRECTION, Milliseconds(duration + FOLLOW_MOVEMENT_INTERVAL)); + break; + } + case EVENT_ALLIGN_TO_FACING_DIRECTION: + owner->SetFacingTo(_target->GetOrientation()); + break; + default: + break; + } + } + + /* + // Target is currently not moving. Return to intended follow position after a short delay. + if (!owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE) && !_faceTarget) + { + _allignMovementTimer.Update(diff); + _facingMovementTimer.Update(diff); + + if (_allignMovementTimer.Passed() && _returnState == RETURN_STATE_NONE) + { + // Return to our follow target + Position dest = _target->GetPosition(); + _target->MovePositionToFirstCollision(dest, _range, _angle.RelativeAngle); + + // Launch a alligning spline when we are too far away from our follow target + if (owner->GetExactDist2d(dest) > 1.f) + { + Movement::MoveSplineInit init(owner); + init.MoveTo(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()); + init.SetVelocity(_target->GetSpeed(SelectSpeedType(_target->GetUnitMovementFlags()))); + init.Launch(); + _returnState = RETURN_STATE_RETURNING; + _facingMovementTimer.Reset(FOLLOW_MOVEMENT_INTERVAL); + } + else + _returnState = RETURN_STATE_RETURNED; + } + + if (_facingMovementTimer.Passed() && _returnState == RETURN_STATE_RETURNING) + { + if (owner->movespline->Finalized()) + { + owner->SetFacingTo(_target->GetOrientation()); + _returnState = RETURN_STATE_RETURNED; + } + else + _facingMovementTimer.Reset(FOLLOW_MOVEMENT_INTERVAL); + } + + _followMovementTimer.Reset(0); + } + + _followMovementTimer.Update(diff); + if (_followMovementTimer.Passed()) + { + // Target is currently moving, launch our next spline + if (!_target->movespline->Finalized() || _target->isMoving()) + LaunchMovement(owner, false); + else if (owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE) && _target->movespline->Finalized() && !_target->isMoving()) + { + // Owner is no longer moving. Prepare out allignment movement + if (!_faceTarget) + { + owner->StopMoving(); + owner->ClearUnitState(UNIT_STATE_FOLLOW_MOVE); + _allignMovementTimer.Reset(ALLING_MOVEMENT_INTERVAL); + DoMovementInform(owner, _target); + } + else + LaunchMovement(owner, true); + } + + _followMovementTimer.Reset(FOLLOW_MOVEMENT_INTERVAL); + } + + */ + return true; +} + +void FollowMovementGenerator::UpdateFollowFormation() +{ + uint8 followSlot = 0; + for (Unit* follower : _target->GetFormationFollowers()) + { + for (uint8 slot = MOTION_SLOT_IDLE; slot < MAX_MOTION_SLOT; ++slot) + { + MovementGenerator* moveGen = follower->GetMotionMaster()->GetMotionSlot(MovementSlot(slot)); + if (!moveGen || moveGen->GetMovementGeneratorType() != FOLLOW_MOTION_TYPE) + continue; + + if (FollowMovementGenerator* followMoveGen = static_cast(moveGen)) + followMoveGen->UpdateFormationFollowOffsets(followSlot); + } + + ++followSlot; + } +} + +void FollowMovementGenerator::UpdateFormationFollowOffsets(uint32 slot) +{ + // Do not update follow values for follow targets that are not part of the follow formation. + if (!_joinFormation) + return; + + GetFollowOffsets(slot, _distance, _angle); +}; + +void FollowMovementGenerator::LaunchMovement(Unit* owner) +{ + Position dest = _target->GetPosition(); + float offset = 0.f; + + // Strafe handling for player sidewards movement + if (_target->IsPlayer()) + { + if (_target->HasUnitMovementFlag(MOVEMENTFLAG_STRAFE_LEFT)) + offset = _target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) ? float(M_PI_4) : float(M_PI_2); + + if (_target->HasUnitMovementFlag(MOVEMENTFLAG_STRAFE_RIGHT)) + offset = _target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) ? -float(M_PI_4) : -float(M_PI_2); + + if (_target->HasUnitMovementFlag(MOVEMENTFLAG_BACKWARD)) + offset += float(M_PI); + } + + // Let's start with a cheap base destination calculation + dest.m_positionX += std::cos(Position::NormalizeOrientation(_target->GetOrientation() + _angle)) * _distance; + dest.m_positionY += std::sin(Position::NormalizeOrientation(_target->GetOrientation() + _angle)) * _distance; + dest.SetOrientation(dest.GetOrientation() + offset); + + // Calculate velocity based on target's speed values + float velocity = GetVelocity(owner, _target, (_joinFormation || _catchUpToTarget)); + if (velocity == 0.f) + return; + + // Calculate catchup speed values if owner follows its target in a formation + if (_joinFormation || _catchUpToTarget) + ApplyCatchUpMod(owner, dest, velocity); + + if (velocity <= 0.1f) + return; + + // Predicting our follow destination + dest.m_positionX += std::cos(Position::NormalizeOrientation(_target->GetOrientation() + offset)) * velocity; + dest.m_positionY += std::sin(Position::NormalizeOrientation(_target->GetOrientation() + offset)) * velocity; + + // Now we calculate our actual destination data + if (!owner->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING)) + { + float relativeAngle = _target->GetRelativeAngle(dest); + float distance = _target->GetExactDist2d(dest); + dest = _target->GetPosition(); + _target->MovePositionToFirstCollision(dest, distance, relativeAngle); + } + + Movement::MoveSplineInit init(owner); + init.MoveTo(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()); + init.SetVelocity(velocity); + init.Launch(); + + owner->AddUnitState(UNIT_STATE_FOLLOW_MOVE); } diff --git a/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.h b/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.h index 8908c798ec..949c626f50 100644 --- a/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.h @@ -18,36 +18,56 @@ #ifndef TRINITY_FOLLOWMOVEMENTGENERATOR_H #define TRINITY_FOLLOWMOVEMENTGENERATOR_H -#include "AbstractFollower.h" #include "MovementGenerator.h" +#include "EventMap.h" #include "Optional.h" #include "Timer.h" class PathGenerator; class Unit; -class FollowMovementGenerator : public MovementGenerator, public AbstractFollower +enum Events +{ + EVENT_ALLIGN_TO_TARGET = 1, + EVENT_ALLIGN_TO_FACING_DIRECTION, +}; + +constexpr uint8 MAX_FOLLOWERS_PER_ROW = 6; +constexpr uint8 DEFAULT_ROW_FOLLOWERS = 2; +constexpr float STRAIGHT_FOLLOW_DISTANCE = 3.f; +constexpr float SIDE_FOLLOW_DISTANCE = 3.3541f; + +class FollowMovementGenerator : public MovementGenerator { public: MovementGeneratorType GetMovementGeneratorType() const override { return FOLLOW_MOTION_TYPE; } - FollowMovementGenerator(Unit* target, float range, ChaseAngle angle, bool alligntToTargetSpeed = false); + FollowMovementGenerator(Unit* target, Optional distance, Optional angle, bool joinFormation = false, bool catchUpToTarget = false); ~FollowMovementGenerator(); 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 Reset(Unit* /*owner*/) override; + bool Update(Unit* owner, uint32 diff) override; + + Unit const* GetTarget() { return _target; } private: - static constexpr uint32 FOLLOW_MOVEMENT_INTERVAL = 800; // sniffed (2 batch update cycles) - // static inline const when? + void UpdateFollowFormation(); + void UpdateFormationFollowOffsets(uint32 slot); + void LaunchMovement(Unit* owner); + + static constexpr uint32 FOLLOW_MOVEMENT_INTERVAL = 400; // sniffed (1 batch update cycle) + static constexpr uint32 ALLIGN_MOVEMENT_INTERVAL = 2000; // sniffed (5 batch update cycles) + + Unit* _target; + float _distance; + float _angle; + bool _joinFormation; + bool _catchUpToTarget; - float const _range; - bool const _useTargetSpeed; - bool _hasStopped; - ChaseAngle const _angle; TimeTrackerSmall _followMovementTimer; + EventMap _events; }; #endif diff --git a/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.cpp index 0ea55c1d1f..ce04f7949c 100644 --- a/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.cpp @@ -23,8 +23,8 @@ #include "MoveSpline.h" #include "FormationMovementGenerator.h" -FormationMovementGenerator::FormationMovementGenerator(Unit* leader, float range, float angle, uint32 point1, uint32 point2) : AbstractFollower(ASSERT_NOTNULL(leader)), -_range(range), _angle(angle), _point1(point1), _point2(point2), _lastLeaderSplineID(0), _hasPredictedDestination(false) { } +FormationMovementGenerator::FormationMovementGenerator(Unit* leader, float range, float angle, uint32 point1, uint32 point2) : + _leader(leader), _range(range), _angle(angle), _point1(point1), _point2(point2), _lastLeaderSplineID(0), _hasPredictedDestination(false) { } void FormationMovementGenerator::DoInitialize(Creature* owner) { @@ -34,7 +34,7 @@ void FormationMovementGenerator::DoInitialize(Creature* owner) bool FormationMovementGenerator::DoUpdate(Creature* owner, uint32 diff) { - Unit* target = GetTarget(); + Unit* target = _leader; if (!owner || !target) return false; diff --git a/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.h b/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.h index d991c14fb6..f72433b1ff 100644 --- a/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.h @@ -18,11 +18,10 @@ #ifndef TRINITY_FORMATIONMOVEMENTGENERATOR_H #define TRINITY_FORMATIONMOVEMENTGENERATOR_H -#include "AbstractFollower.h" #include "MovementGenerator.h" #include "Timer.h" -class FormationMovementGenerator : public MovementGeneratorMedium, public AbstractFollower +class FormationMovementGenerator : public MovementGeneratorMedium { public: explicit FormationMovementGenerator(Unit* leader, float range, float angle, uint32 point1, uint32 point2); @@ -40,6 +39,7 @@ class FormationMovementGenerator : public MovementGeneratorMedium objSize) diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index f41465d8b9..3194a091b7 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -3377,7 +3377,7 @@ void Spell::EffectScriptEffect(SpellEffIndex effIndex) { int32 basepoints0 = damage; // Cast Absorb on totems - for (uint8 slot = SUMMON_SLOT_TOTEM; slot < MAX_TOTEM_SLOT; ++slot) + for (uint8 slot = SUMMON_SLOT_TOTEM_FIRE; slot < MAX_TOTEM_SLOT; ++slot) { if (!unitTarget->m_SummonSlot[slot]) continue; @@ -4608,11 +4608,10 @@ void Spell::EffectResurrectPet(SpellEffIndex /*effIndex*/) if (hadPet) { // Reposition the pet's corpse before reviving so as not to grab aggro - // We can use a different, more accurate version of GetClosePoint() since we have a pet - float x, y, z; // Will be used later to reposition the pet if we have one - player->GetClosePoint(x, y, z, pet->GetCombatReach(), PET_FOLLOW_DIST, pet->GetFollowAngle()); - pet->NearTeleportTo(x, y, z, player->GetOrientation()); - pet->Relocate(x, y, z, player->GetOrientation()); // This is needed so SaveStayPosition() will get the proper coords. + Position pos = player->GetPosition(); + player->MovePositionToFirstCollision(pos, DEFAULT_FOLLOW_DISTANCE_PET, float(M_PI_2)); + pet->NearTeleportTo(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), player->GetOrientation()); + pet->Relocate(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), player->GetOrientation()); // This is needed so SaveStayPosition() will get the proper coords. } pet->SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE); @@ -4644,7 +4643,7 @@ void Spell::EffectDestroyAllTotems(SpellEffIndex /*effIndex*/) return; int32 mana = 0; - for (uint8 slot = SUMMON_SLOT_TOTEM; slot < MAX_TOTEM_SLOT; ++slot) + for (uint8 slot = SUMMON_SLOT_TOTEM_FIRE; slot < MAX_TOTEM_SLOT; ++slot) { if (!m_caster->m_SummonSlot[slot]) continue; @@ -5198,9 +5197,9 @@ void Spell::EffectCreateTamedPet(SpellEffIndex effIndex) return; // relocate - float px, py, pz; - unitTarget->GetClosePoint(px, py, pz, pet->GetCombatReach(), PET_FOLLOW_DIST, pet->GetFollowAngle()); - pet->Relocate(px, py, pz, unitTarget->GetOrientation()); + Position pos = unitTarget->GetPosition(); + unitTarget->MovePositionToFirstCollision(pos, DEFAULT_FOLLOW_DISTANCE_PET, float(M_PI_2)); + pet->Relocate(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), unitTarget->GetOrientation()); // add to world pet->GetMap()->AddToMap(pet->ToCreature()); diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index 856b12f4f2..37f355855a 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -2158,7 +2158,7 @@ public: break; case CHASE_MOTION_TYPE: { - Unit* target = static_cast(movementGenerator)->GetTarget(); + Unit const* target = static_cast(movementGenerator)->GetTarget(); if (!target) handler->SendSysMessage(LANG_MOVEGENS_CHASE_NULL); @@ -2170,7 +2170,7 @@ public: } case FOLLOW_MOTION_TYPE: { - Unit* target = static_cast(movementGenerator)->GetTarget(); + Unit const* target = static_cast(movementGenerator)->GetTarget(); if (!target) handler->SendSysMessage(LANG_MOVEGENS_FOLLOW_NULL); diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index e75837a3ab..55a0ba7761 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -760,8 +760,8 @@ public: return false; } - // Follow player - Using pet's default dist and angle - creature->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, creature->GetFollowAngle()); + // Follow player + creature->FollowTarget(player); handler->PSendSysMessage(LANG_CREATURE_FOLLOW_YOU_NOW, creature->GetName().c_str()); return true; @@ -1351,8 +1351,7 @@ public: return false; } - FollowMovementGenerator const* mgen = static_cast((creature->GetMotionMaster()->top())); - + FollowMovementGenerator* mgen = static_cast((creature->GetMotionMaster()->top())); if (mgen->GetTarget() != player) { handler->PSendSysMessage(LANG_CREATURE_NOT_FOLLOW_YOU, creature->GetName().c_str()); @@ -1362,7 +1361,6 @@ public: // reset movement creature->GetMotionMaster()->MovementExpired(true); - handler->PSendSysMessage(LANG_CREATURE_NOT_FOLLOW_YOU_NOW, creature->GetName().c_str()); return true; } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingDescent/boss_atramedes.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingDescent/boss_atramedes.cpp index 14538767d9..b92e4f0351 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingDescent/boss_atramedes.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingDescent/boss_atramedes.cpp @@ -274,7 +274,7 @@ struct boss_atramedes : public BossAI if (Unit* summoner = summon->ToTempSummon()->GetSummoner()) { summon->CastSpell(summoner, SPELL_TRACKING); - summon->GetMotionMaster()->MoveFollow(summoner, 0.0f, ChaseAngle(0.0f, 0.0f)); + summon->GetMotionMaster()->MoveFollow(summoner, 0.f, 0.f); me->SetFacingToObject(summon); DoCast(summon, SPELL_SONIC_BREATH_CAST); } @@ -288,7 +288,7 @@ struct boss_atramedes : public BossAI summon->CastSpell(summon, SPELL_ROARING_FLAME_BREATH_REVERSE_CAST); summon->CastSpell(summon, SPELL_AGGRO_CREATOR); summon->CastSpell(summoner, SPELL_TRACKING); - summon->GetMotionMaster()->MoveFollow(summoner, 0.0f, ChaseAngle(0.0f, 0.0f)); + summon->GetMotionMaster()->MoveFollow(summoner, 0.0f, 0.f); _reverberatingFlameGUID = summon->GetGUID(); } break; @@ -492,7 +492,7 @@ struct boss_atramedes : public BossAI { flame->CastSpell(flame, SPELL_SONIC_FLAMES); flame->CastSpell(target, SPELL_TRACKING); - flame->GetMotionMaster()->MoveFollow(target, 0.0f, ChaseAngle(0.0f, 0.0f)); + flame->GetMotionMaster()->MoveFollow(target, 0.0f, 0.f); } } break; diff --git a/src/server/scripts/Events/childrens_week.cpp b/src/server/scripts/Events/childrens_week.cpp index 48733f2b7c..a8622f5e04 100644 --- a/src/server/scripts/Events/childrens_week.cpp +++ b/src/server/scripts/Events/childrens_week.cpp @@ -224,7 +224,7 @@ class npc_winterfin_playmate : public CreatureScript orphan->AI()->Talk(TEXT_ORACLE_ORPHAN_3); me->HandleEmoteCommand(EMOTE_STATE_NONE); player->GroupEventHappens(QUEST_PLAYMATE_ORACLE, me); - orphan->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + orphan->FollowTarget(player); Reset(); return; } @@ -331,7 +331,7 @@ class npc_snowfall_glade_playmate : public CreatureScript case 5: orphan->AI()->Talk(TEXT_WOLVAR_ORPHAN_3); player->GroupEventHappens(QUEST_PLAYMATE_WOLVAR, me); - orphan->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + orphan->FollowTarget(player); Reset(); return; } @@ -425,7 +425,7 @@ class npc_the_biggest_tree : public CreatureScript break; case 3: player->GroupEventHappens(QUEST_THE_BIGGEST_TREE_EVER, me); - orphan->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + orphan->FollowTarget(player); Reset(); return; } @@ -522,7 +522,7 @@ class npc_high_oracle_soo_roo : public CreatureScript case 3: orphan->AI()->Talk(TEXT_ORACLE_ORPHAN_6); player->GroupEventHappens(QUEST_THE_BRONZE_DRAGONSHRINE_ORACLE, me); - orphan->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + orphan->FollowTarget(player); Reset(); return; } @@ -617,7 +617,7 @@ class npc_elder_kekek : public CreatureScript case 3: orphan->AI()->Talk(TEXT_WOLVAR_ORPHAN_5); player->GroupEventHappens(QUEST_THE_BRONZE_DRAGONSHRINE_WOLVAR, me); - orphan->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + orphan->FollowTarget(player); Reset(); return; } @@ -738,7 +738,7 @@ class npc_the_etymidian : public CreatureScript timer = 5000; break; case 5: - orphan->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + orphan->FollowTarget(player); player->GroupEventHappens(QUEST_MEETING_A_GREAT_ONE, me); Reset(); return; @@ -875,7 +875,7 @@ class npc_alexstraza_the_lifebinder : public CreatureScript me->SetStandState(UNIT_STAND_STATE_STAND); me->SetOrientation(me->GetHomePosition().GetOrientation()); player->GroupEventHappens(QUEST_THE_DRAGON_QUEEN_ORACLE, me); - orphan->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + orphan->FollowTarget(player); Reset(); return; case 7: @@ -906,7 +906,7 @@ class npc_alexstraza_the_lifebinder : public CreatureScript break; case 12: player->GroupEventHappens(QUEST_THE_DRAGON_QUEEN_WOLVAR, me); - orphan->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + orphan->FollowTarget(player); Reset(); return; } diff --git a/src/server/scripts/Kalimdor/zone_desolace.cpp b/src/server/scripts/Kalimdor/zone_desolace.cpp index 620e5d04d2..2c7c752b3f 100644 --- a/src/server/scripts/Kalimdor/zone_desolace.cpp +++ b/src/server/scripts/Kalimdor/zone_desolace.cpp @@ -87,7 +87,7 @@ public: me->CombatStop(); me->DeleteThreatList(); me->SetSpeedRate(MOVE_RUN, 0.6f); - me->GetMotionMaster()->MoveFollow(caster, PET_FOLLOW_DIST, me->GetFollowAngle()); + me->FollowTarget(caster); me->setActive(true); } } diff --git a/src/server/scripts/Kalimdor/zone_mount_hyjal.cpp b/src/server/scripts/Kalimdor/zone_mount_hyjal.cpp index 586fc6901a..2d6f7b9837 100644 --- a/src/server/scripts/Kalimdor/zone_mount_hyjal.cpp +++ b/src/server/scripts/Kalimdor/zone_mount_hyjal.cpp @@ -602,7 +602,7 @@ struct npc_mh_arch_druid_fandral_staghelm : public PassiveAI void IsSummonedBy(Unit* summoner) override { if (summoner) - me->GetMotionMaster()->MoveFollow(summoner, 0.0f, ChaseAngle(float(M_PI), 0.0f)); + me->FollowTarget(summoner); } void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override diff --git a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp index d03b45086e..389997c484 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp @@ -1141,8 +1141,8 @@ struct npc_argent_captainAI : public ScriptedAI public: npc_argent_captainAI(Creature* creature) : ScriptedAI(creature), instance(creature->GetInstanceScript()), _firstDeath(true) { - FollowAngle = PET_FOLLOW_ANGLE; - FollowDist = PET_FOLLOW_DIST; + FollowAngle = float(M_PI_2); + FollowDist = 3.f; IsUndead = false; } @@ -1172,7 +1172,7 @@ struct npc_argent_captainAI : public ScriptedAI me->SetReactState(REACT_DEFENSIVE); FollowAngle = me->GetAngle(crok) + me->GetOrientation(); FollowDist = me->GetDistance2d(crok); - me->GetMotionMaster()->MoveFollow(crok, FollowDist, FollowAngle, MOTION_SLOT_IDLE); + me->GetMotionMaster()->MoveFollow(crok, FollowDist, FollowAngle, true, true); } me->setActive(true); diff --git a/src/server/scripts/Northrend/zone_grizzly_hills.cpp b/src/server/scripts/Northrend/zone_grizzly_hills.cpp index a3754dff80..f2361b8b51 100644 --- a/src/server/scripts/Northrend/zone_grizzly_hills.cpp +++ b/src/server/scripts/Northrend/zone_grizzly_hills.cpp @@ -95,7 +95,7 @@ public: break; case 11: if (Creature* Mrfloppy = ObjectAccessor::GetCreature(*me, _mrfloppyGUID)) - Mrfloppy->GetMotionMaster()->MoveFollow(me, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + Mrfloppy->FollowTarget(me); break; case 17: if (Creature* Mrfloppy = ObjectAccessor::GetCreature(*me, _mrfloppyGUID)) @@ -151,7 +151,7 @@ public: RWORG->DisappearAndDie(); me->GetMotionMaster()->MovePoint(0, Mrfloppy->GetPositionX(), Mrfloppy->GetPositionY(), Mrfloppy->GetPositionZ()); Mrfloppy->setDeathState(ALIVE); - Mrfloppy->GetMotionMaster()->MoveFollow(me, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + Mrfloppy->FollowTarget(me); Talk(SAY_VICTORY3); } } @@ -189,7 +189,7 @@ public: { Talk(SAY_QUEST_ACCEPT); if (Creature* Mrfloppy = GetClosestCreatureWithEntry(me, NPC_MRFLOPPY, 180.0f)) - Mrfloppy->GetMotionMaster()->MoveFollow(me, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + Mrfloppy->FollowTarget(me); Start(true, false, player->GetGUID()); } diff --git a/src/server/scripts/Northrend/zone_sholazar_basin.cpp b/src/server/scripts/Northrend/zone_sholazar_basin.cpp index fdaa90b92b..4545f5fb00 100644 --- a/src/server/scripts/Northrend/zone_sholazar_basin.cpp +++ b/src/server/scripts/Northrend/zone_sholazar_basin.cpp @@ -289,7 +289,7 @@ public: timer = 5000; break; case 6: - orphan->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + orphan->FollowTarget(player); player->GroupEventHappens(QUEST_THE_MIGHTY_HEMET_NESINGWARY, me); Reset(); return; diff --git a/src/server/scripts/Pet/pet_dk.cpp b/src/server/scripts/Pet/pet_dk.cpp index e45be74a9b..3db8dfa321 100644 --- a/src/server/scripts/Pet/pet_dk.cpp +++ b/src/server/scripts/Pet/pet_dk.cpp @@ -236,13 +236,14 @@ struct npc_pet_dk_army_of_the_dead_ghoul : public AggressorAI npc_pet_dk_army_of_the_dead_ghoul(Creature* creature) : AggressorAI(creature), _readyToAttack(false) { } - void JustAppeared() override + void IsSummonedBy(Unit* summoner) override { DoCastSelf(SPELL_DEATH_KNIGHT_PET_SCALING_2); DoCastSelf(SPELL_DEATH_KNIGHT_PET_SCALING_3); DoCastSelf(SPELL_DEATH_KNIGHT_PET_SCALING_5); DoCastSelf(SPELL_BIRTH); me->AddUnitState(UNIT_STATE_ROOT); + me->FollowTarget(summoner); _events.ScheduleEvent(EVENT_ALLOW_ATTACKS, 4s); } diff --git a/src/server/scripts/Pet/pet_generic.cpp b/src/server/scripts/Pet/pet_generic.cpp index f5325f1b92..1bab437dad 100644 --- a/src/server/scripts/Pet/pet_generic.cpp +++ b/src/server/scripts/Pet/pet_generic.cpp @@ -53,7 +53,7 @@ public: npc_pet_gen_baby_blizzard_bearAI(Creature* creature) : NullCreatureAI(creature) { if (Unit* owner = me->GetCharmerOrOwner()) - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle()); + me->FollowTarget(owner); _events.ScheduleEvent(EVENT_BBB_PET_SIT, urandms(10, 30)); } @@ -117,7 +117,7 @@ public: { _events.Reset(); if (Unit* owner = me->GetCharmerOrOwner()) - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle()); + me->FollowTarget(owner); } void EnterEvadeMode(EvadeReason why) override @@ -137,7 +137,7 @@ public: if (!me->IsWithinDist(owner, 40.f)) { me->RemoveAura(SPELL_EGBERT); - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle()); + me->FollowTarget(owner); } } @@ -237,7 +237,7 @@ public: break; case EVENT_FOLLOW: if (Unit* owner = me->GetCharmerOrOwner()) - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + me->FollowTarget(owner); break; case EVENT_DRINK: me->CastSpell(me, SPELL_PANDAREN_MONK, false); @@ -375,7 +375,6 @@ class npc_pet_gen_lil_ragnaros : public CreatureScript me->RemoveAllAuras(); DoCastSelf(SPELL_DND_LR_1, true); DoCastSelf(SPELL_DND_DESPAWN_BASIC_CAMPFIRE, true); - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); _submerged = true; } else if (me->GetDistance(owner) <= 5.0f && _submerged) diff --git a/src/server/scripts/Pet/pet_mage.cpp b/src/server/scripts/Pet/pet_mage.cpp index aadd0b5d46..8484803510 100644 --- a/src/server/scripts/Pet/pet_mage.cpp +++ b/src/server/scripts/Pet/pet_mage.cpp @@ -64,12 +64,6 @@ struct npc_pet_mage_mirror_image : ScriptedAI AttackStartCaster(who, 20.0f); } - void EnterEvadeMode(EvadeReason /*why*/) override - { - if (Unit* summoner = me->ToTempSummon()->GetSummoner()) - me->GetMotionMaster()->MoveFollow(summoner, PET_FOLLOW_DIST, _angle, true, MOTION_SLOT_IDLE); - } - void IsSummonedBy(Unit* summoner) override { DoCastAOE(SPELL_INHERIT_MASTERS_THREAT_LIST); @@ -79,7 +73,7 @@ struct npc_pet_mage_mirror_image : ScriptedAI DoCast(summoner, SPELL_COPY_WEAPON); _angle = summoner->GetAngle(me); - me->GetMotionMaster()->MoveFollow(summoner, PET_FOLLOW_DIST, _angle, true, MOTION_SLOT_IDLE); + me->FollowTarget(summoner); } void SpellHitTarget(Unit* target, SpellInfo const* spell) override diff --git a/src/server/scripts/Spells/spell_shaman.cpp b/src/server/scripts/Spells/spell_shaman.cpp index efcdb750a8..8281a128d5 100644 --- a/src/server/scripts/Spells/spell_shaman.cpp +++ b/src/server/scripts/Spells/spell_shaman.cpp @@ -1308,7 +1308,7 @@ public: void HandleDummy(AuraEffect const* /*aurEff*/) { Unit* target = GetTarget(); - for (uint8 i = SUMMON_SLOT_TOTEM; i < MAX_TOTEM_SLOT; ++i) + for (uint8 i = SUMMON_SLOT_TOTEM_FIRE; i < MAX_TOTEM_SLOT; ++i) if (!target->m_SummonSlot[i]) return; diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp index 76dd9e576a..5061e0beec 100644 --- a/src/server/scripts/World/npcs_special.cpp +++ b/src/server/scripts/World/npcs_special.cpp @@ -2383,7 +2383,7 @@ public: { Initialize(); if (Unit* owner = me->GetOwner()) - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + me->FollowTarget(owner); } void JustEngagedWith(Unit* /*who*/) override { }