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

Commit

Permalink
Core/Movement: rewrite formation movement
Browse files Browse the repository at this point in the history
* formation members will now draw their speeds from their formation leader and increase them based on how far they are behind their leader
* added sniffed periodic movement interval which additionally helps keeping up with formation leaders
* the formation movement generator will now persist permanently instead of being a one shot type and handles everything on its own once applied
  • Loading branch information
Ovahlord committed Mar 31, 2020
1 parent 05bdaf4 commit c2a676b
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 135 deletions.
4 changes: 2 additions & 2 deletions src/server/game/Entities/Creature/Creature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,15 +311,15 @@ 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;

if (!m_formation->IsLeader(this))
return;

m_formation->LeaderMoveTo(destination, id, moveType, orientation);
m_formation->LeaderStartedMoving();
}

bool Creature::IsFormationLeaderMoveAllowed() const
Expand Down
2 changes: 1 addition & 1 deletion src/server/game/Entities/Creature/Creature.h
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, 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();
Expand Down
32 changes: 6 additions & 26 deletions src/server/game/Entities/Creature/CreatureGroups.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "Log.h"
#include "Map.h"
#include "MotionMaster.h"
#include "MovementGenerator.h"
#include "ObjectMgr.h"

#include <cmath>
Expand Down Expand Up @@ -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);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/server/game/Entities/Creature/CreatureGroups.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
8 changes: 4 additions & 4 deletions src/server/game/Movement/MotionMaster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/server/game/Movement/MotionMaster.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<float>(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);
}
Loading

0 comments on commit c2a676b

Please sign in to comment.