/* * 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 "MotionMaster.h" #include "AbstractFollower.h" #include "Creature.h" #include "CreatureAISelector.h" #include "Containers.h" #include "DB2Stores.h" #include "Errors.h" #include "G3DPosition.hpp" #include "Log.h" #include "Map.h" #include "MoveSpline.h" #include "MoveSplineInit.h" #include "ObjectAccessor.h" #include "PathGenerator.h" #include "PetDefines.h" #include "Player.h" #include "ScriptSystem.h" #include "Unit.h" #include "WaypointDefines.h" #include #include #include "ChaseMovementGenerator.h" #include "ConfusedMovementGenerator.h" #include "FleeingMovementGenerator.h" #include "FlightPathMovementGenerator.h" #include "FollowMovementGenerator.h" #include "FormationMovementGenerator.h" #include "GenericMovementGenerator.h" #include "HomeMovementGenerator.h" #include "IdleMovementGenerator.h" #include "PointMovementGenerator.h" #include "RandomMovementGenerator.h" #include "SplineChainMovementGenerator.h" #include "WaypointMovementGenerator.h" inline MovementGenerator* GetIdleMovementGenerator() { return sMovementGeneratorRegistry->GetRegistryItem(IDLE_MOTION_TYPE)->Create(); } inline bool IsStatic(MovementGenerator* movement) { return (movement == GetIdleMovementGenerator()); } inline void MovementGeneratorPointerDeleter(MovementGenerator* a) { if (a != nullptr && !IsStatic(a)) delete a; } void MovementGeneratorDeleter::operator()(MovementGenerator* a) { MovementGeneratorPointerDeleter(a); } bool MovementGeneratorComparator::operator()(MovementGenerator const* a, MovementGenerator const* b) const { if (a->Mode > b->Mode) return true; else if (a->Mode == b->Mode) return a->Priority > b->Priority; return false; } MovementGeneratorInformation::MovementGeneratorInformation(MovementGeneratorType type, ObjectGuid targetGUID, std::string const& targetName) : Type(type), TargetGUID(targetGUID), TargetName(targetName) { } MotionMaster::MotionMaster(Unit* unit) : _owner(unit), _defaultGenerator(nullptr), _flags(MOTIONMASTER_FLAG_INITIALIZATION_PENDING) { } MotionMaster::~MotionMaster() { _delayedActions.clear(); for (auto itr = _generators.begin(); itr != _generators.end(); itr = _generators.erase(itr)) MovementGeneratorPointerDeleter(*itr); } void MotionMaster::Initialize() { if (HasFlag(MOTIONMASTER_FLAG_INITIALIZATION_PENDING)) return; if (HasFlag(MOTIONMASTER_FLAG_UPDATE)) { DelayedActionDefine action = [this]() { Initialize(); }; _delayedActions.emplace_back(std::move(action), MOTIONMASTER_DELAYED_INITIALIZE); return; } DirectInitialize(); } void MotionMaster::InitializeDefault() { Add(FactorySelector::SelectMovementGenerator(_owner), MOTION_SLOT_DEFAULT); } void MotionMaster::AddToWorld() { if (!HasFlag(MOTIONMASTER_FLAG_INITIALIZATION_PENDING)) return; AddFlag(MOTIONMASTER_FLAG_INITIALIZING); RemoveFlag(MOTIONMASTER_FLAG_INITIALIZATION_PENDING); DirectInitialize(); ResolveDelayedActions(); RemoveFlag(MOTIONMASTER_FLAG_INITIALIZING); } bool MotionMaster::Empty() const { return !_defaultGenerator && _generators.empty(); } uint32 MotionMaster::Size() const { return (_defaultGenerator ? 1 : 0) + uint32(_generators.size()); } std::vector MotionMaster::GetMovementGeneratorsInformation() const { std::vector list; if (_defaultGenerator) list.emplace_back(_defaultGenerator->GetMovementGeneratorType(), ObjectGuid::Empty, std::string()); for (auto itr = _generators.begin(); itr != _generators.end(); ++itr) { MovementGenerator* movement = *itr; MovementGeneratorType const type = movement->GetMovementGeneratorType(); switch (type) { case CHASE_MOTION_TYPE: case FOLLOW_MOTION_TYPE: if (AbstractFollower* followInformation = dynamic_cast(movement)) { if (Unit* target = followInformation->GetTarget()) list.emplace_back(type, target->GetGUID(), target->GetName()); else list.emplace_back(type, ObjectGuid::Empty, std::string()); } else list.emplace_back(type, ObjectGuid::Empty, std::string()); break; default: list.emplace_back(type, ObjectGuid::Empty, std::string()); break; } } return list; } MovementSlot MotionMaster::GetCurrentSlot() const { if (!_generators.empty()) return MOTION_SLOT_ACTIVE; if (_defaultGenerator) return MOTION_SLOT_DEFAULT; return MAX_MOTION_SLOT; } MovementGenerator* MotionMaster::GetCurrentMovementGenerator() const { if (!_generators.empty()) return *_generators.begin(); if (_defaultGenerator) return _defaultGenerator.get(); return nullptr; } MovementGeneratorType MotionMaster::GetCurrentMovementGeneratorType() const { if (Empty()) return MAX_MOTION_TYPE; MovementGenerator const* movement = GetCurrentMovementGenerator(); if (!movement) return MAX_MOTION_TYPE; return movement->GetMovementGeneratorType(); } MovementGeneratorType MotionMaster::GetCurrentMovementGeneratorType(MovementSlot slot) const { if (Empty() || IsInvalidMovementSlot(slot)) return MAX_MOTION_TYPE; if (slot == MOTION_SLOT_ACTIVE && !_generators.empty()) return (*_generators.begin())->GetMovementGeneratorType(); if (slot == MOTION_SLOT_DEFAULT && _defaultGenerator) return _defaultGenerator->GetMovementGeneratorType(); return MAX_MOTION_TYPE; } MovementGenerator* MotionMaster::GetCurrentMovementGenerator(MovementSlot slot) const { if (Empty() || IsInvalidMovementSlot(slot)) return nullptr; if (slot == MOTION_SLOT_ACTIVE && !_generators.empty()) return *_generators.begin(); if (slot == MOTION_SLOT_DEFAULT && _defaultGenerator) return _defaultGenerator.get(); return nullptr; } MovementGenerator* MotionMaster::GetMovementGenerator(std::function const& filter, MovementSlot slot) const { if (Empty() || IsInvalidMovementSlot(slot)) return nullptr; MovementGenerator* movement = nullptr; switch (slot) { case MOTION_SLOT_DEFAULT: if (_defaultGenerator && filter(_defaultGenerator.get())) movement = _defaultGenerator.get(); break; case MOTION_SLOT_ACTIVE: if (!_generators.empty()) { auto itr = std::find_if(_generators.begin(), _generators.end(), std::ref(filter)); if (itr != _generators.end()) movement = *itr; } break; default: break; } return movement; } bool MotionMaster::HasMovementGenerator(std::function const& filter, MovementSlot slot) const { if (Empty() || IsInvalidMovementSlot(slot)) return false; bool value = false; switch (slot) { case MOTION_SLOT_DEFAULT: if (_defaultGenerator && filter(_defaultGenerator.get())) value = true; break; case MOTION_SLOT_ACTIVE: if (!_generators.empty()) { auto itr = std::find_if(_generators.begin(), _generators.end(), std::ref(filter)); value = itr != _generators.end(); } break; default: break; } return value; } void MotionMaster::Update(uint32 diff) { if (!_owner) return; if (HasFlag(MOTIONMASTER_FLAG_INITIALIZATION_PENDING | MOTIONMASTER_FLAG_INITIALIZING)) return; ASSERT(!Empty(), "MotionMaster:Update: update called without Initializing! (%s)", _owner->GetGUID().ToString().c_str()); AddFlag(MOTIONMASTER_FLAG_UPDATE); MovementGenerator* top = GetCurrentMovementGenerator(); if (HasFlag(MOTIONMASTER_FLAG_STATIC_INITIALIZATION_PENDING) && IsStatic(top)) { RemoveFlag(MOTIONMASTER_FLAG_STATIC_INITIALIZATION_PENDING); top->Initialize(_owner); } if (top->HasFlag(MOVEMENTGENERATOR_FLAG_INITIALIZATION_PENDING)) top->Initialize(_owner); if (top->HasFlag(MOVEMENTGENERATOR_FLAG_DEACTIVATED)) top->Reset(_owner); ASSERT(!top->HasFlag(MOVEMENTGENERATOR_FLAG_INITIALIZATION_PENDING | MOVEMENTGENERATOR_FLAG_DEACTIVATED), "MotionMaster:Update: update called on an uninitialized top! (%s) (type: %u, flags: %u)", _owner->GetGUID().ToString().c_str(), top->GetMovementGeneratorType(), top->Flags); if (!top->Update(_owner, diff)) { ASSERT(top == GetCurrentMovementGenerator(), "MotionMaster::Update: top was modified while updating! (%s)", _owner->GetGUID().ToString().c_str()); // Since all the actions that modify any slot are delayed, this movement is guaranteed to be top Pop(true, true); // Natural, and only, call to MovementInform } RemoveFlag(MOTIONMASTER_FLAG_UPDATE); ResolveDelayedActions(); } void MotionMaster::Add(MovementGenerator* movement, MovementSlot slot/* = MOTION_SLOT_ACTIVE*/) { if (!movement) return; if (IsInvalidMovementSlot(slot)) { delete movement; return; } if (HasFlag(MOTIONMASTER_FLAG_DELAYED)) { DelayedActionDefine action = [this, movement, slot]() { Add(movement, slot); }; _delayedActions.emplace_back(std::move(action), MOTIONMASTER_DELAYED_ADD); } else DirectAdd(movement, slot); } void MotionMaster::Remove(MovementGenerator* movement, MovementSlot slot/* = MOTION_SLOT_ACTIVE*/) { if (!movement || IsInvalidMovementSlot(slot)) return; if (HasFlag(MOTIONMASTER_FLAG_DELAYED)) { DelayedActionDefine action = [this, movement, slot]() { Remove(movement, slot); }; _delayedActions.emplace_back(std::move(action), MOTIONMASTER_DELAYED_REMOVE); return; } if (Empty()) return; switch (slot) { case MOTION_SLOT_DEFAULT: if (_defaultGenerator && _defaultGenerator.get() == movement) DirectClearDefault(); break; case MOTION_SLOT_ACTIVE: if (!_generators.empty()) { auto bounds = _generators.equal_range(movement); auto itr = std::find(bounds.first, bounds.second, movement); if (itr != _generators.end()) Remove(itr, GetCurrentMovementGenerator() == *itr, false); } break; default: break; } } void MotionMaster::Remove(MovementGeneratorType type, MovementSlot slot/* = MOTION_SLOT_ACTIVE*/) { if (IsInvalidMovementGeneratorType(type) || IsInvalidMovementSlot(slot)) return; if (HasFlag(MOTIONMASTER_FLAG_DELAYED)) { DelayedActionDefine action = [this, type, slot]() { Remove(type, slot); }; _delayedActions.emplace_back(std::move(action), MOTIONMASTER_DELAYED_REMOVE_TYPE); return; } if (Empty()) return; switch (slot) { case MOTION_SLOT_DEFAULT: if (_defaultGenerator && _defaultGenerator->GetMovementGeneratorType() == type) DirectClearDefault(); break; case MOTION_SLOT_ACTIVE: if (!_generators.empty()) { auto itr = std::find_if(_generators.begin(), _generators.end(), [type](MovementGenerator const* a) -> bool { return a->GetMovementGeneratorType() == type; }); if (itr != _generators.end()) Remove(itr, GetCurrentMovementGenerator() == *itr, false); } break; default: break; } } void MotionMaster::Clear() { if (HasFlag(MOTIONMASTER_FLAG_DELAYED)) { DelayedActionDefine action = [this]() { Clear(); }; _delayedActions.emplace_back(std::move(action), MOTIONMASTER_DELAYED_CLEAR); return; } if (!Empty()) DirectClear(); } void MotionMaster::Clear(MovementSlot slot) { if (IsInvalidMovementSlot(slot)) return; if (HasFlag(MOTIONMASTER_FLAG_DELAYED)) { DelayedActionDefine action = [this, slot]() { Clear(slot); }; _delayedActions.emplace_back(std::move(action), MOTIONMASTER_DELAYED_CLEAR_SLOT); return; } if (Empty()) return; switch (slot) { case MOTION_SLOT_DEFAULT: DirectClearDefault(); break; case MOTION_SLOT_ACTIVE: DirectClear(); break; default: break; } } void MotionMaster::Clear(MovementGeneratorMode mode) { if (HasFlag(MOTIONMASTER_FLAG_DELAYED)) { DelayedActionDefine action = [this, mode]() { Clear(mode); }; _delayedActions.emplace_back(std::move(action), MOTIONMASTER_DELAYED_CLEAR_MODE); return; } if (Empty()) return; std::function criteria = [mode](MovementGenerator* a) -> bool { return a->Mode == mode; }; DirectClear(criteria); } void MotionMaster::Clear(MovementGeneratorPriority priority) { if (HasFlag(MOTIONMASTER_FLAG_DELAYED)) { DelayedActionDefine action = [this, priority]() { Clear(priority); }; _delayedActions.emplace_back(std::move(action), MOTIONMASTER_DELAYED_CLEAR_PRIORITY); return; } if (Empty()) return; std::function criteria = [priority](MovementGenerator* a) -> bool { return a->Priority == priority; }; DirectClear(criteria); } void MotionMaster::PropagateSpeedChange() { if (Empty()) return; MovementGenerator* movement = GetCurrentMovementGenerator(); if (!movement) return; movement->UnitSpeedChanged(); } bool MotionMaster::GetDestination(float &x, float &y, float &z) { if (_owner->movespline->Finalized()) return false; G3D::Vector3 const& dest = _owner->movespline->FinalDestination(); x = dest.x; y = dest.y; z = dest.z; return true; } bool MotionMaster::StopOnDeath() { if (MovementGenerator* movementGenerator = GetCurrentMovementGenerator()) if (movementGenerator->HasFlag(MOVEMENTGENERATOR_FLAG_PERSIST_ON_DEATH)) return false; if (_owner->IsInWorld()) { // Only clear MotionMaster for entities that exists in world // Avoids crashes in the following conditions : // * Using 'call pet' on dead pets // * Using 'call stabled pet' // * Logging in with dead pets Clear(); MoveIdle(); } _owner->StopMoving(); return true; } void MotionMaster::MoveIdle() { Add(GetIdleMovementGenerator(), MOTION_SLOT_DEFAULT); } void MotionMaster::MoveTargetedHome() { Creature* owner = _owner->ToCreature(); if (!owner) { TC_LOG_ERROR("movement.motionmaster", "MotionMaster::MoveTargetedHome: '{}', attempted to move towards target home.", _owner->GetGUID().ToString()); return; } Clear(); Unit* target = owner->GetCharmerOrOwner(); if (!target) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveTargetedHome: '{}', targeted home.", _owner->GetGUID().ToString()); Add(new HomeMovementGenerator()); } else { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveTargetedHome: '{}', starts following '{}'", _owner->GetGUID().ToString(), target->GetGUID().ToString()); Add(new FollowMovementGenerator(target, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE, {})); } } void MotionMaster::MoveRandom(float wanderDistance, Optional duration, MovementSlot slot /*= MOTION_SLOT_DEFAULT*/) { if (_owner->GetTypeId() == TYPEID_UNIT) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveRandom: '{}', started random movement (spawnDist: {})", _owner->GetGUID().ToString(), wanderDistance); Add(new RandomMovementGenerator(wanderDistance, duration), slot); } } void MotionMaster::MoveFollow(Unit* target, float dist, ChaseAngle angle, Optional duration /*= {}*/, MovementSlot slot/* = MOTION_SLOT_ACTIVE*/) { // Ignore movement request if target not exist if (!target || target == _owner) return; TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveFollow: '{}', starts following '{}'", _owner->GetGUID().ToString(), target->GetGUID().ToString()); Add(new FollowMovementGenerator(target, dist, angle, duration), slot); } void MotionMaster::MoveChase(Unit* target, Optional dist, Optional angle) { // Ignore movement request if target not exist if (!target || target == _owner) return; TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveChase: '{}', starts chasing '{}'", _owner->GetGUID().ToString(), target->GetGUID().ToString()); Add(new ChaseMovementGenerator(target, dist, angle)); } void MotionMaster::MoveConfused() { if (_owner->GetTypeId() == TYPEID_PLAYER) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveConfused: '{}', started confused movement.", _owner->GetGUID().ToString()); Add(new ConfusedMovementGenerator()); } else { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveConfused: '{}', started confused movement.", _owner->GetGUID().ToString()); Add(new ConfusedMovementGenerator()); } } void MotionMaster::MoveFleeing(Unit* enemy, Milliseconds time) { if (!enemy) return; TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveFleeing: '{}', flees from '{}' (time: {}ms)", _owner->GetGUID().ToString(), enemy->GetGUID().ToString(), time.count()); if (_owner->GetTypeId() == TYPEID_UNIT) { if (time > 0ms) Add(new TimedFleeingMovementGenerator(enemy->GetGUID(), time)); else Add(new FleeingMovementGenerator(enemy->GetGUID())); } else Add(new FleeingMovementGenerator(enemy->GetGUID())); } void MotionMaster::MovePoint(uint32 id, Position const& pos, bool generatePath/* = true*/, Optional finalOrient/* = {}*/, Optional speed /*= {}*/, MovementWalkRunSpeedSelectionMode speedSelectionMode /*= MovementWalkRunSpeedSelectionMode::Default*/, Optional closeEnoughDistance /*= {}*/) { MovePoint(id, pos.m_positionX, pos.m_positionY, pos.m_positionZ, generatePath, finalOrient, speed, speedSelectionMode, closeEnoughDistance); } void MotionMaster::MovePoint(uint32 id, float x, float y, float z, bool generatePath /*= true*/, Optional finalOrient /*= {}*/, Optional speed /*= {}*/, MovementWalkRunSpeedSelectionMode speedSelectionMode /*= MovementWalkRunSpeedSelectionMode::Default*/, Optional closeEnoughDistance /*= {}*/) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MovePoint: '{}', targeted point Id: {} (X: {}, Y: {}, Z: {})", _owner->GetGUID().ToString(), id, x, y, z); Add(new PointMovementGenerator(id, x, y, z, generatePath, speed, finalOrient, nullptr, nullptr, speedSelectionMode, closeEnoughDistance)); } void MotionMaster::MoveCloserAndStop(uint32 id, Unit* target, float distance) { float distanceToTravel = _owner->GetExactDist2d(target) - distance; if (distanceToTravel > 0.0f) { float angle = _owner->GetAbsoluteAngle(target); float destx = _owner->GetPositionX() + distanceToTravel * std::cos(angle); float desty = _owner->GetPositionY() + distanceToTravel * std::sin(angle); MovePoint(id, destx, desty, target->GetPositionZ()); } else { // We are already close enough. We just need to turn toward the target without changing position. std::function initializer = [=, this, target = target->GetGUID()](Movement::MoveSplineInit& init) { init.MoveTo(_owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ()); if (Unit const* refreshedTarget = ObjectAccessor::GetUnit(*_owner, target)) init.SetFacing(refreshedTarget); }; Add(new GenericMovementGenerator(std::move(initializer), EFFECT_MOTION_TYPE, id)); } } void MotionMaster::MoveLand(uint32 id, Position const& pos, Optional tierTransitionId /*= {}*/, Optional velocity /*= {}*/, MovementWalkRunSpeedSelectionMode speedSelectionMode /*= MovementWalkRunSpeedSelectionMode::Default*/) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveLand: '{}', landing point Id: {} (X: {}, Y: {}, Z: {})", _owner->GetGUID().ToString(), id, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); std::function initializer = [=](Movement::MoveSplineInit& init) { init.MoveTo(PositionToVector3(pos), false); init.SetAnimation(AnimTier::Ground, tierTransitionId.value_or(0)); switch (speedSelectionMode) { case MovementWalkRunSpeedSelectionMode::ForceRun: init.SetWalk(false); break; case MovementWalkRunSpeedSelectionMode::ForceWalk: init.SetWalk(true); break; case MovementWalkRunSpeedSelectionMode::Default: default: break; } if (velocity) init.SetVelocity(*velocity); }; Add(new GenericMovementGenerator(std::move(initializer), EFFECT_MOTION_TYPE, id)); } void MotionMaster::MoveTakeoff(uint32 id, Position const& pos, Optional tierTransitionId /*= {}*/, Optional velocity /*= {}*/, MovementWalkRunSpeedSelectionMode speedSelectionMode /*= MovementWalkRunSpeedSelectionMode::Default*/) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveTakeoff: '{}', landing point Id: {} (X: {}, Y: {}, Z: {})", _owner->GetGUID().ToString(), id, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); std::function initializer = [=](Movement::MoveSplineInit& init) { init.MoveTo(PositionToVector3(pos), false); init.SetAnimation(AnimTier::Hover, tierTransitionId.value_or(0)); switch (speedSelectionMode) { case MovementWalkRunSpeedSelectionMode::ForceRun: init.SetWalk(false); break; case MovementWalkRunSpeedSelectionMode::ForceWalk: init.SetWalk(true); break; case MovementWalkRunSpeedSelectionMode::Default: default: break; } if (velocity) init.SetVelocity(*velocity); }; Add(new GenericMovementGenerator(std::move(initializer), EFFECT_MOTION_TYPE, id)); } void MotionMaster::MoveCharge(float x, float y, float z, float speed /*= SPEED_CHARGE*/, uint32 id /*= EVENT_CHARGE*/, bool generatePath /*= false*/, Unit const* target /*= nullptr*/, Movement::SpellEffectExtraData const* spellEffectExtraData /*= nullptr*/) { /* if (_slot[MOTION_SLOT_CONTROLLED] && _slot[MOTION_SLOT_CONTROLLED]->GetMovementGeneratorType() != DISTRACT_MOTION_TYPE) return; */ TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveCharge: '{}', charging point Id: {} (X: {}, Y: {}, Z: {})", _owner->GetGUID().ToString(), id, x, y, z); PointMovementGenerator* movement = new PointMovementGenerator(id, x, y, z, generatePath, speed, {}, target, spellEffectExtraData); movement->Priority = MOTION_PRIORITY_HIGHEST; movement->BaseUnitState = UNIT_STATE_CHARGING; Add(movement); } void MotionMaster::MoveCharge(PathGenerator const& path, float speed /*= SPEED_CHARGE*/, Unit const* target /*= nullptr*/, Movement::SpellEffectExtraData const* spellEffectExtraData /*= nullptr*/) { G3D::Vector3 dest = path.GetActualEndPosition(); MoveCharge(dest.x, dest.y, dest.z, speed, EVENT_CHARGE_PREPATH); // If this is ever changed to not happen immediately then all spell effect handlers that use this must be updated // Charge movement is not started when using EVENT_CHARGE_PREPATH Movement::MoveSplineInit init(_owner); init.MovebyPath(path.GetPath()); init.SetVelocity(speed); if (target) init.SetFacing(target); if (spellEffectExtraData) init.SetSpellEffectExtraData(*spellEffectExtraData); init.Launch(); } void MotionMaster::MoveKnockbackFrom(Position const& origin, float speedXY, float speedZ, Movement::SpellEffectExtraData const* spellEffectExtraData /*= nullptr*/) { // This function may make players fall below map if (_owner->GetTypeId() == TYPEID_PLAYER) return; if (speedXY < 0.01f) return; Position dest = _owner->GetPosition(); float moveTimeHalf = speedZ / Movement::gravity; float dist = 2 * moveTimeHalf * speedXY; float max_height = -Movement::computeFallElevation(moveTimeHalf, false, -speedZ); // Use a mmap raycast to get a valid destination. _owner->MovePositionToFirstCollision(dest, dist, _owner->GetRelativeAngle(origin) + float(M_PI)); std::function initializer = [=, effect = (spellEffectExtraData ? Optional(*spellEffectExtraData) : Optional())](Movement::MoveSplineInit& init) { init.MoveTo(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false); init.SetParabolic(max_height, 0); init.SetOrientationFixed(true); init.SetVelocity(speedXY); if (effect) init.SetSpellEffectExtraData(*effect); }; GenericMovementGenerator* movement = new GenericMovementGenerator(std::move(initializer), EFFECT_MOTION_TYPE, 0); movement->Priority = MOTION_PRIORITY_HIGHEST; movement->AddFlag(MOVEMENTGENERATOR_FLAG_PERSIST_ON_DEATH); Add(movement); } void MotionMaster::MoveJumpTo(float angle, float speedXY, float speedZ) { // This function may make players fall below map if (_owner->GetTypeId() == TYPEID_PLAYER) return; float x, y, z = _owner->GetPositionZ(); float moveTimeHalf = speedZ / Movement::gravity; float dist = 2 * moveTimeHalf * speedXY; _owner->GetNearPoint2D(nullptr, x, y, dist, _owner->GetOrientation() + angle); _owner->UpdateAllowedPositionZ(x, y, z); MoveJump(x, y, z, 0.0f, speedXY, speedZ); } void MotionMaster::MoveJump(Position const& pos, float speedXY, float speedZ, uint32 id/* = EVENT_JUMP*/, bool hasOrientation/* = false*/, JumpArrivalCastArgs const* arrivalCast /*= nullptr*/, Movement::SpellEffectExtraData const* spellEffectExtraData /*= nullptr*/) { MoveJump(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), speedXY, speedZ, id, hasOrientation, arrivalCast, spellEffectExtraData); } void MotionMaster::MoveJump(float x, float y, float z, float o, float speedXY, float speedZ, uint32 id /*= EVENT_JUMP*/, bool hasOrientation /* = false*/, JumpArrivalCastArgs const* arrivalCast /*= nullptr*/, Movement::SpellEffectExtraData const* spellEffectExtraData /*= nullptr*/) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveJump: '{}', jumps to point Id: {} (X: {}, Y: {}, Z: {})", _owner->GetGUID().ToString(), id, x, y, z); if (speedXY < 0.01f) return; float moveTimeHalf = speedZ / Movement::gravity; float max_height = -Movement::computeFallElevation(moveTimeHalf, false, -speedZ); std::function initializer = [=, effect = (spellEffectExtraData ? Optional(*spellEffectExtraData) : Optional())](Movement::MoveSplineInit& init) { init.MoveTo(x, y, z, false); init.SetParabolic(max_height, 0); init.SetVelocity(speedXY); if (hasOrientation) init.SetFacing(o); if (effect) init.SetSpellEffectExtraData(*effect); }; uint32 arrivalSpellId = 0; ObjectGuid arrivalSpellTargetGuid; if (arrivalCast) { arrivalSpellId = arrivalCast->SpellId; arrivalSpellTargetGuid = arrivalCast->Target; } GenericMovementGenerator* movement = new GenericMovementGenerator(std::move(initializer), EFFECT_MOTION_TYPE, id, arrivalSpellId, arrivalSpellTargetGuid); movement->Priority = MOTION_PRIORITY_HIGHEST; movement->BaseUnitState = UNIT_STATE_JUMPING; Add(movement); } void MotionMaster::MoveJumpWithGravity(Position const& pos, float speedXY, float gravity, uint32 id/* = EVENT_JUMP*/, bool hasOrientation/* = false*/, JumpArrivalCastArgs const* arrivalCast /*= nullptr*/, Movement::SpellEffectExtraData const* spellEffectExtraData /*= nullptr*/) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveJumpWithGravity: '{}', jumps to point Id: {} ({})", _owner->GetGUID().ToString(), id, pos.ToString()); if (speedXY < 0.01f) return; std::function initializer = [=, effect = (spellEffectExtraData ? Optional(*spellEffectExtraData) : Optional())](Movement::MoveSplineInit& init) { init.MoveTo(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), false); init.SetParabolicVerticalAcceleration(gravity, 0); init.SetUncompressed(); init.SetVelocity(speedXY); init.SetUnlimitedSpeed(); if (hasOrientation) init.SetFacing(pos.GetOrientation()); if (effect) init.SetSpellEffectExtraData(*effect); }; uint32 arrivalSpellId = 0; ObjectGuid arrivalSpellTargetGuid; if (arrivalCast) { arrivalSpellId = arrivalCast->SpellId; arrivalSpellTargetGuid = arrivalCast->Target; } GenericMovementGenerator* movement = new GenericMovementGenerator(std::move(initializer), EFFECT_MOTION_TYPE, id, arrivalSpellId, arrivalSpellTargetGuid); movement->Priority = MOTION_PRIORITY_HIGHEST; movement->BaseUnitState = UNIT_STATE_JUMPING; movement->AddFlag(MOVEMENTGENERATOR_FLAG_PERSIST_ON_DEATH); Add(movement); } void MotionMaster::MoveCirclePath(float x, float y, float z, float radius, bool clockwise, uint8 stepCount) { std::function initializer = [=, this](Movement::MoveSplineInit& init) { float step = 2 * float(M_PI) / stepCount * (clockwise ? -1.0f : 1.0f); Position const& pos = { x, y, z, 0.0f }; float angle = pos.GetAbsoluteAngle(_owner->GetPositionX(), _owner->GetPositionY()); // add the owner's current position as starting point as it gets removed after entering the cycle init.Path().push_back(G3D::Vector3(_owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ())); for (uint8 i = 0; i < stepCount; angle += step, ++i) { G3D::Vector3 point; point.x = x + radius * cosf(angle); point.y = y + radius * sinf(angle); if (_owner->IsFlying()) point.z = z; else point.z = _owner->GetMapHeight(point.x, point.y, z) + _owner->GetHoverOffset(); init.Path().push_back(point); } if (_owner->IsFlying()) { init.SetFly(); init.SetCyclic(); init.SetAnimation(AnimTier::Hover); } else { init.SetWalk(true); init.SetCyclic(); } }; Add(new GenericMovementGenerator(std::move(initializer), EFFECT_MOTION_TYPE, 0)); } void MotionMaster::MoveSmoothPath(uint32 pointId, Position const* pathPoints, size_t pathSize, bool walk, bool fly) { Movement::PointsArray path; path.reserve(pathSize); std::transform(pathPoints, pathPoints + pathSize, std::back_inserter(path), [](Position const& point) { return G3D::Vector3(point.GetPositionX(), point.GetPositionY(), point.GetPositionZ()); }); std::function initializer = [=](Movement::MoveSplineInit& init) { init.MovebyPath(path); init.SetWalk(walk); if (fly) { init.SetFly(); init.SetUncompressed(); init.SetSmooth(); } }; // This code is not correct // GenericMovementGenerator does not affect UNIT_STATE_ROAMING_MOVE // need to call PointMovementGenerator with various pointIds Add(new GenericMovementGenerator(std::move(initializer), EFFECT_MOTION_TYPE, pointId)); } void MotionMaster::MoveAlongSplineChain(uint32 pointId, uint16 dbChainId, bool walk) { Creature* owner = _owner->ToCreature(); if (!owner) { TC_LOG_ERROR("movement.motionmaster", "MotionMaster::MoveAlongSplineChain: '{}', tried to walk along DB spline chain. Ignoring.", _owner->GetGUID().ToString()); return; } std::vector const* chain = sScriptSystemMgr->GetSplineChain(owner, dbChainId); if (!chain) { TC_LOG_ERROR("movement.motionmaster", "MotionMaster::MoveAlongSplineChain: '{}', tried to walk along non-existing spline chain with DB Id: {}.", _owner->GetGUID().ToString(), dbChainId); return; } MoveAlongSplineChain(pointId, *chain, walk); } void MotionMaster::MoveAlongSplineChain(uint32 pointId, std::vector const& chain, bool walk) { Add(new SplineChainMovementGenerator(pointId, chain, walk)); } void MotionMaster::ResumeSplineChain(SplineChainResumeInfo const& info) { if (info.Empty()) { TC_LOG_ERROR("movement.motionmaster", "MotionMaster::ResumeSplineChain: '{}', tried to resume a spline chain from empty info.", _owner->GetGUID().ToString()); return; } Add(new SplineChainMovementGenerator(info)); } void MotionMaster::MoveFall(uint32 id/* = 0*/) { // Use larger distance for vmap height search than in most other cases float tz = _owner->GetMapHeight(_owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ(), true, MAX_FALL_DISTANCE); if (tz <= INVALID_HEIGHT) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveFall: '{}', unable to retrieve a proper height at map Id: {} (X: {}, Y: {}, Z: {})", _owner->GetGUID().ToString(), _owner->GetMap()->GetId(), _owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ()); return; } // Abort too if the ground is very near if (std::fabs(_owner->GetPositionZ() - tz) < 0.1f) return; // rooted units don't move (also setting falling+root flag causes client freezes) if (_owner->HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_STUNNED)) return; _owner->SetFall(true); // Don't run spline movement for players if (_owner->GetTypeId() == TYPEID_PLAYER) { _owner->ToPlayer()->SetFallInformation(0, _owner->GetPositionZ()); return; } std::function initializer = [=, this](Movement::MoveSplineInit& init) { init.MoveTo(_owner->GetPositionX(), _owner->GetPositionY(), tz + _owner->GetHoverOffset(), false); init.SetFall(); }; GenericMovementGenerator* movement = new GenericMovementGenerator(std::move(initializer), EFFECT_MOTION_TYPE, id); movement->Priority = MOTION_PRIORITY_HIGHEST; Add(movement); } void MotionMaster::MoveSeekAssistance(float x, float y, float z) { if (Creature* creature = _owner->ToCreature()) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveSeekAssistance: '{}', seeks assistance (X: {}, Y: {}, Z: {})", creature->GetGUID().ToString(), x, y, z); creature->AttackStop(); creature->CastStop(); creature->DoNotReacquireSpellFocusTarget(); creature->SetReactState(REACT_PASSIVE); Add(new AssistanceMovementGenerator(EVENT_ASSIST_MOVE, x, y, z)); } else TC_LOG_ERROR("movement.motionmaster", "MotionMaster::MoveSeekAssistance: '{}', attempted to seek assistance.", _owner->GetGUID().ToString()); } void MotionMaster::MoveSeekAssistanceDistract(uint32 time) { if (_owner->GetTypeId() == TYPEID_UNIT) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveSeekAssistanceDistract: '{}', is distracted after assistance call (Time: {})", _owner->GetGUID().ToString(), time); Add(new AssistanceDistractMovementGenerator(time, _owner->GetOrientation())); } else TC_LOG_ERROR("movement.motionmaster", "MotionMaster::MoveSeekAssistanceDistract: '{}', attempted to call distract assistance.", _owner->GetGUID().ToString()); } void MotionMaster::MoveTaxiFlight(uint32 path, uint32 pathnode) { if (_owner->GetTypeId() == TYPEID_PLAYER) { if (path < sTaxiPathNodesByPath.size()) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveTaxiFlight: '{}', taxi to path Id: {} (node {})", _owner->GetGUID().ToString(), path, pathnode); // Only one FLIGHT_MOTION_TYPE is allowed bool hasExisting = HasMovementGenerator([](MovementGenerator const* gen) { return gen->GetMovementGeneratorType() == FLIGHT_MOTION_TYPE; }); ASSERT(!hasExisting, "Duplicate flight path movement generator"); FlightPathMovementGenerator* movement = new FlightPathMovementGenerator(); movement->LoadPath(_owner->ToPlayer(), pathnode); Add(movement); } else TC_LOG_ERROR("movement.motionmaster", "MotionMaster::MoveTaxiFlight: '{}', attempted taxi to non-existing path Id: {} (node: {})", _owner->GetGUID().ToString(), path, pathnode); } else TC_LOG_ERROR("movement.motionmaster", "MotionMaster::MoveTaxiFlight: '{}', attempted taxi to path Id: {} (node: {})", _owner->GetGUID().ToString(), path, pathnode); } void MotionMaster::MoveDistract(uint32 timer, float orientation) { /* if (_slot[MOTION_SLOT_CONTROLLED]) return; */ TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveDistract: '{}', distracted (timer: {}, orientation: {})", _owner->GetGUID().ToString(), timer, orientation); Add(new DistractMovementGenerator(timer, orientation)); } void MotionMaster::MovePath(uint32 pathId, bool repeatable, Optional duration, Optional speed, MovementWalkRunSpeedSelectionMode speedSelectionMode, Optional> waitTimeRangeAtPathEnd, Optional wanderDistanceAtPathEnds, bool followPathBackwardsFromEndToStart, bool generatePath) { if (!pathId) return; TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MovePath: '{}', starts moving over path Id: {} (repeatable: {})", _owner->GetGUID().ToString(), pathId, repeatable ? "YES" : "NO"); Add(new WaypointMovementGenerator(pathId, repeatable, duration, speed, speedSelectionMode, waitTimeRangeAtPathEnd, wanderDistanceAtPathEnds, followPathBackwardsFromEndToStart, generatePath), MOTION_SLOT_DEFAULT); } void MotionMaster::MovePath(WaypointPath const& path, bool repeatable, Optional duration, Optional speed, MovementWalkRunSpeedSelectionMode speedSelectionMode, Optional> waitTimeRangeAtPathEnd, Optional wanderDistanceAtPathEnds, bool followPathBackwardsFromEndToStart, bool generatePath) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MovePath: '{}', starts moving over path Id: {} (repeatable: {})", _owner->GetGUID().ToString(), path.Id, repeatable ? "YES" : "NO"); Add(new WaypointMovementGenerator(path, repeatable, duration, speed, speedSelectionMode, waitTimeRangeAtPathEnd, wanderDistanceAtPathEnds, followPathBackwardsFromEndToStart, generatePath), MOTION_SLOT_DEFAULT); } void MotionMaster::MoveRotate(uint32 id, uint32 time, RotateDirection direction) { if (!time) return; TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveRotate: '{}', starts rotate (time: {}, direction: {})", _owner->GetGUID().ToString(), time, direction); Add(new RotateMovementGenerator(id, time, direction)); } void MotionMaster::MoveFormation(Unit* leader, float range, float angle, uint32 point1, uint32 point2) { if (_owner->GetTypeId() == TYPEID_UNIT && leader) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveFormation: '{}', started to move in a formation with leader {}", _owner->GetGUID().ToString(), leader->GetGUID().ToString()); Add(new FormationMovementGenerator(leader, range, angle, point1, point2), MOTION_SLOT_DEFAULT); } } void MotionMaster::LaunchMoveSpline(std::function&& initializer, uint32 id/*= 0*/, MovementGeneratorPriority priority/* = MOTION_PRIORITY_NORMAL*/, MovementGeneratorType type/*= EFFECT_MOTION_TYPE*/) { if (IsInvalidMovementGeneratorType(type)) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::LaunchMoveSpline: '{}', tried to launch a spline with an invalid MovementGeneratorType: {} (Id: {}, Priority: {})", _owner->GetGUID().ToString(), type, id, priority); return; } TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::LaunchMoveSpline: '{}', initiates spline Id: {} (Type: {}, Priority: {})", _owner->GetGUID().ToString(), id, type, priority); GenericMovementGenerator* movement = new GenericMovementGenerator(std::move(initializer), type, id); movement->Priority = priority; Add(movement); } void MotionMaster::CalculateJumpSpeeds(float dist, UnitMoveType moveType, float speedMultiplier, float minHeight, float maxHeight, float& speedXY, float& speedZ) const { float baseSpeed = _owner->IsControlledByPlayer() ? playerBaseMoveSpeed[moveType] : baseMoveSpeed[moveType]; if (Creature* creature = _owner->ToCreature()) baseSpeed *= creature->GetCreatureTemplate()->speed_run; speedXY = std::min(baseSpeed * 3.0f * speedMultiplier, std::max(28.0f, _owner->GetSpeed(moveType) * 4.0f)); float duration = dist / speedXY; float durationSqr = duration * duration; float height; if (durationSqr < minHeight * 8 / Movement::gravity) height = minHeight; else if (durationSqr > maxHeight * 8 / Movement::gravity) height = maxHeight; else height = Movement::gravity * durationSqr / 8; speedZ = std::sqrt(2 * Movement::gravity * height); } /******************** Private methods ********************/ void MotionMaster::ResolveDelayedActions() { while (!_delayedActions.empty()) { _delayedActions.front().Resolve(); _delayedActions.pop_front(); } } void MotionMaster::Remove(MotionMasterContainer::iterator iterator, bool active, bool movementInform) { MovementGenerator* pointer = *iterator; _generators.erase(iterator); Delete(pointer, active, movementInform); } void MotionMaster::Pop(bool active, bool movementInform) { if (!_generators.empty()) Remove(_generators.begin(), active, movementInform); } void MotionMaster::DirectInitialize() { // Clear ALL movement generators (including default) DirectClearDefault(); DirectClear(); InitializeDefault(); } void MotionMaster::DirectClear() { // First delete Top if (!_generators.empty()) Pop(true, false); // Then the rest while (!_generators.empty()) Pop(false, false); // Make sure the storage is empty ClearBaseUnitStates(); } void MotionMaster::DirectClearDefault() { if (_defaultGenerator) DeleteDefault(_generators.empty(), false); } void MotionMaster::DirectClear(std::function const& filter) { if (_generators.empty()) return; MovementGenerator const* top = GetCurrentMovementGenerator(); for (auto itr = _generators.begin(); itr != _generators.end();) { if (filter(*itr)) { MovementGenerator* movement = *itr; itr = _generators.erase(itr); Delete(movement, movement == top, false); } else ++itr; } } void MotionMaster::DirectAdd(MovementGenerator* movement, MovementSlot slot/* = MOTION_SLOT_ACTIVE*/) { /* if (MovementGenerator* curr = _slot[slot]) { _slot[slot] = nullptr; // in case a new one is generated in this slot during directdelete if (_top == slot && (_cleanFlag & MOTIONMMASTER_CLEANFLAG_UPDATE)) DelayedDelete(curr); else DirectDelete(curr); } else if (_top < slot) { _top = slot; } _slot[slot] = m; if (_top > slot) _initialize[slot] = true; else { _initialize[slot] = false; m->Initialize(_owner); } */ /* * NOTE: This mimics old behaviour: only one MOTION_SLOT_IDLE, MOTION_SLOT_ACTIVE, MOTION_SLOT_CONTROLLED * On future changes support for multiple will be added */ switch (slot) { case MOTION_SLOT_DEFAULT: if (_defaultGenerator) _defaultGenerator->Finalize(_owner, _generators.empty(), false); _defaultGenerator = MovementGeneratorPointer(movement); if (IsStatic(movement)) AddFlag(MOTIONMASTER_FLAG_STATIC_INITIALIZATION_PENDING); break; case MOTION_SLOT_ACTIVE: if (!_generators.empty()) { if (movement->Priority >= (*_generators.begin())->Priority) { auto itr = _generators.begin(); if (movement->Priority == (*itr)->Priority) Remove(itr, true, false); else (*itr)->Deactivate(_owner); } else { auto itr = std::find_if(_generators.begin(), _generators.end(), [movement](MovementGenerator const* a) -> bool { return a->Priority == movement->Priority; }); if (itr != _generators.end()) Remove(itr, false, false); } } else _defaultGenerator->Deactivate(_owner); _generators.emplace(movement); AddBaseUnitState(movement); break; default: break; } } void MotionMaster::Delete(MovementGenerator* movement, bool active, bool movementInform) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::Delete: deleting generator (Priority: {}, Flags: {}, BaseUnitState: {}, Type: {}), owner: '{}'", movement->Priority, movement->Flags, movement->BaseUnitState, movement->GetMovementGeneratorType(), _owner->GetGUID().ToString()); movement->Finalize(_owner, active, movementInform); ClearBaseUnitState(movement); MovementGeneratorPointerDeleter(movement); } void MotionMaster::DeleteDefault(bool active, bool movementInform) { TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::DeleteDefault: deleting generator (Priority: {}, Flags: {}, BaseUnitState: {}, Type: {}), owner: '{}'", _defaultGenerator->Priority, _defaultGenerator->Flags, _defaultGenerator->BaseUnitState, _defaultGenerator->GetMovementGeneratorType(), _owner->GetGUID().ToString()); _defaultGenerator->Finalize(_owner, active, movementInform); _defaultGenerator = MovementGeneratorPointer(GetIdleMovementGenerator()); AddFlag(MOTIONMASTER_FLAG_STATIC_INITIALIZATION_PENDING); } void MotionMaster::AddBaseUnitState(MovementGenerator const* movement) { if (!movement || !movement->BaseUnitState) return; _baseUnitStatesMap.emplace(movement->BaseUnitState, movement); _owner->AddUnitState(movement->BaseUnitState); } void MotionMaster::ClearBaseUnitState(MovementGenerator const* movement) { if (!movement || !movement->BaseUnitState) return; Trinity::Containers::MultimapErasePair(_baseUnitStatesMap, movement->BaseUnitState, movement); if (_baseUnitStatesMap.count(movement->BaseUnitState) == 0) _owner->ClearUnitState(movement->BaseUnitState); } void MotionMaster::ClearBaseUnitStates() { uint32 unitState = 0; for (auto itr = _baseUnitStatesMap.begin(); itr != _baseUnitStatesMap.end(); ++itr) unitState |= itr->first; _owner->ClearUnitState(unitState); _baseUnitStatesMap.clear(); }