diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 6f467302619..8237a926dad 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -311,7 +311,7 @@ bool Creature::IsFormationLeader() const return m_formation->IsLeader(this); } -void Creature::SignalFormationMovement(Position const& destination, uint32 id/* = 0*/, uint32 moveType/* = 0*/, bool orientation/* = false*/) +void Creature::SignalFormationMovement() { if (!m_formation) return; @@ -319,7 +319,7 @@ void Creature::SignalFormationMovement(Position const& destination, uint32 id/* if (!m_formation->IsLeader(this)) return; - m_formation->LeaderMoveTo(destination, id, moveType, orientation); + m_formation->LeaderStartedMoving(); } bool Creature::IsFormationLeaderMoveAllowed() const diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index d2a9dbc66f6..f823de369d4 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -297,7 +297,7 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma CreatureGroup* GetFormation() { return m_formation; } void SetFormation(CreatureGroup* formation) { m_formation = formation; } bool IsFormationLeader() const; - void SignalFormationMovement(Position const& destination, uint32 id = 0, uint32 moveType = 0, bool orientation = false); + void SignalFormationMovement(); bool IsFormationLeaderMoveAllowed() const; Unit* SelectVictim(); diff --git a/src/server/game/Entities/Creature/CreatureGroups.cpp b/src/server/game/Entities/Creature/CreatureGroups.cpp index 19bed3c73cc..9c45fd3499a 100644 --- a/src/server/game/Entities/Creature/CreatureGroups.cpp +++ b/src/server/game/Entities/Creature/CreatureGroups.cpp @@ -22,6 +22,7 @@ #include "Log.h" #include "Map.h" #include "MotionMaster.h" +#include "MovementGenerator.h" #include "ObjectMgr.h" #include @@ -226,44 +227,23 @@ void CreatureGroup::FormationReset(bool dismiss) m_Formed = !dismiss; } -void CreatureGroup::LeaderMoveTo(Position destination, uint32 id /*= 0*/, uint32 moveType /*= 0*/, bool orientation /*= false*/) +void CreatureGroup::LeaderStartedMoving() { - //! To do: This should probably get its own movement generator or use WaypointMovementGenerator. - //! If the leader's path is known, member's path can be plotted as well using formation offsets. if (!m_leader) return; - float x = destination.GetPositionX(), y = destination.GetPositionY(), z = destination.GetPositionZ(); - - float pathangle = std::atan2(m_leader->GetPositionY() - y, m_leader->GetPositionX() - x); - for (CreatureGroupMemberType::iterator itr = m_members.begin(); itr != m_members.end(); ++itr) { Creature* member = itr->first; if (member == m_leader || !member->IsAlive() || member->GetVictim() || !(itr->second->groupAI & FLAG_IDLE_IN_FORMATION)) continue; - if (itr->second->point_1) - if (m_leader->GetCurrentWaypointInfo().first == itr->second->point_1 || m_leader->GetCurrentWaypointInfo().first == itr->second->point_2) - itr->second->follow_angle = float(M_PI) * 2 - itr->second->follow_angle; - - float angle = itr->second->follow_angle; + float angle = itr->second->follow_angle + float(M_PI); float dist = itr->second->follow_dist; - float dx = x + std::cos(angle + pathangle) * dist; - float dy = y + std::sin(angle + pathangle) * dist; - float dz = z; - - Trinity::NormalizeMapCoord(dx); - Trinity::NormalizeMapCoord(dy); - - if (!member->IsFlying()) - member->UpdateGroundPositionZ(dx, dy, dz); - - Position point(dx, dy, dz, destination.GetOrientation()); - - member->GetMotionMaster()->MoveFormation(id, point, moveType, !member->IsWithinDist(m_leader, dist + MAX_DESYNC), orientation); - member->SetHomePosition(dx, dy, dz, pathangle); + MovementGenerator const* moveGen = member->GetMotionMaster()->GetMotionSlot(MOTION_SLOT_IDLE); + if (!moveGen || moveGen->GetMovementGeneratorType() != FORMATION_MOTION_TYPE) + member->GetMotionMaster()->MoveFormation(m_leader, dist, angle, itr->second->point_1, itr->second->point_2); } } diff --git a/src/server/game/Entities/Creature/CreatureGroups.h b/src/server/game/Entities/Creature/CreatureGroups.h index 2c6e9865d67..7d2f8be38fe 100644 --- a/src/server/game/Entities/Creature/CreatureGroups.h +++ b/src/server/game/Entities/Creature/CreatureGroups.h @@ -90,7 +90,7 @@ class TC_GAME_API CreatureGroup void RemoveMember(Creature* member); void FormationReset(bool dismiss); - void LeaderMoveTo(Position destination, uint32 id = 0, uint32 moveType = 0, bool orientation = false); + void LeaderStartedMoving(); void MemberAttackStart(Creature* member, Unit* target); bool CanLeaderStartMoving() const; }; diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index 9c1fd9eee17..27b3ad76397 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -707,12 +707,12 @@ void MotionMaster::MoveRotate(uint32 time, RotateDirection direction) Mutate(new RotateMovementGenerator(time, direction), MOTION_SLOT_ACTIVE); } -void MotionMaster::MoveFormation(uint32 id, Position destination, uint32 moveType, bool forceRun /*= false*/, bool forceOrientation /*= false*/) +void MotionMaster::MoveFormation(Unit* leader, float range, float angle, uint32 point1, uint32 point2) { - if (_owner->GetTypeId() == TYPEID_UNIT) + if (_owner->GetTypeId() == TYPEID_UNIT && leader) { - TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveFormation: '%s', targeted point Id: %u (X: %f, Y: %f, Z: %f)", _owner->GetGUID().ToString().c_str(), id, destination.GetPositionX(), destination.GetPositionY(), destination.GetPositionZ()); - Mutate(new FormationMovementGenerator(id, destination, moveType, forceRun, forceOrientation), MOTION_SLOT_ACTIVE); + TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveFormation: '%s', started to move in a formation with leader %s", _owner->GetGUID().ToString().c_str(), leader->GetGUID().ToString().c_str()); + Mutate(new FormationMovementGenerator(leader, range, angle, point1, point2), MOTION_SLOT_IDLE); } } diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h index 781118f825a..4b1df92d78d 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -209,7 +209,7 @@ class TC_GAME_API MotionMaster void MovePath(WaypointPath& path, bool repeatable); void MoveRotate(uint32 time, RotateDirection direction); - void MoveFormation(uint32 id, Position destination, uint32 moveType, bool forceRun = false, bool forceOrientation = false); + void MoveFormation(Unit* leader, float range, float angle, uint32 point1, uint32 point2); void LaunchMoveSpline(Movement::MoveSplineInit&& init, uint32 id = 0, MovementSlot slot = MOTION_SLOT_ACTIVE, MovementGeneratorType type = EFFECT_MOTION_TYPE); private: diff --git a/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.cpp index a1382ec673e..0ea55c1d1ff 100644 --- a/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.cpp @@ -17,114 +17,165 @@ #include "Creature.h" #include "CreatureAI.h" +#include "CreatureGroups.h" +#include "G3DPosition.hpp" #include "MoveSplineInit.h" #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) { } + void FormationMovementGenerator::DoInitialize(Creature* owner) { owner->AddUnitState(UNIT_STATE_ROAMING); + _nextMoveTimer.Reset(0); +} - if (owner->HasUnitState(UNIT_STATE_NOT_MOVE) || owner->IsMovementPreventedByCasting()) - { - _interrupt = true; - owner->StopMoving(); - return; - } - - owner->AddUnitState(UNIT_STATE_ROAMING_MOVE); +bool FormationMovementGenerator::DoUpdate(Creature* owner, uint32 diff) +{ + Unit* target = GetTarget(); - Movement::MoveSplineInit init(owner); - init.MoveTo(_destination.GetPositionX(), _destination.GetPositionY(), _destination.GetPositionZ()); - if (_orientation) - init.SetFacing(_destination.GetOrientation()); + if (!owner || !target) + return false; - switch (_moveType) + // Owner cannot move. Reset all fields and wait for next action + if (owner->HasUnitState(UNIT_STATE_NOT_MOVE) || owner->IsMovementPreventedByCasting()) { - case 2: // WAYPOINT_MOVE_TYPE_LAND - init.SetAnimation(Movement::ToGround); - break; - case 3: // WAYPOINT_MOVE_TYPE_TAKEOFF - init.SetAnimation(Movement::ToFly); - break; - case 1: // WAYPOINT_MOVE_TYPE_RUN - init.SetWalk(false); - break; - case 0: // WAYPOINT_MOVE_TYPE_WALK - init.SetWalk(true); - break; + _nextMoveTimer.Reset(0); + _hasPredictedDestination = false; + owner->ClearUnitState(UNIT_STATE_ROAMING_MOVE); + owner->StopMoving(); + return true; } - if (_run) - init.SetWalk(false); - - init.Launch(); -} - -bool FormationMovementGenerator::DoUpdate(Creature* owner, uint32 /*diff*/) -{ - if (!owner) - return false; + // Update home position + owner->SetHomePosition(owner->GetPosition()); - if (owner->HasUnitState(UNIT_STATE_NOT_MOVE) || owner->IsMovementPreventedByCasting()) + // Leader has stopped moving, so do we as well + if (owner->HasUnitState(UNIT_STATE_ROAMING_MOVE) && _hasPredictedDestination && target->movespline->Finalized() && target->movespline->GetId() == _lastLeaderSplineID) { - _interrupt = true; owner->StopMoving(); + _nextMoveTimer.Reset(0); + _hasPredictedDestination = false; return true; } - if ((_interrupt && owner->movespline->Finalized()) || (_recalculateSpeed && !owner->movespline->Finalized())) + // Formation leader has launched a new spline, launch a new one for our member as well + // This action does not reset the regular movement launch cycle interval + if (!target->movespline->Finalized() && target->movespline->GetId() != _lastLeaderSplineID) { - _recalculateSpeed = false; - _interrupt = false; + // Update formation angle + if (_point1 && target->IsCreature()) + { + if (CreatureGroup* formation = target->ToCreature()->GetFormation()) + { + if (Creature* leader = formation->getLeader()) + { + uint8 currentWaypoint = leader->GetCurrentWaypointInfo().first; + if (currentWaypoint == _point1 || currentWaypoint == _point2) + _angle = float(M_PI) * 2 - _angle; + } + } + } - owner->AddUnitState(UNIT_STATE_ROAMING_MOVE); + LaunchMovement(owner, target); + _lastLeaderSplineID = target->movespline->GetId(); + return true; + } - Movement::MoveSplineInit init(owner); - init.MoveTo(_destination.GetPositionX(), _destination.GetPositionY(), _destination.GetPositionZ()); - if (_orientation) - init.SetFacing(_destination.GetOrientation()); + _nextMoveTimer.Update(diff); + if (_nextMoveTimer.Passed()) + { + _nextMoveTimer.Reset(FORMATION_MOVEMENT_INTERVAL); - switch (_moveType) + // Our leader has a different position than on our last check, launch movement. + if (_lastLeaderPosition != target->GetPosition()) { - case 2: // WAYPOINT_MOVE_TYPE_LAND - init.SetAnimation(Movement::ToGround); - break; - case 3: // WAYPOINT_MOVE_TYPE_TAKEOFF - init.SetAnimation(Movement::ToFly); - break; - case 1: // WAYPOINT_MOVE_TYPE_RUN - init.SetWalk(false); - break; - case 0: // WAYPOINT_MOVE_TYPE_WALK - init.SetWalk(true); - break; + LaunchMovement(owner, target); + return true; } + } - if (_run) - init.SetWalk(false); - init.Launch(); + // We have reached our destination before launching a new movement. Alling facing with leader + if (owner->HasUnitState(UNIT_STATE_ROAMING_MOVE) && owner->movespline->Finalized()) + { + owner->ClearUnitState(UNIT_STATE_ROAMING_MOVE); + owner->SetFacingTo(target->GetOrientation()); + MovementInform(owner); } - return !owner->movespline->Finalized(); + return true; } -void FormationMovementGenerator::DoFinalize(Creature* owner) +void FormationMovementGenerator::LaunchMovement(Creature* owner, Unit* target) { - owner->ClearUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE); + float relativeAngle = 0.f; + + // Determine our relative angle to our current spline destination point + if (!target->movespline->Finalized()) + relativeAngle = target->GetRelativeAngle(Vector3ToPosition(target->movespline->CurrentDestination())); + + // Destination calculation + /* + According to sniff data, formation members have a periodic move interal of 1,2s. + Each of these splines has a exact duration of 1650ms +- 1ms when no pathfinding is involved. + To get a representative result like that we have to predict our formation leader's path + and apply our formation shape based on that destination. + */ + Position dest = target->GetPosition(); + float velocity = 0.f; + + // Formation leader is moving. Predict our destination + if (!target->movespline->Finalized()) + { + // Pick up leader's spline velocity + velocity = target->movespline->Velocity(); - if (owner->movespline->Finalized()) - MovementInform(owner); + // Calculate travel distance to get a 1650ms result + float travelDist = velocity * 1.65f; + + // Move destination ahead... + target->MovePositionToFirstCollision(dest, travelDist, relativeAngle); + // ... and apply formation shape + target->MovePositionToFirstCollision(dest, _range, _angle + relativeAngle); + + float distance = owner->GetExactDist(dest); + + // Calculate catchup speed mod (Limit to a maximum of 50% of our original velocity + float velocityMod = std::min(distance / travelDist, 1.5f); + + // Now we will always stay synch with our leader + velocity *= velocityMod; + _hasPredictedDestination = true; + } + else + { + // Formation leader is not moving. Just apply the base formation shape on his position. + target->MovePositionToFirstCollision(dest, _range, _angle + relativeAngle); + _hasPredictedDestination = false; + } + + // Leader is not moving, so just pick up his default walk speed + if (velocity == 0.f) + velocity = target->GetSpeed(MOVE_WALK); + + Movement::MoveSplineInit init(owner); + init.MoveTo(PositionToVector3(dest)); + init.SetVelocity(velocity); + init.Launch(); + + _lastLeaderPosition = target->GetPosition(); + owner->AddUnitState(UNIT_STATE_ROAMING_MOVE); } -void FormationMovementGenerator::DoReset(Creature* owner) +void FormationMovementGenerator::DoFinalize(Creature* owner) { - owner->StopMoving(); - DoInitialize(owner); + owner->ClearUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE); } void FormationMovementGenerator::MovementInform(Creature* owner) { if (owner->AI()) - owner->AI()->MovementInform(FORMATION_MOTION_TYPE, _movementId); + owner->AI()->MovementInform(FORMATION_MOTION_TYPE, 0); } diff --git a/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.h b/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.h index 27ab7d50da5..d991c14fb6d 100644 --- a/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/FormationMovementGenerator.h @@ -18,32 +18,37 @@ #ifndef TRINITY_FORMATIONMOVEMENTGENERATOR_H #define TRINITY_FORMATIONMOVEMENTGENERATOR_H +#include "AbstractFollower.h" #include "MovementGenerator.h" +#include "Timer.h" -class FormationMovementGenerator : public MovementGeneratorMedium< Creature, FormationMovementGenerator > +class FormationMovementGenerator : public MovementGeneratorMedium, public AbstractFollower { public: - explicit FormationMovementGenerator(uint32 id, Position destination, uint32 moveType, bool run, bool orientation) : _movementId(id), _destination(destination), _moveType(moveType), _run(run), _orientation(orientation), _recalculateSpeed(false), _interrupt(false) { } + explicit FormationMovementGenerator(Unit* leader, float range, float angle, uint32 point1, uint32 point2); MovementGeneratorType GetMovementGeneratorType() const override { return FORMATION_MOTION_TYPE; } - void DoInitialize(Creature*); - void DoFinalize(Creature*); - void DoReset(Creature*); - bool DoUpdate(Creature*, uint32); - - void UnitSpeedChanged() override { _recalculateSpeed = true; } + void DoInitialize(Creature* owner); + void DoFinalize(Creature* owner); + void DoReset(Creature* owner) { DoInitialize(owner); }; + bool DoUpdate(Creature* owner, uint32 diff); private: - void MovementInform(Creature*); - - uint32 _movementId; - Position _destination; - uint32 _moveType; - bool _run; - bool _orientation; - bool _recalculateSpeed; - bool _interrupt; + void MovementInform(Creature* owner); + + void LaunchMovement(Creature* owner, Unit* target); + + static constexpr uint32 FORMATION_MOVEMENT_INTERVAL = 1200; // sniffed (3 batch update cycles) + float const _range; + float _angle; + uint32 const _point1; + uint32 const _point2; + uint32 _lastLeaderSplineID; + bool _hasPredictedDestination; + + Position _lastLeaderPosition; + TimeTrackerSmall _nextMoveTimer; }; #endif // TRINITY_FORMATIONMOVEMENTGENERATOR_H diff --git a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp index 2a298d2453e..b42bca2ac51 100755 --- a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp @@ -53,7 +53,7 @@ void PointMovementGenerator::DoInitialize(T* owner) // Call for creature group update if (Creature* creature = owner->ToCreature()) - creature->SignalFormationMovement(Position(_x, _y, _z), _movementId); + creature->SignalFormationMovement(); } template @@ -87,7 +87,7 @@ bool PointMovementGenerator::DoUpdate(T* owner, uint32 /*diff*/) // Call for creature group update if (Creature* creature = owner->ToCreature()) - creature->SignalFormationMovement(Position(_x, _y, _z), _movementId); + creature->SignalFormationMovement(); } return !owner->movespline->Finalized(); diff --git a/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp index ed851495f8d..c9e53aeb988 100644 --- a/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp @@ -160,7 +160,7 @@ void RandomMovementGenerator::SetRandomLocation(Creature* owner) } // Call for creature group update - owner->SignalFormationMovement(position); + owner->SignalFormationMovement(); } template diff --git a/src/server/game/Movement/MovementGenerators/SplineChainMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/SplineChainMovementGenerator.cpp index bbc48bb30f4..6ff71c46c5e 100644 --- a/src/server/game/Movement/MovementGenerators/SplineChainMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/SplineChainMovementGenerator.cpp @@ -44,6 +44,10 @@ uint32 SplineChainMovementGenerator::SendPathSpline(Unit* me, float velocity, Mo else init.SetWalk(_walk); + // inform formation + if (me->IsCreature()) + me->ToCreature()->SignalFormationMovement(); + return init.Launch(); } diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp index 01714cfce2e..7dbcf75437c 100755 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp @@ -173,7 +173,6 @@ void WaypointMovementGenerator::StartMove(Creature* creature, bool rel ASSERT(_currentNode < _path->nodes.size(), "WaypointMovementGenerator::StartMove: tried to reference a node id (%u) which is not included in path (%u)", _currentNode, _path->id); WaypointNode const &waypoint = _path->nodes[_currentNode]; - Position formationDest(waypoint.x, waypoint.y, waypoint.z, (waypoint.orientation && waypoint.delay) ? waypoint.orientation : 0.0f); _isArrivalDone = false; _recalculateSpeed = false; @@ -184,15 +183,7 @@ void WaypointMovementGenerator::StartMove(Creature* creature, bool rel //! If creature is on transport, we assume waypoints set in DB are already transport offsets if (transportPath) - { init.DisableTransportPathTransformations(); - if (TransportBase* trans = creature->GetDirectTransport()) - { - float orientation = formationDest.GetOrientation(); - trans->CalculatePassengerPosition(formationDest.m_positionX, formationDest.m_positionY, formationDest.m_positionZ, &orientation); - formationDest.SetOrientation(orientation); - } - } //! Do not use formationDest here, MoveTo requires transport offsets due to DisableTransportPathTransformations() call //! but formationDest contains global coordinates @@ -223,7 +214,7 @@ void WaypointMovementGenerator::StartMove(Creature* creature, bool rel init.Launch(); // inform formation - creature->SignalFormationMovement(formationDest, waypoint.id, waypoint.moveType, (waypoint.orientation && waypoint.delay) ? true : false); + creature->SignalFormationMovement(); return; }