diff --git a/Content.Client/GameObjects/Components/StackVisualizer.cs b/Content.Client/GameObjects/Components/StackVisualizer.cs
index bc666fe1ef..b3d3b488cc 100644
--- a/Content.Client/GameObjects/Components/StackVisualizer.cs
+++ b/Content.Client/GameObjects/Components/StackVisualizer.cs
@@ -8,7 +8,6 @@ using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
-using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components
{
@@ -60,8 +59,7 @@ namespace Content.Client.GameObjects.Components
/// Sprite layers used in stack visualizer. Sprites first in layer correspond to lower stack states
/// e.g. _spriteLayers[0] is lower stack level than _spriteLayers[1].
///
- [DataField("stackLayers")]
- private readonly List _spriteLayers = new();
+ [DataField("stackLayers")] private readonly List _spriteLayers = new();
///
/// Determines if the visualizer uses composite or non-composite layers for icons. Defaults to false.
@@ -76,10 +74,9 @@ namespace Content.Client.GameObjects.Components
///
///
///
- [DataField("composite")]
- private bool _isComposite;
- [DataField("sprite")]
- private ResourcePath? _spritePath;
+ [DataField("composite")] private bool _isComposite;
+
+ [DataField("sprite")] private ResourcePath? _spritePath;
public override void InitializeEntity(IEntity entity)
{
@@ -126,7 +123,7 @@ namespace Content.Client.GameObjects.Components
maxCount = _spriteLayers.Count;
}
- var activeLayer = ContentHelpers.RoundToNearestLevels(actual, maxCount, _spriteLayers.Count - 1);
+ var activeLayer = ContentHelpers.RoundToEqualLevels(actual, maxCount, _spriteLayers.Count);
spriteComponent.LayerSetState(IconLayer, _spriteLayers[activeLayer]);
}
diff --git a/Content.Shared/Utility/ContentHelpers.cs b/Content.Shared/Utility/ContentHelpers.cs
index 31b7751c3a..dad2e39768 100644
--- a/Content.Shared/Utility/ContentHelpers.cs
+++ b/Content.Shared/Utility/ContentHelpers.cs
@@ -78,7 +78,7 @@ namespace Content.Shared.Utility
/// The maximum value of the scale.
/// Number of segments the scale is subdivided into.
/// The segment lies on.
- ///
+ /// If level is 1 or less
public static int RoundToNearestLevels(double actual, double max, int levels)
{
if (levels <= 1)
@@ -98,5 +98,42 @@ namespace Content.Shared.Utility
return (int) Math.Round(actual / max * levels, MidpointRounding.AwayFromZero);
}
+
+ ///
+ /// Basically helper for when you need to choose 0..N-1 element based on what
+ /// percentage does actual/max takes.
+ /// Example:
+ /// We have a stack of 30 elements.
+ /// When is:
+ /// - 0..9 we return 0.
+ /// - 10..19 we return 1.
+ /// - 20..30 we return 2.
+ ///
+ /// Useful when selecting N sprites for display in stacks, etc.
+ ///
+ /// How many out of max elements are there
+ ///
+ ///
+ /// The
+ /// if level is one or less
+ public static int RoundToEqualLevels(double actual, double max, int levels)
+ {
+ if (levels <= 1)
+ {
+ throw new ArgumentException("Levels must be greater than 1.", nameof(levels));
+ }
+
+ if (actual >= max)
+ {
+ return levels - 1;
+ }
+
+ if (actual <= 0)
+ {
+ return 0;
+ }
+
+ return (int) Math.Round(actual / max * levels, MidpointRounding.ToZero);
+ }
}
}
diff --git a/Content.Tests/Shared/Utility/ContentHelpers_Test.cs b/Content.Tests/Shared/Utility/ContentHelpers_Test.cs
index 186fde1db4..206e18eca6 100644
--- a/Content.Tests/Shared/Utility/ContentHelpers_Test.cs
+++ b/Content.Tests/Shared/Utility/ContentHelpers_Test.cs
@@ -55,7 +55,7 @@ namespace Content.Tests.Shared.Utility
(3, 5, 2, 1),
(4, 5, 2, 2),
(5, 5, 2, 2),
-
+
// Testing even counts
(0, 6, 5, 0),
(1, 6, 5, 1),
@@ -64,7 +64,7 @@ namespace Content.Tests.Shared.Utility
(4, 6, 5, 3),
(5, 6, 5, 4),
(6, 6, 5, 5),
-
+
// Testing transparency disable use case
(0, 6, 6, 0),
(1, 6, 6, 1),
@@ -97,5 +97,54 @@ namespace Content.Tests.Shared.Utility
(double val, double max, int size, int expected) = data;
Assert.That(ContentHelpers.RoundToNearestLevels(val, max, size), Is.EqualTo(expected));
}
+
+ [Parallelizable]
+ [Test]
+ // Testing odd max on even levels
+ [TestCase(0, 5, 2, ExpectedResult = 0)]
+ [TestCase(1, 5, 2, ExpectedResult = 0)]
+ [TestCase(2, 5, 2, ExpectedResult = 0)]
+ [TestCase(3, 5, 2, ExpectedResult = 1)]
+ [TestCase(4, 5, 2, ExpectedResult = 1)]
+ [TestCase(5, 5, 2, ExpectedResult = 1)]
+ // Testing even max on odd levels
+ [TestCase(0, 6, 3, ExpectedResult = 0)]
+ [TestCase(1, 6, 3, ExpectedResult = 0)]
+ [TestCase(2, 6, 3, ExpectedResult = 1)]
+ [TestCase(3, 6, 3, ExpectedResult = 1)]
+ [TestCase(4, 6, 3, ExpectedResult = 2)]
+ [TestCase(5, 6, 3, ExpectedResult = 2)]
+ [TestCase(6, 6, 3, ExpectedResult = 2)]
+ // Testing even max on even levels
+ [TestCase(0, 4, 2, ExpectedResult = 0)]
+ [TestCase(1, 4, 2, ExpectedResult = 0)]
+ [TestCase(2, 4, 2, ExpectedResult = 1)]
+ [TestCase(3, 4, 2, ExpectedResult = 1)]
+ [TestCase(4, 4, 2, ExpectedResult = 1)]
+ // Testing odd max on odd levels
+ [TestCase(0, 5, 3, ExpectedResult = 0)]
+ [TestCase(1, 5, 3, ExpectedResult = 0)]
+ [TestCase(2, 5, 3, ExpectedResult = 1)]
+ [TestCase(3, 5, 3, ExpectedResult = 1)]
+ [TestCase(4, 5, 3, ExpectedResult = 2)]
+ // Larger odd max on odd levels
+ [TestCase(0, 7, 3, ExpectedResult = 0)]
+ [TestCase(1, 7, 3, ExpectedResult = 0)]
+ [TestCase(2, 7, 3, ExpectedResult = 0)]
+ [TestCase(3, 7, 3, ExpectedResult = 1)]
+ [TestCase(4, 7, 3, ExpectedResult = 1)]
+ [TestCase(5, 7, 3, ExpectedResult = 2)]
+ [TestCase(6, 7, 3, ExpectedResult = 2)]
+ [TestCase(7, 7, 3, ExpectedResult = 2)]
+ // Testing edge cases
+ [TestCase(0.1, 6, 5, ExpectedResult = 0)]
+ [TestCase(-32, 6, 5, ExpectedResult = 0)]
+ [TestCase(2.4, 6, 5, ExpectedResult = 1)]
+ [TestCase(2.5, 6, 5, ExpectedResult = 2)]
+ [TestCase(320, 6, 5, ExpectedResult = 4)]
+ public int TestEqual(double val, double max, int size)
+ {
+ return ContentHelpers.RoundToEqualLevels(val, max, size);
+ }
}
}