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);
}
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()
+3 -3
View File
@@ -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)
+7 -3
View File
@@ -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