Core/Movement: Formation Rewrite

Port From (https://github.com/TrinityCore/TrinityCore/commit/924116f0461f5e1e03a026129b81dfe23faa20e4)
This commit is contained in:
hondacrx
2022-02-17 16:18:54 -05:00
parent d9b9739999
commit 266284247c
8 changed files with 168 additions and 123 deletions
+2 -2
View File
@@ -130,7 +130,7 @@ namespace Game.Entities
return m_formation.IsLeader(this); 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) if (m_formation == null)
return; return;
@@ -138,7 +138,7 @@ namespace Game.Entities
if (!m_formation.IsLeader(this)) if (!m_formation.IsLeader(this))
return; return;
m_formation.LeaderMoveTo(destination, id, moveType, orientation); m_formation.LeaderStartedMoving();
} }
public bool IsFormationLeaderMoveAllowed() public bool IsFormationLeaderMoveAllowed()
@@ -274,51 +274,23 @@ namespace Game.Entities
_formed = !dismiss; _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) if (_leader == null)
return; return;
Position pos = new(destination);
float pathangle = (float)Math.Atan2(_leader.GetPositionY() - pos.GetPositionY(), _leader.GetPositionX() - pos.GetPositionX());
foreach (var pair in _members) foreach (var pair in _members)
{ {
Creature member = pair.Key; Creature member = pair.Key;
if (member == _leader || !member.IsAlive() || member.IsEngaged() || !pair.Value.GroupAI.HasAnyFlag((uint)GroupAIFlags.IdleInFormation)) if (member == _leader || !member.IsAlive() || member.IsEngaged() || !pair.Value.GroupAI.HasAnyFlag((uint)GroupAIFlags.IdleInFormation))
continue; continue;
if (pair.Value.LeaderWaypointIDs[0] != 0) float angle = pair.Value.FollowAngle + MathF.PI; // for some reason, someone thought it was a great idea to invert relativ angles...
{
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 dist = pair.Value.FollowDist; float dist = pair.Value.FollowDist;
float dx = pos.GetPositionX() + MathF.Cos(angle + pathangle) * dist; var moveGen = member.GetMotionMaster().GetMovementGenerator(movement => { return movement.GetMovementGeneratorType() == MovementGeneratorType.Formation; }, MovementSlot.Default);
float dy = pos.GetPositionY() + MathF.Sin(angle + pathangle) * dist; if (moveGen == null)
float dz = pos.GetPositionZ(); member.GetMotionMaster().MoveFormation(_leader, dist, angle, pair.Value.LeaderWaypointIDs[0], pair.Value.LeaderWaypointIDs[1]);
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);
} }
} }
@@ -17,18 +17,32 @@
using Framework.Constants; using Framework.Constants;
using Game.Entities; using Game.Entities;
using System;
namespace Game.Movement namespace Game.Movement
{ {
public class FormationMovementGenerator : MovementGeneratorMedium<Creature> 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; _abstractFollower = new(leader);
_destination = destination; _range = range;
_moveType = moveType; _angle = angle;
_run = run; _point1 = point1;
_orientation = orientation; _point2 = point2;
Mode = MovementGeneratorMode.Default; Mode = MovementGeneratorMode.Default;
Priority = MovementGeneratorPriority.Normal; Priority = MovementGeneratorPriority.Normal;
@@ -48,33 +62,7 @@ namespace Game.Movement
return; return;
} }
owner.AddUnitState(UnitState.RoamingMove); _nextMoveTimer.Reset(0);
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();
} }
public override void DoReset(Creature owner) public override void DoReset(Creature owner)
@@ -86,57 +74,154 @@ namespace Game.Movement
public override bool DoUpdate(Creature owner, uint diff) public override bool DoUpdate(Creature owner, uint diff)
{ {
if (!owner) Unit target = _abstractFollower.GetTarget();
if (owner == null || target == null)
return false; return false;
// Owner cannot move. Reset all fields and wait for next action
if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting()) if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting())
{ {
AddFlag(MovementGeneratorFlags.Interrupted); AddFlag(MovementGeneratorFlags.Interrupted);
owner.ClearUnitState(UnitState.RoamingMove);
owner.StopMoving(); owner.StopMoving();
return true; 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); // Leader has stopped moving, so do we as well
init.MoveTo(_destination.GetPositionX(), _destination.GetPositionY(), _destination.GetPositionZ()); if (owner.HasUnitState(UnitState.RoamingMove) && _hasPredictedDestination && target.MoveSpline.Finalized() && target.MoveSpline.GetId() == _lastLeaderSplineID)
if (_orientation) {
init.SetFacing(_destination.GetOrientation()); 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: CreatureGroup formation = target.ToCreature().GetFormation();
init.SetAnimation(AnimType.ToGround); if (formation != null)
break; {
case WaypointMoveType.Takeoff: Creature leader = formation.GetLeader();
init.SetAnimation(AnimType.ToFly); if (leader != null)
break; {
case WaypointMoveType.Run: uint currentWaypoint = leader.GetCurrentWaypointInfo().nodeId;
init.SetWalk(false); if (currentWaypoint == _point1 || currentWaypoint == _point2)
break; _angle = MathF.PI * 2 - _angle;
case WaypointMoveType.Walk: }
init.SetWalk(true); }
break;
} }
if (_run) LaunchMovement(owner, target);
init.SetWalk(false); _lastLeaderSplineID = target.MoveSpline.GetId();
init.Launch(); return true;
} }
if (owner.MoveSpline.Finalized()) _nextMoveTimer.Update((int)diff);
if (_nextMoveTimer.Passed())
{ {
RemoveFlag(MovementGeneratorFlags.Transitory); _nextMoveTimer.Reset(FORMATION_MOVEMENT_INTERVAL);
AddFlag(MovementGeneratorFlags.InformEnabled);
return false; // 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; 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) public override void DoDeactivate(Creature owner)
{ {
AddFlag(MovementGeneratorFlags.Deactivated); AddFlag(MovementGeneratorFlags.Deactivated);
@@ -166,13 +251,7 @@ namespace Game.Movement
void MovementInform(Creature owner) void MovementInform(Creature owner)
{ {
if (owner.GetAI() != null) 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 // Call for creature group update
Creature creature = owner.ToCreature(); Creature creature = owner.ToCreature();
if (creature != null) if (creature != null)
creature.SignalFormationMovement(_destination, _movementId); creature.SignalFormationMovement();
} }
public override void DoReset(T owner) public override void DoReset(T owner)
@@ -124,7 +124,7 @@ namespace Game.Movement
// Call for creature group update // Call for creature group update
Creature creature = owner.ToCreature(); Creature creature = owner.ToCreature();
if (creature != null) if (creature != null)
creature.SignalFormationMovement(_destination, _movementId); creature.SignalFormationMovement();
} }
if (owner.MoveSpline.Finalized()) if (owner.MoveSpline.Finalized())
@@ -201,7 +201,7 @@ namespace Game.Movement
} }
// Call for creature group update // Call for creature group update
owner.SignalFormationMovement(position); owner.SignalFormationMovement();
} }
public override void UnitSpeedChanged() { AddFlag(MovementGeneratorFlags.SpeedUpdatePending); } 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})"); 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]; 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); 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 creature is on transport, we assume waypoints set in DB are already transport offsets
if (transportPath) if (transportPath)
{
init.DisableTransportPathTransformations(); 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 //! Do not use formationDest here, MoveTo requires transport offsets due to DisableTransportPathTransformations() call
//! but formationDest contains global coordinates //! but formationDest contains global coordinates
@@ -389,7 +379,7 @@ namespace Game.Movement
init.Launch(); init.Launch();
// inform formation // inform formation
owner.SignalFormationMovement(formationDest, waypoint.id, waypoint.moveType, (waypoint.orientation != 0 && waypoint.delay != 0)); owner.SignalFormationMovement();
} }
bool ComputeNextNode() bool ComputeNextNode()
+3 -3
View File
@@ -994,10 +994,10 @@ namespace Game.Movement
Add(new RotateMovementGenerator(id, time, direction)); 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) if (_owner.GetTypeId() == TypeId.Unit && leader != null)
Add(new FormationMovementGenerator(id, destination, moveType, forceRun, forceOrientation)); 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) public void LaunchMoveSpline(MoveSplineInit init, uint id = 0, MovementGeneratorPriority priority = MovementGeneratorPriority.Normal, MovementGeneratorType type = MovementGeneratorType.Effect)
+5 -1
View File
@@ -54,6 +54,8 @@ namespace Game.Movement
anim_tier = args.animTier; anim_tier = args.animTier;
splineIsFacingOnly = args.path.Count == 2 && args.facing.type != MonsterMoveType.Normal && ((args.path[1] - args.path[0]).Length() < 0.1f); 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 // Check if its a stop spline
if (args.flags.HasFlag(SplineFlag.Done)) if (args.flags.HasFlag(SplineFlag.Done))
{ {
@@ -342,7 +344,8 @@ namespace Game.Movement
public bool IsCyclic() { return splineflags.HasFlag(SplineFlag.Cyclic); } public bool IsCyclic() { return splineflags.HasFlag(SplineFlag.Cyclic); }
public bool IsFalling() { return splineflags.HasFlag(SplineFlag.Falling); } public bool IsFalling() { return splineflags.HasFlag(SplineFlag.Falling); }
public bool Initialized() { return !spline.Empty(); } 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 #region Fields
public MoveSplineInitArgs InitArgs; public MoveSplineInitArgs InitArgs;
@@ -358,6 +361,7 @@ namespace Game.Movement
public int effect_start_time; public int effect_start_time;
public int point_Idx; public int point_Idx;
public int point_Idx_offset; public int point_Idx_offset;
public float velocity;
public Optional<SpellEffectExtraData> spell_effect_extra; public Optional<SpellEffectExtraData> spell_effect_extra;
public Optional<AnimTierTransition> anim_tier; public Optional<AnimTierTransition> anim_tier;
#endregion #endregion