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;