Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

Commit

Permalink
Core/Movement: reimplement follow movement (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ovahlord committed Jun 26, 2020
1 parent 120a4cc commit b09d6e2
Show file tree
Hide file tree
Showing 40 changed files with 561 additions and 372 deletions.
4 changes: 2 additions & 2 deletions src/server/game/AI/CoreAI/PetAI.cpp
Expand Up @@ -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());
}
}

Expand Down Expand Up @@ -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)))
{
Expand Down
7 changes: 2 additions & 5 deletions src/server/game/AI/CreatureAI.cpp
Expand Up @@ -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);
}
}
}
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/server/game/AI/PlayerAI/PlayerAI.cpp
Expand Up @@ -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);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp
Expand Up @@ -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();
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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);
}
}

Expand Down
10 changes: 7 additions & 3 deletions src/server/game/AI/SmartScripts/SmartAI.cpp
Expand Up @@ -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))
Expand All @@ -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);
}
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 1 addition & 5 deletions src/server/game/Entities/Creature/TemporarySummon.cpp
Expand Up @@ -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();
Expand All @@ -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);
Expand Down
3 changes: 0 additions & 3 deletions src/server/game/Entities/Creature/TemporarySummon.h
Expand Up @@ -106,16 +106,13 @@ 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
bool IsGuardianPet() const;
bool IsWarlockMinion() const;
protected:
Unit* const m_owner;
float m_followAngle;
};

class TC_GAME_API Guardian : public Minion
Expand Down
14 changes: 7 additions & 7 deletions src/server/game/Entities/Pet/Pet.cpp
Expand Up @@ -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())
{
Expand Down Expand Up @@ -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)",
Expand Down
7 changes: 4 additions & 3 deletions src/server/game/Entities/Pet/PetDefines.h
Expand Up @@ -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
4 changes: 2 additions & 2 deletions src/server/game/Entities/Totem/Totem.cpp
Expand Up @@ -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);
Expand Down Expand Up @@ -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())
{
Expand Down
79 changes: 72 additions & 7 deletions src/server/game/Entities/Unit/Unit.cpp
Expand Up @@ -16,7 +16,6 @@
*/

#include "Unit.h"
#include "AbstractFollower.h"
#include "Battlefield.h"
#include "BattlefieldMgr.h"
#include "Battleground.h"
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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())
{
Expand Down
27 changes: 10 additions & 17 deletions src/server/game/Entities/Unit/Unit.h
Expand Up @@ -226,7 +226,6 @@ enum InventorySlot
NULL_SLOT = 255
};

struct AbstractFollower;
struct FactionTemplateEntry;
struct LiquidData;
struct LiquidTypeEntry;
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -982,6 +973,7 @@ class TC_GAME_API Unit : public WorldObject
typedef std::list<AuraApplication *> AuraApplicationList;

typedef std::vector<std::pair<uint8 /*procEffectMask*/, AuraApplication*>> AuraApplicationProcContainer;
typedef std::vector<Unit*> FormationFollowerContainer;

typedef std::map<uint8, AuraApplication*> VisibleAuraMap;

Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -1896,8 +1892,6 @@ class TC_GAME_API Unit : public WorldObject

void RewardRage(uint32 baseRage, bool attacker);

virtual float GetFollowAngle() const { return static_cast<float>(M_PI/2); }

void OutDebugInfo() const;
virtual bool IsLoading() const { return false; }
bool IsDuringRemoveFromWorld() const {return m_duringRemoveFromWorld;}
Expand Down Expand Up @@ -2023,7 +2017,6 @@ class TC_GAME_API Unit : public WorldObject
virtual void ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidData> const& liquidData);

void InterruptMovementBasedAuras();

private:

void UpdateSplineMovement(uint32 t_diff);
Expand Down Expand Up @@ -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<AbstractFollower*> m_followingMe;

GuidSet m_ComboPointHolders;

RedirectThreatInfo _redirectThreadInfo;
Expand All @@ -2072,6 +2063,8 @@ class TC_GAME_API Unit : public WorldObject
SpellHistory* m_spellHistory;

PositionUpdateInfo _positionUpdateInfo;

FormationFollowerContainer _formationFollowers;
};

namespace Trinity
Expand Down

0 comments on commit b09d6e2

Please sign in to comment.