diff --git a/Source/Game/Entities/Unit/Unit.Fields.cs b/Source/Game/Entities/Unit/Unit.Fields.cs index 8718b99c3..1182ea1b6 100644 --- a/Source/Game/Entities/Unit/Unit.Fields.cs +++ b/Source/Game/Entities/Unit/Unit.Fields.cs @@ -41,7 +41,7 @@ namespace Game.Entities public MoveSpline MoveSpline { get; set; } MotionMaster i_motionMaster; public uint m_movementCounter; //< Incrementing counter used in movement packets - TimeTrackerSmall movesplineTimer; + TimeTrackerSmall splineSyncTimer; MovementForces _movementForces; PositionUpdateInfo _positionUpdateInfo; protected Unit m_unitMovedByMe; // only ever set for players, and only for direct client control diff --git a/Source/Game/Entities/Unit/Unit.Movement.cs b/Source/Game/Entities/Unit/Unit.Movement.cs index dc5fbf37b..b38ca790e 100644 --- a/Source/Game/Entities/Unit/Unit.Movement.cs +++ b/Source/Game/Entities/Unit/Unit.Movement.cs @@ -1700,20 +1700,30 @@ namespace Game.Entities void UpdateSplineMovement(uint diff) { - int positionUpdateDelay = 400; - if (MoveSpline.Finalized()) return; MoveSpline.UpdateState((int)diff); bool arrived = MoveSpline.Finalized(); + if (MoveSpline.IsCyclic()) + { + splineSyncTimer.Update((int)diff); + if (splineSyncTimer.Passed()) + { + splineSyncTimer.Reset(5000); // Retail value, do not change + + FlightSplineSync flightSplineSync = new(); + flightSplineSync.Guid = GetGUID(); + flightSplineSync.SplineDist = MoveSpline.TimePassed() / MoveSpline.Duration(); + SendMessageToSet(flightSplineSync, true); + } + } + if (arrived) DisableSpline(); - movesplineTimer.Update((int)diff); - if (movesplineTimer.Passed() || arrived) - UpdateSplinePosition(); + UpdateSplinePosition(); } void UpdateSplinePosition() { diff --git a/Source/Game/Entities/Unit/Unit.cs b/Source/Game/Entities/Unit/Unit.cs index 61eb2fc24..25a868265 100644 --- a/Source/Game/Entities/Unit/Unit.cs +++ b/Source/Game/Entities/Unit/Unit.cs @@ -87,7 +87,7 @@ namespace Game.Entities m_serverSideVisibility.SetValue(ServerSideVisibilityType.Ghost, GhostVisibilityType.Alive); - movesplineTimer = new TimeTrackerSmall(); + splineSyncTimer = new TimeTrackerSmall(); m_unitData = new UnitData(); } diff --git a/Source/Game/Movement/MotionMaster.cs b/Source/Game/Movement/MotionMaster.cs index 87db7d39c..29eae4558 100644 --- a/Source/Game/Movement/MotionMaster.cs +++ b/Source/Game/Movement/MotionMaster.cs @@ -797,8 +797,12 @@ namespace Game.Movement float angle = pos.GetAbsoluteAngle(_owner.GetPositionX(), _owner.GetPositionY()); MoveSplineInit init = new(_owner); - init.args.path = new Vector3[stepCount]; - for (byte i = 0; i < stepCount; angle += step, ++i) + + init.args.path = new Vector3[stepCount + 1]; + + // add the owner's current position as starting point as it gets removed after entering the cycle + init.args.path[0] = new Vector3(_owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ()); + for (byte i = 1; i < stepCount; angle += step, ++i) { Vector3 point = new(); point.X = (float)(x + radius * Math.Cos(angle)); diff --git a/Source/Game/Movement/MoveSpline.cs b/Source/Game/Movement/MoveSpline.cs index b8ae18413..a8e3496b6 100644 --- a/Source/Game/Movement/MoveSpline.cs +++ b/Source/Game/Movement/MoveSpline.cs @@ -88,7 +88,10 @@ namespace Game.Movement Spline.EvaluationMode[] modes = new Spline.EvaluationMode[2] { Spline.EvaluationMode.Linear, Spline.EvaluationMode.Catmullrom }; if (args.flags.HasFlag(SplineFlag.Cyclic)) { - spline.InitCyclicSpline(args.path, args.path.Length, modes[Convert.ToInt32(args.flags.IsSmooth())], 0, args.initialOrientation); + int cyclic_point = 0; + if (splineflags.HasFlag(SplineFlag.EnterCycle)) + cyclic_point = 1; // shouldn't be modified, came from client + spline.InitCyclicSpline(args.path, args.path.Length, modes[Convert.ToInt32(args.flags.IsSmooth())], cyclic_point, args.initialOrientation); } else { @@ -288,6 +291,40 @@ namespace Game.Movement point_Idx = spline.First(); time_passed %= Duration(); result = UpdateResult.NextCycle; + // Remove first point from the path after one full cycle. + // That point was the position of the unit prior to entering the cycle and it shouldn't be repeated with continuous cycles. + if (splineflags.HasFlag(SplineFlag.EnterCycle)) + { + splineflags.SetUnsetFlag(SplineFlag.EnterCycle, false); + + MoveSplineInitArgs args = new(spline.GetPointCount()); + args.path = spline.GetPoints().AsSpan().Slice(spline.First() + 1, spline.Last()).ToArray(); + args.facing = facing; + args.flags = splineflags; + args.path_Idx_offset = point_Idx_offset; + // MoveSplineFlag::Parabolic | MoveSplineFlag::Animation not supported currently + //args.parabolic_amplitude = ?; + //args.time_perc = ?; + args.splineId = m_Id; + args.initialOrientation = initialOrientation; + args.velocity = 1.0f; // Calculated below + args.HasVelocity = true; + args.TransformForTransport = onTransport; + if (args.Validate(null)) + { + // New cycle should preserve previous cycle's duration for some weird reason, even though + // the path is really different now. Blizzard is weird. Or this was just a simple oversight. + // Since our splines precalculate length with velocity in mind, if we want to find the desired + // velocity, we have to make a fake spline, calculate its duration and then compare it to the + // desired duration, thus finding out how much the velocity has to be increased for them to match. + MoveSpline tempSpline = new(); + tempSpline.Initialize(args); + args.velocity = (float)tempSpline.Duration() / Duration(); + + if (args.Validate(null)) + InitSpline(args); + } + } } else { diff --git a/Source/Game/Movement/MoveSplineInit.cs b/Source/Game/Movement/MoveSplineInit.cs index 60e789588..2e929392b 100644 --- a/Source/Game/Movement/MoveSplineInit.cs +++ b/Source/Game/Movement/MoveSplineInit.cs @@ -100,7 +100,8 @@ namespace Game.Movement // correct first vertex args.path[0] = new Vector3(real_position.X, real_position.Y, real_position.Z); args.initialOrientation = real_position.W; - move_spline.onTransport = !unit.GetTransGUID().IsEmpty(); + args.flags.SetUnsetFlag(SplineFlag.EnterCycle, args.flags.HasFlag(SplineFlag.Cyclic)); + move_spline.onTransport = transport; MovementFlag moveFlags = unit.m_movementInfo.GetMovementFlags(); if (!args.flags.HasFlag(SplineFlag.Backward)) diff --git a/Source/Game/Networking/Packets/MovementPackets.cs b/Source/Game/Networking/Packets/MovementPackets.cs index 416bda84f..e6331af2d 100644 --- a/Source/Game/Networking/Packets/MovementPackets.cs +++ b/Source/Game/Networking/Packets/MovementPackets.cs @@ -454,6 +454,20 @@ namespace Game.Networking.Packets public Vector3 Pos; } + class FlightSplineSync : ServerPacket + { + public FlightSplineSync() : base(ServerOpcodes.FlightSplineSync, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WriteFloat(SplineDist); + } + + public ObjectGuid Guid; + public float SplineDist; + } + public class MoveSplineSetSpeed : ServerPacket { public MoveSplineSetSpeed(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { }