diff --git a/OpenRA.Game/Primitives/RingBuffer.cs b/OpenRA.Game/Primitives/RingBuffer.cs new file mode 100644 index 0000000000..2a1d70e0a6 --- /dev/null +++ b/OpenRA.Game/Primitives/RingBuffer.cs @@ -0,0 +1,146 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; + +namespace OpenRA.Primitives +{ + /// Fixed size rorating buffer backed by an array. + public class RingBuffer : ICollection, IEnumerable + { + readonly IComparer comparer; + readonly T[] values; + int start; + + public int Capacity => values.Length; + public int Count { get; private set; } + public bool IsReadOnly => false; + + public RingBuffer(int capacity, IComparer comparer) + { + this.comparer = comparer; + values = new T[capacity]; + start = 0; + Count = 0; + } + + public RingBuffer(int capacity) + : this(capacity, Comparer.Default) { } + + public void Add(T value) + { + values[(start + Count) % values.Length] = value; + if (Count < values.Length) + Count++; + else + start = (start + 1) % values.Length; + } + + public void Clear() + { + Array.Clear(values, 0, values.Length); + start = 0; + Count = 0; + } + + public bool Contains(T value) + { + var capacity = values.Length; + var end = start + Count; + for (var i = start; i < end; ++i) + if (comparer.Compare(values[i % capacity], value) == 0) + return true; + + return false; + } + + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + if (arrayIndex < 0) + throw new ArgumentNullException(nameof(arrayIndex)); + + if (arrayIndex + Count >= array.Length) + throw new ArgumentException("Invalid array capacity"); + + var destinationIndex = arrayIndex; + var end = start + Count; + var capacity = values.Length; + for (var i = start; i < end; ++i) + array[destinationIndex++] = values[i % capacity]; + } + + public bool Remove(T value) + { + var capacity = values.Length; + var end = start + Count; + for (var i = start; i < end; ++i) + { + if (comparer.Compare(values[i % capacity], value) == 0) + { + end--; + for (var j = i; j < end; ++j) + values[j % capacity] = values[(j + 1) % capacity]; + + Count--; + return true; + } + } + + return false; + } + + public T this[int pos] + { + get => values[(start + pos) % values.Length]; + + set + { + if (pos >= Count) + throw new ArgumentException($"Index out of bounds: {pos}"); + + values[(start + pos) % values.Length] = value; + } + } + + public T First() + { + if (Count == 0) + throw new ArgumentException("Empty buffer"); + + return values[start]; + } + + public T Last() + { + if (Count == 0) + throw new ArgumentException("Empty buffer"); + + return values[(start + Count - 1) % values.Length]; + } + + public IEnumerator GetEnumerator() + { + var initState = start + Count; + for (var i = 0; i < Count; i++) + { + if (start + Count != initState) + throw new InvalidOperationException("Collection was modified; enumeration operation may not execute"); + yield return values[(start + i) % values.Length]; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } + } +} diff --git a/OpenRA.Game/WAngle.cs b/OpenRA.Game/WAngle.cs index b284852251..65630cf528 100644 --- a/OpenRA.Game/WAngle.cs +++ b/OpenRA.Game/WAngle.cs @@ -39,7 +39,7 @@ namespace OpenRA public static WAngle operator -(WAngle a) { return new WAngle(-a.Angle); } public static bool operator ==(WAngle me, WAngle other) { return me.Angle == other.Angle; } - public static bool operator !=(WAngle me, WAngle other) { return !(me == other); } + public static bool operator !=(WAngle me, WAngle other) { return me.Angle != other.Angle; } public override int GetHashCode() { return Angle.GetHashCode(); } diff --git a/OpenRA.Mods.Common/Activities/Air/Fly.cs b/OpenRA.Mods.Common/Activities/Air/Fly.cs index 87d444761d..01318490ba 100644 --- a/OpenRA.Mods.Common/Activities/Air/Fly.cs +++ b/OpenRA.Mods.Common/Activities/Air/Fly.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; @@ -29,7 +28,7 @@ namespace OpenRA.Mods.Common.Activities Target target; Target lastVisibleTarget; bool useLastVisibleTarget; - readonly List positionBuffer = new(); + readonly RingBuffer previousPositions = new(5); public Fly(Actor self, in Target t, WDist nearEnough, WPos? initialTargetPosition = null, Color? targetLineColor = null) : this(self, t, initialTargetPosition, targetLineColor) @@ -63,13 +62,10 @@ namespace OpenRA.Mods.Common.Activities public static void FlyTick(Actor self, Aircraft aircraft, WAngle desiredFacing, WDist desiredAltitude, in WVec moveOverride, bool idleTurn = false) { var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition); - var move = aircraft.Info.CanSlide ? aircraft.FlyStep(desiredFacing) : aircraft.FlyStep(aircraft.Facing); - if (moveOverride != WVec.Zero) - move = moveOverride; + var move = moveOverride != WVec.Zero ? moveOverride : (aircraft.Info.CanSlide ? aircraft.FlyStep(desiredFacing) : aircraft.FlyStep(aircraft.Facing)); var oldFacing = aircraft.Facing; - var turnSpeed = aircraft.GetTurnSpeed(idleTurn); - aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, turnSpeed); + aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, aircraft.GetTurnSpeed(idleTurn)); var roll = idleTurn ? aircraft.Info.IdleRoll ?? aircraft.Info.Roll : aircraft.Info.Roll; if (roll != WAngle.Zero) @@ -104,22 +100,16 @@ namespace OpenRA.Mods.Common.Activities // Should only be used for vertical-only movement, usually VTOL take-off or land. Terrain-induced altitude changes should always be handled by FlyTick. public static bool VerticalTakeOffOrLandTick(Actor self, Aircraft aircraft, WAngle desiredFacing, WDist desiredAltitude, bool idleTurn = false) { - var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition); - var move = WVec.Zero; - var turnSpeed = idleTurn ? aircraft.IdleTurnSpeed ?? aircraft.TurnSpeed : aircraft.TurnSpeed; aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, turnSpeed); - if (dat != desiredAltitude) - { - var maxDelta = aircraft.Info.AltitudeVelocity.Length; - var deltaZ = (desiredAltitude.Length - dat.Length).Clamp(-maxDelta, maxDelta); - move += new WVec(0, 0, deltaZ); - } - else + var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition); + if (dat == desiredAltitude) return false; - aircraft.SetPosition(self, aircraft.CenterPosition + move); + var maxDelta = aircraft.Info.AltitudeVelocity.Length; + var deltaZ = (desiredAltitude.Length - dat.Length).Clamp(-maxDelta, maxDelta); + aircraft.SetPosition(self, aircraft.CenterPosition + new WVec(0, 0, deltaZ)); return true; } @@ -203,8 +193,7 @@ namespace OpenRA.Mods.Common.Activities // HACK: Consider ourselves blocked if we have moved by less than 64 WDist in the last five ticks // Stop if we are blocked and close enough - if (positionBuffer.Count >= 5 && (positionBuffer.Last() - positionBuffer[0]).LengthSquared < 4096 && - delta.HorizontalLengthSquared <= nearEnough.LengthSquared) + if (previousPositions.Count == previousPositions.Capacity && (previousPositions.First() - previousPositions.Last()).LengthSquared < 4096 && delta.HorizontalLengthSquared <= nearEnough.LengthSquared) return true; // The next move would overshoot, so consider it close enough or set final position if we CanSlide @@ -253,11 +242,8 @@ namespace OpenRA.Mods.Common.Activities desiredFacing = aircraft.Facing; } - positionBuffer.Add(self.CenterPosition); - if (positionBuffer.Count > 5) - positionBuffer.RemoveAt(0); - - FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude); + previousPositions.Add(self.CenterPosition); + FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, WVec.Zero); return false; } diff --git a/OpenRA.Mods.Common/Util.cs b/OpenRA.Mods.Common/Util.cs index 93abb794c1..9d0370f98e 100644 --- a/OpenRA.Mods.Common/Util.cs +++ b/OpenRA.Mods.Common/Util.cs @@ -29,13 +29,17 @@ namespace OpenRA.Mods.Common public static int TickFacing(int facing, int desiredFacing, int rot) { var leftTurn = (facing - desiredFacing) & 0xFF; - var rightTurn = (desiredFacing - facing) & 0xFF; - if (Math.Min(leftTurn, rightTurn) < rot) + if (leftTurn < rot) return desiredFacing & 0xFF; - else if (rightTurn < leftTurn) + + var rightTurn = (desiredFacing - facing) & 0xFF; + if (rightTurn < rot) + return desiredFacing & 0xFF; + + if (rightTurn < leftTurn) return (facing + rot) & 0xFF; - else - return (facing - rot) & 0xFF; + + return (facing - rot) & 0xFF; } /// @@ -46,8 +50,11 @@ namespace OpenRA.Mods.Common public static WAngle TickFacing(WAngle facing, WAngle desiredFacing, WAngle step) { var leftTurn = (facing - desiredFacing).Angle; + if (leftTurn < step.Angle) + return desiredFacing; + var rightTurn = (desiredFacing - facing).Angle; - if (leftTurn < step.Angle || rightTurn < step.Angle) + if (rightTurn < step.Angle) return desiredFacing; return rightTurn < leftTurn ? facing + step : facing - step;