Core/Movement: Formation Rewrite
Port From (https://github.com/TrinityCore/TrinityCore/commit/924116f0461f5e1e03a026129b81dfe23faa20e4)
This commit is contained in:
@@ -130,7 +130,7 @@ namespace Game.Entities
|
||||
return m_formation.IsLeader(this);
|
||||
}
|
||||
|
||||
public void SignalFormationMovement(Position destination, uint id = 0, WaypointMoveType moveType = 0, bool orientation = false)
|
||||
public void SignalFormationMovement()
|
||||
{
|
||||
if (m_formation == null)
|
||||
return;
|
||||
@@ -138,7 +138,7 @@ namespace Game.Entities
|
||||
if (!m_formation.IsLeader(this))
|
||||
return;
|
||||
|
||||
m_formation.LeaderMoveTo(destination, id, moveType, orientation);
|
||||
m_formation.LeaderStartedMoving();
|
||||
}
|
||||
|
||||
public bool IsFormationLeaderMoveAllowed()
|
||||
|
||||
@@ -274,51 +274,23 @@ namespace Game.Entities
|
||||
_formed = !dismiss;
|
||||
}
|
||||
|
||||
public void LeaderMoveTo(Position destination, uint id = 0, WaypointMoveType moveType = 0, bool orientation = false)
|
||||
public void 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 (_leader == null)
|
||||
return;
|
||||
|
||||
Position pos = new(destination);
|
||||
float pathangle = (float)Math.Atan2(_leader.GetPositionY() - pos.GetPositionY(), _leader.GetPositionX() - pos.GetPositionX());
|
||||
|
||||
foreach (var pair in _members)
|
||||
{
|
||||
Creature member = pair.Key;
|
||||
if (member == _leader || !member.IsAlive() || member.IsEngaged() || !pair.Value.GroupAI.HasAnyFlag((uint)GroupAIFlags.IdleInFormation))
|
||||
continue;
|
||||
|
||||
if (pair.Value.LeaderWaypointIDs[0] != 0)
|
||||
{
|
||||
for (var i = 0; i < 2; ++i)
|
||||
{
|
||||
if (_leader.GetCurrentWaypointInfo().nodeId == pair.Value.LeaderWaypointIDs[i])
|
||||
{
|
||||
pair.Value.FollowAngle = MathF.PI * 2f - pair.Value.FollowAngle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float angle = pair.Value.FollowAngle;
|
||||
float angle = pair.Value.FollowAngle + MathF.PI; // for some reason, someone thought it was a great idea to invert relativ angles...
|
||||
float dist = pair.Value.FollowDist;
|
||||
|
||||
float dx = pos.GetPositionX() + MathF.Cos(angle + pathangle) * dist;
|
||||
float dy = pos.GetPositionY() + MathF.Sin(angle + pathangle) * dist;
|
||||
float dz = pos.GetPositionZ();
|
||||
|
||||
GridDefines.NormalizeMapCoord(ref dx);
|
||||
GridDefines.NormalizeMapCoord(ref dy);
|
||||
|
||||
if (!member.IsFlying())
|
||||
member.UpdateGroundPositionZ(dx, dy, ref dz);
|
||||
|
||||
member.SetHomePosition(dx, dy, dz, pathangle);
|
||||
|
||||
Position point = new(dx, dy, dz, destination.GetOrientation());
|
||||
member.GetMotionMaster().MoveFormation(id, point, moveType, !member.IsWithinDist(_leader, dist + 5.0f), orientation);
|
||||
var moveGen = member.GetMotionMaster().GetMovementGenerator(movement => { return movement.GetMovementGeneratorType() == MovementGeneratorType.Formation; }, MovementSlot.Default);
|
||||
if (moveGen == null)
|
||||
member.GetMotionMaster().MoveFormation(_leader, dist, angle, pair.Value.LeaderWaypointIDs[0], pair.Value.LeaderWaypointIDs[1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,18 +17,32 @@
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.Entities;
|
||||
using System;
|
||||
|
||||
namespace Game.Movement
|
||||
{
|
||||
public class FormationMovementGenerator : MovementGeneratorMedium<Creature>
|
||||
{
|
||||
public FormationMovementGenerator(uint id, Position destination, WaypointMoveType moveType, bool run, bool orientation)
|
||||
AbstractFollower _abstractFollower;
|
||||
|
||||
static int FORMATION_MOVEMENT_INTERVAL = 1200; // sniffed (3 batch update cycles)
|
||||
float _range;
|
||||
float _angle;
|
||||
uint _point1;
|
||||
uint _point2;
|
||||
uint _lastLeaderSplineID;
|
||||
bool _hasPredictedDestination;
|
||||
|
||||
Position _lastLeaderPosition;
|
||||
TimeTrackerSmall _nextMoveTimer = new();
|
||||
|
||||
public FormationMovementGenerator(Unit leader, float range, float angle, uint point1, uint point2)
|
||||
{
|
||||
_movementId = id;
|
||||
_destination = destination;
|
||||
_moveType = moveType;
|
||||
_run = run;
|
||||
_orientation = orientation;
|
||||
_abstractFollower = new(leader);
|
||||
_range = range;
|
||||
_angle = angle;
|
||||
_point1 = point1;
|
||||
_point2 = point2;
|
||||
|
||||
Mode = MovementGeneratorMode.Default;
|
||||
Priority = MovementGeneratorPriority.Normal;
|
||||
@@ -48,33 +62,7 @@ namespace Game.Movement
|
||||
return;
|
||||
}
|
||||
|
||||
owner.AddUnitState(UnitState.RoamingMove);
|
||||
|
||||
MoveSplineInit init = new(owner);
|
||||
init.MoveTo(_destination.GetPositionX(), _destination.GetPositionY(), _destination.GetPositionZ());
|
||||
if (_orientation)
|
||||
init.SetFacing(_destination.GetOrientation());
|
||||
|
||||
switch (_moveType)
|
||||
{
|
||||
case WaypointMoveType.Land:
|
||||
init.SetAnimation(AnimType.ToGround);
|
||||
break;
|
||||
case WaypointMoveType.Takeoff:
|
||||
init.SetAnimation(AnimType.ToFly);
|
||||
break;
|
||||
case WaypointMoveType.Run:
|
||||
init.SetWalk(false);
|
||||
break;
|
||||
case WaypointMoveType.Walk:
|
||||
init.SetWalk(true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_run)
|
||||
init.SetWalk(false);
|
||||
|
||||
init.Launch();
|
||||
_nextMoveTimer.Reset(0);
|
||||
}
|
||||
|
||||
public override void DoReset(Creature owner)
|
||||
@@ -86,57 +74,154 @@ namespace Game.Movement
|
||||
|
||||
public override bool DoUpdate(Creature owner, uint diff)
|
||||
{
|
||||
if (!owner)
|
||||
Unit target = _abstractFollower.GetTarget();
|
||||
|
||||
if (owner == null || target == null)
|
||||
return false;
|
||||
|
||||
// Owner cannot move. Reset all fields and wait for next action
|
||||
if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting())
|
||||
{
|
||||
AddFlag(MovementGeneratorFlags.Interrupted);
|
||||
owner.ClearUnitState(UnitState.RoamingMove);
|
||||
owner.StopMoving();
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((HasFlag(MovementGeneratorFlags.Interrupted) && owner.MoveSpline.Finalized()) || (HasFlag(MovementGeneratorFlags.SpeedUpdatePending) && !owner.MoveSpline.Finalized()))
|
||||
// Leader has stopped moving, so do we as well
|
||||
if (target.MoveSpline.Finalized() && target.MoveSpline.GetId() == _lastLeaderSplineID && _hasPredictedDestination)
|
||||
{
|
||||
RemoveFlag(MovementGeneratorFlags.Interrupted | MovementGeneratorFlags.SpeedUpdatePending);
|
||||
owner.ClearUnitState(UnitState.RoamingMove);
|
||||
owner.StopMoving();
|
||||
_nextMoveTimer.Reset(0);
|
||||
_hasPredictedDestination = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
owner.AddUnitState(UnitState.RoamingMove);
|
||||
// Update home position
|
||||
owner.SetHomePosition(owner.GetPosition());
|
||||
if (HasFlag(MovementGeneratorFlags.Interrupted))
|
||||
RemoveFlag(MovementGeneratorFlags.Interrupted);
|
||||
|
||||
MoveSplineInit init = new(owner);
|
||||
init.MoveTo(_destination.GetPositionX(), _destination.GetPositionY(), _destination.GetPositionZ());
|
||||
if (_orientation)
|
||||
init.SetFacing(_destination.GetOrientation());
|
||||
// Leader has stopped moving, so do we as well
|
||||
if (owner.HasUnitState(UnitState.RoamingMove) && _hasPredictedDestination && target.MoveSpline.Finalized() && target.MoveSpline.GetId() == _lastLeaderSplineID)
|
||||
{
|
||||
owner.StopMoving();
|
||||
_nextMoveTimer.Reset(0);
|
||||
_hasPredictedDestination = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (_moveType)
|
||||
// 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)
|
||||
{
|
||||
// Update formation angle
|
||||
if (_point1 != 0 && target.IsCreature())
|
||||
{
|
||||
case WaypointMoveType.Land:
|
||||
init.SetAnimation(AnimType.ToGround);
|
||||
break;
|
||||
case WaypointMoveType.Takeoff:
|
||||
init.SetAnimation(AnimType.ToFly);
|
||||
break;
|
||||
case WaypointMoveType.Run:
|
||||
init.SetWalk(false);
|
||||
break;
|
||||
case WaypointMoveType.Walk:
|
||||
init.SetWalk(true);
|
||||
break;
|
||||
CreatureGroup formation = target.ToCreature().GetFormation();
|
||||
if (formation != null)
|
||||
{
|
||||
Creature leader = formation.GetLeader();
|
||||
if (leader != null)
|
||||
{
|
||||
uint currentWaypoint = leader.GetCurrentWaypointInfo().nodeId;
|
||||
if (currentWaypoint == _point1 || currentWaypoint == _point2)
|
||||
_angle = MathF.PI * 2 - _angle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_run)
|
||||
init.SetWalk(false);
|
||||
init.Launch();
|
||||
LaunchMovement(owner, target);
|
||||
_lastLeaderSplineID = target.MoveSpline.GetId();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (owner.MoveSpline.Finalized())
|
||||
_nextMoveTimer.Update((int)diff);
|
||||
if (_nextMoveTimer.Passed())
|
||||
{
|
||||
RemoveFlag(MovementGeneratorFlags.Transitory);
|
||||
AddFlag(MovementGeneratorFlags.InformEnabled);
|
||||
return false;
|
||||
_nextMoveTimer.Reset(FORMATION_MOVEMENT_INTERVAL);
|
||||
|
||||
// Our leader has a different position than on our last check, launch movement.
|
||||
if (_lastLeaderPosition != target.GetPosition())
|
||||
{
|
||||
LaunchMovement(owner, target);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We have reached our destination before launching a new movement. Alling facing with leader
|
||||
if (owner.HasUnitState(UnitState.RoamingMove) && owner.MoveSpline.Finalized())
|
||||
{
|
||||
owner.ClearUnitState(UnitState.RoamingMove);
|
||||
owner.SetFacingTo(target.GetOrientation());
|
||||
MovementInform(owner);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LaunchMovement(Creature owner, Unit target)
|
||||
{
|
||||
float relativeAngle = 0.0f;
|
||||
|
||||
// Determine our relative angle to our current spline destination point
|
||||
if (!target.MoveSpline.Finalized())
|
||||
relativeAngle = target.GetRelativeAngle(new Position(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.0f;
|
||||
|
||||
// Formation leader is moving. Predict our destination
|
||||
if (!target.MoveSpline.Finalized())
|
||||
{
|
||||
// Pick up leader's spline velocity
|
||||
velocity = target.MoveSpline.velocity;
|
||||
|
||||
// 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 = Math.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.0f)
|
||||
velocity = target.GetSpeed(UnitMoveType.Walk);
|
||||
|
||||
MoveSplineInit init = new(owner);
|
||||
init.MoveTo(dest);
|
||||
init.SetVelocity(velocity);
|
||||
init.Launch();
|
||||
|
||||
_lastLeaderPosition = target.GetPosition();
|
||||
owner.AddUnitState(UnitState.RoamingMove);
|
||||
}
|
||||
|
||||
public override void DoDeactivate(Creature owner)
|
||||
{
|
||||
AddFlag(MovementGeneratorFlags.Deactivated);
|
||||
@@ -166,13 +251,7 @@ namespace Game.Movement
|
||||
void MovementInform(Creature owner)
|
||||
{
|
||||
if (owner.GetAI() != null)
|
||||
owner.GetAI().MovementInform(MovementGeneratorType.Formation, _movementId);
|
||||
owner.GetAI().MovementInform(MovementGeneratorType.Formation, 0);
|
||||
}
|
||||
|
||||
uint _movementId;
|
||||
Position _destination;
|
||||
WaypointMoveType _moveType;
|
||||
bool _run;
|
||||
bool _orientation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Game.Movement
|
||||
// Call for creature group update
|
||||
Creature creature = owner.ToCreature();
|
||||
if (creature != null)
|
||||
creature.SignalFormationMovement(_destination, _movementId);
|
||||
creature.SignalFormationMovement();
|
||||
}
|
||||
|
||||
public override void DoReset(T owner)
|
||||
@@ -124,7 +124,7 @@ namespace Game.Movement
|
||||
// Call for creature group update
|
||||
Creature creature = owner.ToCreature();
|
||||
if (creature != null)
|
||||
creature.SignalFormationMovement(_destination, _movementId);
|
||||
creature.SignalFormationMovement();
|
||||
}
|
||||
|
||||
if (owner.MoveSpline.Finalized())
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace Game.Movement
|
||||
}
|
||||
|
||||
// Call for creature group update
|
||||
owner.SignalFormationMovement(position);
|
||||
owner.SignalFormationMovement();
|
||||
}
|
||||
|
||||
public override void UnitSpeedChanged() { AddFlag(MovementGeneratorFlags.SpeedUpdatePending); }
|
||||
|
||||
@@ -341,7 +341,6 @@ namespace Game.Movement
|
||||
|
||||
Cypher.Assert(_currentNode < _path.nodes.Count, $"WaypointMovementGenerator.StartMove: tried to reference a node id ({_currentNode}) which is not included in path ({_path.id})");
|
||||
WaypointNode waypoint = _path.nodes[_currentNode];
|
||||
Position formationDest = new(waypoint.x, waypoint.y, waypoint.z, (waypoint.orientation != 0 && waypoint.delay != 0) ? waypoint.orientation : 0.0f);
|
||||
|
||||
RemoveFlag(MovementGeneratorFlags.Transitory | MovementGeneratorFlags.InformEnabled | MovementGeneratorFlags.TimedPaused);
|
||||
|
||||
@@ -351,16 +350,7 @@ namespace Game.Movement
|
||||
|
||||
//! If creature is on transport, we assume waypoints set in DB are already transport offsets
|
||||
if (transportPath)
|
||||
{
|
||||
init.DisableTransportPathTransformations();
|
||||
ITransport trans = owner.GetDirectTransport();
|
||||
if (trans != null)
|
||||
{
|
||||
float orientation = formationDest.GetOrientation();
|
||||
trans.CalculatePassengerPosition(ref formationDest.posX, ref formationDest.posY, ref formationDest.posZ, ref orientation);
|
||||
formationDest.SetOrientation(orientation);
|
||||
}
|
||||
}
|
||||
|
||||
//! Do not use formationDest here, MoveTo requires transport offsets due to DisableTransportPathTransformations() call
|
||||
//! but formationDest contains global coordinates
|
||||
@@ -389,7 +379,7 @@ namespace Game.Movement
|
||||
init.Launch();
|
||||
|
||||
// inform formation
|
||||
owner.SignalFormationMovement(formationDest, waypoint.id, waypoint.moveType, (waypoint.orientation != 0 && waypoint.delay != 0));
|
||||
owner.SignalFormationMovement();
|
||||
}
|
||||
|
||||
bool ComputeNextNode()
|
||||
|
||||
@@ -994,10 +994,10 @@ namespace Game.Movement
|
||||
Add(new RotateMovementGenerator(id, time, direction));
|
||||
}
|
||||
|
||||
public void MoveFormation(uint id, Position destination, WaypointMoveType moveType, bool forceRun = false, bool forceOrientation = false)
|
||||
public void MoveFormation(Unit leader, float range, float angle, uint point1, uint point2)
|
||||
{
|
||||
if (_owner.GetTypeId() == TypeId.Unit)
|
||||
Add(new FormationMovementGenerator(id, destination, moveType, forceRun, forceOrientation));
|
||||
if (_owner.GetTypeId() == TypeId.Unit && leader != null)
|
||||
Add(new FormationMovementGenerator(leader, range, angle, point1, point2), MovementSlot.Default);
|
||||
}
|
||||
|
||||
public void LaunchMoveSpline(MoveSplineInit init, uint id = 0, MovementGeneratorPriority priority = MovementGeneratorPriority.Normal, MovementGeneratorType type = MovementGeneratorType.Effect)
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace Game.Movement
|
||||
anim_tier = args.animTier;
|
||||
splineIsFacingOnly = args.path.Count == 2 && args.facing.type != MonsterMoveType.Normal && ((args.path[1] - args.path[0]).Length() < 0.1f);
|
||||
|
||||
velocity = args.velocity;
|
||||
|
||||
// Check if its a stop spline
|
||||
if (args.flags.HasFlag(SplineFlag.Done))
|
||||
{
|
||||
@@ -141,7 +143,7 @@ namespace Game.Movement
|
||||
time_passed = Duration();
|
||||
}
|
||||
public Vector4 ComputePosition(int time_point, int point_index)
|
||||
{
|
||||
{
|
||||
float u = 1.0f;
|
||||
int seg_time = spline.Length(point_index, point_index + 1);
|
||||
if (seg_time > 0)
|
||||
@@ -255,7 +257,7 @@ namespace Game.Movement
|
||||
{
|
||||
return time_passed > 0;
|
||||
}
|
||||
|
||||
|
||||
public void Interrupt() { splineflags.SetUnsetFlag(SplineFlag.Done); }
|
||||
public void UpdateState(int difftime)
|
||||
{
|
||||
@@ -342,7 +344,8 @@ namespace Game.Movement
|
||||
public bool IsCyclic() { return splineflags.HasFlag(SplineFlag.Cyclic); }
|
||||
public bool IsFalling() { return splineflags.HasFlag(SplineFlag.Falling); }
|
||||
public bool Initialized() { return !spline.Empty(); }
|
||||
public Vector3 FinalDestination() { return Initialized() ? spline.GetPoint(spline.Last()) : new Vector3(); }
|
||||
public Vector3 FinalDestination() { return Initialized() ? spline.GetPoint(spline.Last()) : Vector3.Zero; }
|
||||
public Vector3 CurrentDestination() { return Initialized() ? spline.GetPoint(point_Idx + 1) : Vector3.Zero; }
|
||||
|
||||
#region Fields
|
||||
public MoveSplineInitArgs InitArgs;
|
||||
@@ -358,6 +361,7 @@ namespace Game.Movement
|
||||
public int effect_start_time;
|
||||
public int point_Idx;
|
||||
public int point_Idx_offset;
|
||||
public float velocity;
|
||||
public Optional<SpellEffectExtraData> spell_effect_extra;
|
||||
public Optional<AnimTierTransition> anim_tier;
|
||||
#endregion
|
||||
|
||||
Reference in New Issue
Block a user