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
@@ -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()