diff --git a/Content.Client/Chemistry/Components/SolutionItemStatusComponent.cs b/Content.Client/Chemistry/Components/SolutionItemStatusComponent.cs
new file mode 100644
index 0000000000..58c5a05894
--- /dev/null
+++ b/Content.Client/Chemistry/Components/SolutionItemStatusComponent.cs
@@ -0,0 +1,22 @@
+using Content.Client.Chemistry.EntitySystems;
+using Content.Client.Chemistry.UI;
+
+namespace Content.Client.Chemistry.Components;
+
+///
+/// Exposes a solution container's contents via a basic item status control.
+///
+///
+/// Shows the solution volume, max volume, and transfer amount.
+///
+///
+///
+[RegisterComponent]
+public sealed partial class SolutionItemStatusComponent : Component
+{
+ ///
+ /// The ID of the solution that will be shown on the item status control.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public string Solution = "default";
+}
diff --git a/Content.Client/Chemistry/EntitySystems/SolutionItemStatusSystem.cs b/Content.Client/Chemistry/EntitySystems/SolutionItemStatusSystem.cs
new file mode 100644
index 0000000000..76aab516a7
--- /dev/null
+++ b/Content.Client/Chemistry/EntitySystems/SolutionItemStatusSystem.cs
@@ -0,0 +1,22 @@
+using Content.Client.Chemistry.Components;
+using Content.Client.Chemistry.UI;
+using Content.Client.Items;
+using Content.Shared.Chemistry.EntitySystems;
+
+namespace Content.Client.Chemistry.EntitySystems;
+
+///
+/// Wires up item status logic for .
+///
+///
+public sealed class SolutionItemStatusSystem : EntitySystem
+{
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ Subs.ItemStatus(
+ entity => new SolutionStatusControl(entity, EntityManager, _solutionContainerSystem));
+ }
+}
diff --git a/Content.Client/Chemistry/UI/SolutionStatusControl.cs b/Content.Client/Chemistry/UI/SolutionStatusControl.cs
new file mode 100644
index 0000000000..1a33ffb0e1
--- /dev/null
+++ b/Content.Client/Chemistry/UI/SolutionStatusControl.cs
@@ -0,0 +1,59 @@
+using Content.Client.Chemistry.Components;
+using Content.Client.Chemistry.EntitySystems;
+using Content.Client.Items.UI;
+using Content.Client.Message;
+using Content.Client.Stylesheets;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.FixedPoint;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.Chemistry.UI;
+
+///
+/// Displays basic solution information for .
+///
+///
+public sealed class SolutionStatusControl : PollingItemStatusControl
+{
+ private readonly Entity _parent;
+ private readonly IEntityManager _entityManager;
+ private readonly SharedSolutionContainerSystem _solutionContainers;
+ private readonly RichTextLabel _label;
+
+ public SolutionStatusControl(
+ Entity parent,
+ IEntityManager entityManager,
+ SharedSolutionContainerSystem solutionContainers)
+ {
+ _parent = parent;
+ _entityManager = entityManager;
+ _solutionContainers = solutionContainers;
+ _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
+ AddChild(_label);
+ }
+
+ protected override Data PollData()
+ {
+ if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.Solution, out _, out var solution))
+ return default;
+
+ FixedPoint2? transferAmount = null;
+ if (_entityManager.TryGetComponent(_parent.Owner, out SolutionTransferComponent? transfer))
+ transferAmount = transfer.TransferAmount;
+
+ return new Data(solution.Volume, solution.MaxVolume, transferAmount);
+ }
+
+ protected override void Update(in Data data)
+ {
+ var markup = Loc.GetString("solution-status-volume",
+ ("currentVolume", data.Volume),
+ ("maxVolume", data.MaxVolume));
+ if (data.TransferVolume is { } transferVolume)
+ markup += "\n" + Loc.GetString("solution-status-transfer", ("volume", transferVolume));
+ _label.SetMarkup(markup);
+ }
+
+ public readonly record struct Data(FixedPoint2 Volume, FixedPoint2 MaxVolume, FixedPoint2? TransferVolume);
+}
diff --git a/Content.Client/Items/UI/PollingItemStatusControl.cs b/Content.Client/Items/UI/PollingItemStatusControl.cs
new file mode 100644
index 0000000000..39cffb06f6
--- /dev/null
+++ b/Content.Client/Items/UI/PollingItemStatusControl.cs
@@ -0,0 +1,28 @@
+using Robust.Client.UserInterface;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Items.UI;
+
+///
+/// A base for item status controls that poll data every frame. Avoids UI updates if data didn't change.
+///
+/// The full status control data that is polled every frame.
+public abstract class PollingItemStatusControl : Control where TData : struct, IEquatable
+{
+ private TData _lastData;
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ var newData = PollData();
+ if (newData.Equals(_lastData))
+ return;
+
+ _lastData = newData;
+ Update(newData);
+ }
+
+ protected abstract TData PollData();
+ protected abstract void Update(in TData data);
+}
diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs
index a589abb83a..5fc17447c3 100644
--- a/Content.Client/Stylesheets/StyleNano.cs
+++ b/Content.Client/Stylesheets/StyleNano.cs
@@ -1234,6 +1234,11 @@ namespace Content.Client.Stylesheets
new StyleProperty("font", notoSans10),
}),
+ Element()
+ .Class(StyleClassItemStatus)
+ .Prop(nameof(RichTextLabel.LineHeightScale), 0.7f)
+ .Prop(nameof(Control.Margin), new Thickness(0, 0, 0, -6)),
+
// Slider
new StyleRule(SelectorElement.Type(typeof(Slider)), new []
{
diff --git a/Content.Client/Tools/ToolSystem.cs b/Content.Client/Tools/ToolSystem.cs
index 3eb2cc45cb..2207242918 100644
--- a/Content.Client/Tools/ToolSystem.cs
+++ b/Content.Client/Tools/ToolSystem.cs
@@ -13,7 +13,7 @@ namespace Content.Client.Tools
{
base.Initialize();
- Subs.ItemStatus(ent => new WelderStatusControl(ent));
+ Subs.ItemStatus(ent => new WelderStatusControl(ent, EntityManager, this));
Subs.ItemStatus(ent => new MultipleToolStatusControl(ent));
}
diff --git a/Content.Client/Tools/UI/WelderStatusControl.cs b/Content.Client/Tools/UI/WelderStatusControl.cs
index dae742efc3..3d44d6fa84 100644
--- a/Content.Client/Tools/UI/WelderStatusControl.cs
+++ b/Content.Client/Tools/UI/WelderStatusControl.cs
@@ -1,55 +1,45 @@
+using Content.Client.Items.UI;
using Content.Client.Message;
using Content.Client.Stylesheets;
+using Content.Shared.FixedPoint;
using Content.Shared.Tools.Components;
-using Robust.Client.UserInterface;
+using Content.Shared.Tools.Systems;
using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Timing;
namespace Content.Client.Tools.UI;
-public sealed class WelderStatusControl : Control
+public sealed class WelderStatusControl : PollingItemStatusControl
{
- [Dependency] private readonly IGameTiming _gameTiming = default!;
-
- private readonly ToolSystem _tool;
-
private readonly Entity _parent;
+ private readonly IEntityManager _entityManager;
+ private readonly SharedToolSystem _toolSystem;
private readonly RichTextLabel _label;
- public WelderStatusControl(Entity parent)
+ public WelderStatusControl(Entity parent, IEntityManager entityManager, SharedToolSystem toolSystem)
{
- IoCManager.InjectDependencies(this);
-
_parent = parent;
- var entMan = IoCManager.Resolve();
- _tool = entMan.System();
-
+ _entityManager = entityManager;
+ _toolSystem = toolSystem;
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);
UpdateDraw();
}
- ///
- protected override void FrameUpdate(FrameEventArgs args)
+ protected override Data PollData()
{
- base.FrameUpdate(args);
-
- Update();
+ var (fuel, capacity) = _toolSystem.GetWelderFuelAndCapacity(_parent, _parent.Comp);
+ return new Data(fuel, capacity, _parent.Comp.Enabled);
}
- public void Update()
+ protected override void Update(in Data data)
{
- if (!_gameTiming.IsFirstTimePredicted)
- return;
-
- var (fuel, fuelCap) = _tool.GetWelderFuelAndCapacity(_parent, _parent);
- var lit = _parent.Comp.Enabled;
-
_label.SetMarkup(Loc.GetString("welder-component-on-examine-detailed-message",
- ("colorName", fuel < fuelCap / 4f ? "darkorange" : "orange"),
- ("fuelLeft", Math.Round(fuel.Float(), 1)),
- ("fuelCapacity", fuelCap),
- ("status", Loc.GetString(lit ? "welder-component-on-examine-welder-lit-message" : "welder-component-on-examine-welder-not-lit-message"))));
+ ("colorName", data.Fuel < data.FuelCapacity / 4f ? "darkorange" : "orange"),
+ ("fuelLeft", data.Fuel),
+ ("fuelCapacity", data.FuelCapacity),
+ ("status", Loc.GetString(data.Lit ? "welder-component-on-examine-welder-lit-message" : "welder-component-on-examine-welder-not-lit-message"))));
}
+
+ public record struct Data(FixedPoint2 Fuel, FixedPoint2 FuelCapacity, bool Lit);
}
diff --git a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs
index bb550b0433..e57c15462e 100644
--- a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs
+++ b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs
@@ -28,6 +28,15 @@ public sealed class HandsUIController : UIController, IOnStateEntered _handLookup = new();
private HandsComponent? _playerHandsComponent;
private HandButton? _activeHand = null;
+
+ // We only have two item status controls (left and right hand),
+ // but we may have more than two hands.
+ // We handle this by having the item status be the *last active* hand of that side.
+ // These variables store which that is.
+ // ("middle" hands are hardcoded as right, whatever)
+ private HandButton? _statusHandLeft;
+ private HandButton? _statusHandRight;
+
private int _backupSuffix = 0; //this is used when autogenerating container names if they don't have names
private HotbarGui? HandsGui => UIManager.GetActiveUIWidgetOrNull();
@@ -180,8 +189,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered();
_hands = UIManager.GetUIController();
diff --git a/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml b/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml
index 0e9f0c77f9..3afe11ba33 100644
--- a/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml
+++ b/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml
@@ -10,21 +10,14 @@
Orientation="Vertical"
HorizontalAlignment="Center">
-
-
-
-
-
-
+
+
+
+
+ ColumnLimit="6"/>
+
();
- hotbarController.Setup(HandContainer, StatusPanel, StoragePanel);
+ hotbarController.Setup(HandContainer, StoragePanel);
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin);
}
- public void UpdatePanelEntity(EntityUid? entity)
+ public void UpdatePanelEntityLeft(EntityUid? entity)
{
- StatusPanel.Update(entity);
- if (entity == null)
- {
- StatusPanel.Visible = false;
- return;
- }
+ StatusPanelLeft.Update(entity);
+ }
- StatusPanel.Visible = true;
+ public void UpdatePanelEntityRight(EntityUid? entity)
+ {
+ StatusPanelRight.Update(entity);
+ }
+
+ public void SetHighlightHand(HandLocation? hand)
+ {
+ StatusPanelLeft.UpdateHighlight(hand is HandLocation.Left);
+ StatusPanelRight.UpdateHighlight(hand is HandLocation.Middle or HandLocation.Right);
}
}
diff --git a/Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml b/Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml
index d469e6ced0..81142d64d2 100644
--- a/Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml
+++ b/Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml
@@ -3,22 +3,26 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
VerticalAlignment="Bottom"
- HorizontalAlignment="Center"
- MinSize="150 0">
-
-
-
-
-
-
-
+ HorizontalAlignment="Center">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml.cs b/Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml.cs
index 90ae571711..e1fe6ab246 100644
--- a/Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml.cs
@@ -1,3 +1,4 @@
+using System.Numerics;
using Content.Client.Items;
using Content.Client.Resources;
using Content.Shared.Hands.Components;
@@ -5,6 +6,7 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Inventory.VirtualItem;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
@@ -14,12 +16,15 @@ using static Content.Client.IoC.StaticIoC;
namespace Content.Client.UserInterface.Systems.Inventory.Controls;
[GenerateTypedNameReferences]
-public sealed partial class ItemStatusPanel : BoxContainer
+public sealed partial class ItemStatusPanel : Control
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables] private EntityUid? _entity;
+ // Tracked so we can re-run SetSide() if the theme changes.
+ private HandLocation _side;
+
public ItemStatusPanel()
{
RobustXamlLoader.Load(this);
@@ -30,41 +35,65 @@ public sealed partial class ItemStatusPanel : BoxContainer
public void SetSide(HandLocation location)
{
- string texture;
+ // AN IMPORTANT REMINDER ABOUT THIS CODE:
+ // In the UI, the RIGHT hand is on the LEFT on the screen.
+ // So that a character facing DOWN matches the hand positions.
+
+ Texture? texture;
+ Texture? textureHighlight;
StyleBox.Margin cutOut;
StyleBox.Margin flat;
- Label.AlignMode textAlign;
+ Thickness contentMargin;
switch (location)
{
- case HandLocation.Left:
- texture = "/Textures/Interface/Nano/item_status_right.svg.96dpi.png";
- cutOut = StyleBox.Margin.Left | StyleBox.Margin.Top;
- flat = StyleBox.Margin.Right | StyleBox.Margin.Bottom;
- textAlign = Label.AlignMode.Right;
+ case HandLocation.Right:
+ texture = Theme.ResolveTexture("item_status_right");
+ textureHighlight = Theme.ResolveTexture("item_status_right_highlight");
+ cutOut = StyleBox.Margin.Left;
+ flat = StyleBox.Margin.Right;
+ contentMargin = MarginFromThemeColor("_itemstatus_content_margin_right");
break;
case HandLocation.Middle:
- texture = "/Textures/Interface/Nano/item_status_middle.svg.96dpi.png";
- cutOut = StyleBox.Margin.Right | StyleBox.Margin.Top;
- flat = StyleBox.Margin.Left | StyleBox.Margin.Bottom;
- textAlign = Label.AlignMode.Left;
- break;
- case HandLocation.Right:
- texture = "/Textures/Interface/Nano/item_status_left.svg.96dpi.png";
- cutOut = StyleBox.Margin.Right | StyleBox.Margin.Top;
- flat = StyleBox.Margin.Left | StyleBox.Margin.Bottom;
- textAlign = Label.AlignMode.Left;
+ case HandLocation.Left:
+ texture = Theme.ResolveTexture("item_status_left");
+ textureHighlight = Theme.ResolveTexture("item_status_left_highlight");
+ cutOut = StyleBox.Margin.Right;
+ flat = StyleBox.Margin.Left;
+ contentMargin = MarginFromThemeColor("_itemstatus_content_margin_left");
break;
default:
throw new ArgumentOutOfRangeException(nameof(location), location, null);
}
- var panel = (StyleBoxTexture) Panel.PanelOverride!;
- panel.Texture = ResC.GetTexture(texture);
- panel.SetPatchMargin(flat, 2);
- panel.SetPatchMargin(cutOut, 13);
+ Contents.Margin = contentMargin;
- ItemNameLabel.Align = textAlign;
+ var panel = (StyleBoxTexture) Panel.PanelOverride!;
+ panel.Texture = texture;
+ panel.SetPatchMargin(flat, 4);
+ panel.SetPatchMargin(cutOut, 7);
+
+ var panelHighlight = (StyleBoxTexture) HighlightPanel.PanelOverride!;
+ panelHighlight.Texture = textureHighlight;
+ panelHighlight.SetPatchMargin(flat, 4);
+ panelHighlight.SetPatchMargin(cutOut, 7);
+
+ _side = location;
+ }
+
+ private Thickness MarginFromThemeColor(string itemName)
+ {
+ // This is the worst thing I've ever programmed
+ // (can you tell I'm a graphics programmer?)
+ // (the margin needs to change depending on the UI theme, so we use a fake color entry to store the value)
+
+ var color = Theme.ResolveColorOrSpecified(itemName);
+ return new Thickness(color.RByte, color.GByte, color.BByte, color.AByte);
+ }
+
+ protected override void OnThemeUpdated()
+ {
+ SetSide(_side);
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -79,7 +108,7 @@ public sealed partial class ItemStatusPanel : BoxContainer
{
ClearOldStatus();
_entity = null;
- Panel.Visible = false;
+ VisWrapper.Visible = false;
return;
}
@@ -91,7 +120,12 @@ public sealed partial class ItemStatusPanel : BoxContainer
UpdateItemName();
}
- Panel.Visible = true;
+ VisWrapper.Visible = true;
+ }
+
+ public void UpdateHighlight(bool highlight)
+ {
+ HighlightPanel.Visible = highlight;
}
private void UpdateItemName()
diff --git a/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs b/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs
new file mode 100644
index 0000000000..492fad3872
--- /dev/null
+++ b/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs
@@ -0,0 +1,178 @@
+using System.Numerics;
+using Content.Client.Resources;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Weapons.Ranged.ItemStatus;
+
+///
+/// Renders one or more rows of bullets for item status.
+///
+///
+/// This is a custom control to allow complex responsive layout logic.
+///
+public sealed class BulletRender : Control
+{
+ private static readonly Color ColorA = Color.FromHex("#b68f0e");
+ private static readonly Color ColorB = Color.FromHex("#d7df60");
+ private static readonly Color ColorGoneA = Color.FromHex("#000000");
+ private static readonly Color ColorGoneB = Color.FromHex("#222222");
+
+ ///
+ /// Try to ensure there's at least this many bullets on one row.
+ ///
+ ///
+ /// For example, if there are two rows and the second row has only two bullets,
+ /// we "steal" some bullets from the row below it to make it look nicer.
+ ///
+ public const int MinCountPerRow = 7;
+
+ public const int BulletHeight = 12;
+ public const int BulletSeparationNormal = 3;
+ public const int BulletSeparationTiny = 2;
+ public const int BulletWidthNormal = 5;
+ public const int BulletWidthTiny = 2;
+ public const int VerticalSeparation = 2;
+
+ private readonly Texture _bulletTiny;
+ private readonly Texture _bulletNormal;
+
+ private int _capacity;
+ private BulletType _type = BulletType.Normal;
+
+ public int Rows { get; set; } = 2;
+ public int Count { get; set; }
+
+ public int Capacity
+ {
+ get => _capacity;
+ set
+ {
+ _capacity = value;
+ InvalidateMeasure();
+ }
+ }
+
+ public BulletType Type
+ {
+ get => _type;
+ set
+ {
+ _type = value;
+ InvalidateMeasure();
+ }
+ }
+
+ public BulletRender()
+ {
+ var resC = IoCManager.Resolve();
+ _bulletTiny = resC.GetTexture("/Textures/Interface/ItemStatus/Bullets/tiny.png");
+ _bulletNormal = resC.GetTexture("/Textures/Interface/ItemStatus/Bullets/normal.png");
+ }
+
+ protected override Vector2 MeasureOverride(Vector2 availableSize)
+ {
+ var countPerRow = Math.Min(Capacity, CountPerRow(availableSize.X));
+
+ var rows = Math.Min((int) MathF.Ceiling(Capacity / (float) countPerRow), Rows);
+
+ var height = BulletHeight * rows + (BulletSeparationNormal * rows - 1);
+ var width = RowWidth(countPerRow);
+
+ return new Vector2(width, height);
+ }
+
+ protected override void Draw(DrawingHandleScreen handle)
+ {
+ // Scale rendering in this control by UIScale.
+ var currentTransform = handle.GetTransform();
+ handle.SetTransform(Matrix3.CreateScale(new Vector2(UIScale)) * currentTransform);
+
+ var countPerRow = CountPerRow(Size.X);
+
+ var (separation, _) = BulletParams();
+ var texture = Type == BulletType.Normal ? _bulletNormal : _bulletTiny;
+
+ var pos = new Vector2();
+
+ var altColor = false;
+
+ var spent = Capacity - Count;
+
+ var bulletsDone = 0;
+
+ // Draw by rows, bottom to top.
+ for (var row = 0; row < Rows; row++)
+ {
+ altColor = false;
+
+ var thisRowCount = Math.Min(countPerRow, Capacity - bulletsDone);
+ if (thisRowCount <= 0)
+ break;
+
+ // Handle MinCountPerRow
+ // We only do this if:
+ // 1. The next row would have less than MinCountPerRow bullets.
+ // 2. The next row is actually visible (we aren't the last row).
+ // 3. MinCountPerRow is actually smaller than the count per row (avoid degenerate cases).
+ var nextRowCount = Capacity - bulletsDone - thisRowCount;
+ if (nextRowCount < MinCountPerRow && row != Rows - 1 && MinCountPerRow < countPerRow)
+ thisRowCount -= MinCountPerRow - nextRowCount;
+
+ // Account for row width to right-align.
+ var rowWidth = RowWidth(thisRowCount);
+ pos.X += Size.X - rowWidth;
+
+ // Draw row left to right (so overlapping works)
+ for (var bullet = 0; bullet < thisRowCount; bullet++)
+ {
+ var absIdx = Capacity - bulletsDone - thisRowCount + bullet;
+ Color color;
+ if (absIdx >= spent)
+ color = altColor ? ColorA : ColorB;
+ else
+ color = altColor ? ColorGoneA : ColorGoneB;
+
+ var renderPos = pos;
+ renderPos.Y = Size.Y - renderPos.Y - BulletHeight;
+ handle.DrawTexture(texture, renderPos, color);
+ pos.X += separation;
+ altColor ^= true;
+ }
+
+ bulletsDone += thisRowCount;
+ pos.X = 0;
+ pos.Y += BulletHeight + VerticalSeparation;
+ }
+ }
+
+ private int CountPerRow(float width)
+ {
+ var (separation, bulletWidth) = BulletParams();
+ return (int) ((width - bulletWidth + separation) / separation);
+ }
+
+ private (int separation, int width) BulletParams()
+ {
+ return Type switch
+ {
+ BulletType.Normal => (BulletSeparationNormal, BulletWidthNormal),
+ BulletType.Tiny => (BulletSeparationTiny, BulletWidthTiny),
+ _ => throw new ArgumentOutOfRangeException()
+ };
+ }
+
+ private int RowWidth(int count)
+ {
+ var (separation, bulletWidth) = BulletParams();
+
+ return (count - 1) * separation + bulletWidth;
+ }
+
+ public enum BulletType
+ {
+ Normal,
+ Tiny
+ }
+}
diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.AmmoCounter.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.AmmoCounter.cs
index 32343af56f..cc7405b047 100644
--- a/Content.Client/Weapons/Ranged/Systems/GunSystem.AmmoCounter.cs
+++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.AmmoCounter.cs
@@ -4,11 +4,11 @@ using Content.Client.Items;
using Content.Client.Resources;
using Content.Client.Stylesheets;
using Content.Client.Weapons.Ranged.Components;
+using Content.Client.Weapons.Ranged.ItemStatus;
using Robust.Client.Animations;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Graphics;
namespace Content.Client.Weapons.Ranged.Systems;
@@ -91,116 +91,26 @@ public sealed partial class GunSystem
private sealed class DefaultStatusControl : Control
{
- private readonly BoxContainer _bulletsListTop;
- private readonly BoxContainer _bulletsListBottom;
+ private readonly BulletRender _bulletRender;
public DefaultStatusControl()
{
MinHeight = 15;
HorizontalExpand = true;
- VerticalAlignment = Control.VAlignment.Center;
- AddChild(new BoxContainer
+ VerticalAlignment = VAlignment.Center;
+ AddChild(_bulletRender = new BulletRender
{
- Orientation = BoxContainer.LayoutOrientation.Vertical,
- HorizontalExpand = true,
- VerticalAlignment = VAlignment.Center,
- SeparationOverride = 0,
- Children =
- {
- (_bulletsListTop = new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Horizontal,
- SeparationOverride = 0
- }),
- new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Horizontal,
- HorizontalExpand = true,
- Children =
- {
- new Control
- {
- HorizontalExpand = true,
- Children =
- {
- (_bulletsListBottom = new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Horizontal,
- VerticalAlignment = VAlignment.Center,
- SeparationOverride = 0
- }),
- }
- },
- }
- }
- }
+ HorizontalAlignment = HAlignment.Right,
+ VerticalAlignment = VAlignment.Bottom
});
}
public void Update(int count, int capacity)
{
- _bulletsListTop.RemoveAllChildren();
- _bulletsListBottom.RemoveAllChildren();
+ _bulletRender.Count = count;
+ _bulletRender.Capacity = capacity;
- string texturePath;
- if (capacity <= 20)
- {
- texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
- }
- else if (capacity <= 30)
- {
- texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png";
- }
- else
- {
- texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png";
- }
-
- var texture = StaticIoC.ResC.GetTexture(texturePath);
-
- const int tinyMaxRow = 60;
-
- if (capacity > tinyMaxRow)
- {
- FillBulletRow(_bulletsListBottom, Math.Min(tinyMaxRow, count), tinyMaxRow, texture);
- FillBulletRow(_bulletsListTop, Math.Max(0, count - tinyMaxRow), capacity - tinyMaxRow, texture);
- }
- else
- {
- FillBulletRow(_bulletsListBottom, count, capacity, texture);
- }
- }
-
- private static void FillBulletRow(Control container, int count, int capacity, Texture texture)
- {
- var colorA = Color.FromHex("#b68f0e");
- var colorB = Color.FromHex("#d7df60");
- var colorGoneA = Color.FromHex("#000000");
- var colorGoneB = Color.FromHex("#222222");
-
- var altColor = false;
-
- for (var i = count; i < capacity; i++)
- {
- container.AddChild(new TextureRect
- {
- Texture = texture,
- ModulateSelfOverride = altColor ? colorGoneA : colorGoneB
- });
-
- altColor ^= true;
- }
-
- for (var i = 0; i < count; i++)
- {
- container.AddChild(new TextureRect
- {
- Texture = texture,
- ModulateSelfOverride = altColor ? colorA : colorB
- });
-
- altColor ^= true;
- }
+ _bulletRender.Type = capacity > 50 ? BulletRender.BulletType.Tiny : BulletRender.BulletType.Normal;
}
}
@@ -291,7 +201,7 @@ public sealed partial class GunSystem
private sealed class ChamberMagazineStatusControl : Control
{
- private readonly BoxContainer _bulletsList;
+ private readonly BulletRender _bulletRender;
private readonly TextureRect _chamberedBullet;
private readonly Label _noMagazineLabel;
private readonly Label _ammoCount;
@@ -308,23 +218,16 @@ public sealed partial class GunSystem
HorizontalExpand = true,
Children =
{
- (_chamberedBullet = new TextureRect
- {
- Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png"),
- VerticalAlignment = VAlignment.Center,
- HorizontalAlignment = HAlignment.Right,
- }),
- new Control() { MinSize = new Vector2(5,0) },
new Control
{
HorizontalExpand = true,
+ Margin = new Thickness(0, 0, 5, 0),
Children =
{
- (_bulletsList = new BoxContainer
+ (_bulletRender = new BulletRender
{
- Orientation = BoxContainer.LayoutOrientation.Horizontal,
- VerticalAlignment = VAlignment.Center,
- SeparationOverride = 0
+ HorizontalAlignment = HAlignment.Right,
+ VerticalAlignment = VAlignment.Bottom
}),
(_noMagazineLabel = new Label
{
@@ -333,12 +236,25 @@ public sealed partial class GunSystem
})
}
},
- new Control() { MinSize = new Vector2(5,0) },
- (_ammoCount = new Label
+ new BoxContainer
{
- StyleClasses = {StyleNano.StyleClassItemStatus},
- HorizontalAlignment = HAlignment.Right,
- }),
+ Orientation = BoxContainer.LayoutOrientation.Vertical,
+ VerticalAlignment = VAlignment.Bottom,
+ Margin = new Thickness(0, 0, 0, 2),
+ Children =
+ {
+ (_ammoCount = new Label
+ {
+ StyleClasses = {StyleNano.StyleClassItemStatus},
+ HorizontalAlignment = HAlignment.Right,
+ }),
+ (_chamberedBullet = new TextureRect
+ {
+ Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered.png"),
+ HorizontalAlignment = HAlignment.Left,
+ }),
+ }
+ }
}
});
}
@@ -348,61 +264,24 @@ public sealed partial class GunSystem
_chamberedBullet.ModulateSelfOverride =
chambered ? Color.FromHex("#d7df60") : Color.Black;
- _bulletsList.RemoveAllChildren();
-
if (!magazine)
{
+ _bulletRender.Visible = false;
_noMagazineLabel.Visible = true;
_ammoCount.Visible = false;
return;
}
+ _bulletRender.Visible = true;
_noMagazineLabel.Visible = false;
_ammoCount.Visible = true;
- var texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
- var texture = StaticIoC.ResC.GetTexture(texturePath);
+ _bulletRender.Count = count;
+ _bulletRender.Capacity = capacity;
+
+ _bulletRender.Type = capacity > 50 ? BulletRender.BulletType.Tiny : BulletRender.BulletType.Normal;
_ammoCount.Text = $"x{count:00}";
- capacity = Math.Min(capacity, 20);
- FillBulletRow(_bulletsList, count, capacity, texture);
- }
-
- private static void FillBulletRow(Control container, int count, int capacity, Texture texture)
- {
- var colorA = Color.FromHex("#b68f0e");
- var colorB = Color.FromHex("#d7df60");
- var colorGoneA = Color.FromHex("#000000");
- var colorGoneB = Color.FromHex("#222222");
-
- var altColor = false;
-
- // Draw the empty ones
- for (var i = count; i < capacity; i++)
- {
- container.AddChild(new TextureRect
- {
- Texture = texture,
- ModulateSelfOverride = altColor ? colorGoneA : colorGoneB,
- Stretch = TextureRect.StretchMode.KeepCentered
- });
-
- altColor ^= true;
- }
-
- // Draw the full ones, but limit the count to the capacity
- count = Math.Min(count, capacity);
- for (var i = 0; i < count; i++)
- {
- container.AddChild(new TextureRect
- {
- Texture = texture,
- ModulateSelfOverride = altColor ? colorA : colorB,
- Stretch = TextureRect.StretchMode.KeepCentered
- });
-
- altColor ^= true;
- }
}
public void PlayAlarmAnimation(Animation animation)
diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj
index 8782454eac..70e404fdef 100644
--- a/Content.Server/Content.Server.csproj
+++ b/Content.Server/Content.Server.csproj
@@ -28,7 +28,6 @@
-
diff --git a/Content.Server/Entry/IgnoredComponents.cs b/Content.Server/Entry/IgnoredComponents.cs
index fe073da7a4..d9b81c8e5a 100644
--- a/Content.Server/Entry/IgnoredComponents.cs
+++ b/Content.Server/Entry/IgnoredComponents.cs
@@ -14,12 +14,13 @@ namespace Content.Server.Entry
"Icon",
"HandheldGPS",
"CableVisualizer",
+ "SolutionItemStatus",
"UIFragment",
"PdaBorderColor",
"InventorySlots",
"LightFade",
"HolidayRsiSwap",
- "OptionsVisualizer",
+ "OptionsVisualizer"
};
}
}
diff --git a/Content.Server/Tools/ToolSystem.cs b/Content.Server/Tools/ToolSystem.cs
index 7b2de57efc..7738a6398f 100644
--- a/Content.Server/Tools/ToolSystem.cs
+++ b/Content.Server/Tools/ToolSystem.cs
@@ -45,10 +45,10 @@ public sealed class ToolSystem : SharedToolSystem
if (welder.WelderTimer < welder.WelderUpdateTimer)
continue;
- if (!SolutionContainerSystem.ResolveSolution((uid, solutionContainer), welder.FuelSolutionName, ref welder.FuelSolution, out var solution))
+ if (!SolutionContainerSystem.TryGetSolution((uid, solutionContainer), welder.FuelSolutionName, out var solutionComp, out var solution))
continue;
- SolutionContainerSystem.RemoveReagent(welder.FuelSolution.Value, welder.FuelReagent, welder.FuelConsumption * welder.WelderTimer);
+ SolutionContainerSystem.RemoveReagent(solutionComp.Value, welder.FuelReagent, welder.FuelConsumption * welder.WelderTimer);
if (solution.GetTotalPrototypeQuantity(welder.FuelReagent) <= FixedPoint2.Zero)
{
diff --git a/Content.Shared/Tools/Components/WelderComponent.cs b/Content.Shared/Tools/Components/WelderComponent.cs
index f133be675d..3c78a03fde 100644
--- a/Content.Shared/Tools/Components/WelderComponent.cs
+++ b/Content.Shared/Tools/Components/WelderComponent.cs
@@ -23,12 +23,6 @@ public sealed partial class WelderComponent : Component
[DataField]
public string FuelSolutionName = "Welder";
- ///
- /// Solution on the entity that contains the fuel.
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- public Entity? FuelSolution;
-
///
/// Reagent that will be used as fuel for welding.
///
diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs b/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs
index 6bab296db5..e790b59cd1 100644
--- a/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs
+++ b/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs
@@ -24,10 +24,10 @@ public abstract partial class SharedToolSystem
public virtual void TurnOn(Entity entity, EntityUid? user)
{
- if (!SolutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution))
+ if (!SolutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.FuelSolutionName, out var solutionComp, out _))
return;
- SolutionContainerSystem.RemoveReagent(entity.Comp.FuelSolution.Value, entity.Comp.FuelReagent, entity.Comp.FuelLitCost);
+ SolutionContainerSystem.RemoveReagent(solutionComp.Value, entity.Comp.FuelReagent, entity.Comp.FuelLitCost);
AdminLogger.Add(LogType.InteractActivate, LogImpact.Low,
$"{ToPrettyString(user):user} toggled {ToPrettyString(entity.Owner):welder} on");
@@ -45,9 +45,17 @@ public abstract partial class SharedToolSystem
public (FixedPoint2 fuel, FixedPoint2 capacity) GetWelderFuelAndCapacity(EntityUid uid, WelderComponent? welder = null, SolutionContainerManagerComponent? solutionContainer = null)
{
- if (!Resolve(uid, ref welder, ref solutionContainer)
- || !SolutionContainerSystem.ResolveSolution((uid, solutionContainer), welder.FuelSolutionName, ref welder.FuelSolution, out var fuelSolution))
- return (FixedPoint2.Zero, FixedPoint2.Zero);
+ if (!Resolve(uid, ref welder, ref solutionContainer))
+ return default;
+
+ if (!SolutionContainer.TryGetSolution(
+ (uid, solutionContainer),
+ welder.FuelSolutionName,
+ out _,
+ out var fuelSolution))
+ {
+ return default;
+ }
return (fuelSolution.GetTotalPrototypeQuantity(welder.FuelReagent), fuelSolution.MaxVolume);
}
@@ -89,13 +97,13 @@ public abstract partial class SharedToolSystem
if (TryComp(target, out ReagentTankComponent? tank)
&& tank.TankType == ReagentTankType.Fuel
&& SolutionContainerSystem.TryGetDrainableSolution(target, out var targetSoln, out var targetSolution)
- && SolutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution, out var welderSolution))
+ && SolutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.FuelSolutionName, out var solutionComp, out var welderSolution))
{
var trans = FixedPoint2.Min(welderSolution.AvailableVolume, targetSolution.Volume);
if (trans > 0)
{
var drained = SolutionContainerSystem.Drain(target, targetSoln.Value, trans);
- SolutionContainerSystem.TryAddSolution(entity.Comp.FuelSolution.Value, drained);
+ SolutionContainerSystem.TryAddSolution(solutionComp.Value, drained);
_audioSystem.PlayPredicted(entity.Comp.WelderRefill, entity, user: args.User);
_popup.PopupClient(Loc.GetString("welder-component-after-interact-refueled-message"), entity, args.User);
}
@@ -153,7 +161,7 @@ public abstract partial class SharedToolSystem
private void OnActivateAttempt(Entity entity, ref ItemToggleActivateAttemptEvent args)
{
- if (!SolutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution, out var solution))
+ if (!SolutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.FuelSolutionName, out _, out var solution))
{
args.Cancelled = true;
args.Popup = Loc.GetString("welder-component-no-fuel-message");
diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.cs b/Content.Shared/Tools/Systems/SharedToolSystem.cs
index 362f4f7683..9edae9b78f 100644
--- a/Content.Shared/Tools/Systems/SharedToolSystem.cs
+++ b/Content.Shared/Tools/Systems/SharedToolSystem.cs
@@ -31,6 +31,7 @@ public abstract partial class SharedToolSystem : EntitySystem
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly TileSystem _tiles = default!;
[Dependency] private readonly TurfSystem _turfs = default!;
+ [Dependency] protected readonly SharedSolutionContainerSystem SolutionContainer = default!;
public override void Initialize()
{
diff --git a/Resources/Locale/en-US/chemistry/components/solution-status.ftl b/Resources/Locale/en-US/chemistry/components/solution-status.ftl
new file mode 100644
index 0000000000..0ec5f932e0
--- /dev/null
+++ b/Resources/Locale/en-US/chemistry/components/solution-status.ftl
@@ -0,0 +1,2 @@
+solution-status-volume = Volume: [color=white]{$currentVolume}/{$maxVolume}u[/color]
+solution-status-transfer = Transfer: [color=white]{$volume}u[/color]
diff --git a/Resources/Locale/en-US/tools/components/welder-component.ftl b/Resources/Locale/en-US/tools/components/welder-component.ftl
index 681975deb8..6307068521 100644
--- a/Resources/Locale/en-US/tools/components/welder-component.ftl
+++ b/Resources/Locale/en-US/tools/components/welder-component.ftl
@@ -4,7 +4,8 @@ welder-component-no-fuel-message = The welder has no fuel left!
welder-component-no-fuel-in-tank = The {$owner} is empty.
welder-component-on-examine-welder-lit-message = [color=orange]Lit[/color]
welder-component-on-examine-welder-not-lit-message = Not lit
-welder-component-on-examine-detailed-message = Fuel: [color={$colorName}]{$fuelLeft}/{$fuelCapacity}[/color]. {$status}
+welder-component-on-examine-detailed-message = Fuel: [color={$colorName}]{$fuelLeft}/{$fuelCapacity}[/color]
+ {$status}
welder-component-suicide-lit-others-message = {$victim} welds their every orifice closed! It looks like they are trying to commit suicide!
welder-component-suicide-lit-message = You weld your every orifice closed!
welder-component-suicide-unlit-others-message = {$victim} bashes themselves with the unlit welding torch!
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml
index 0e19c03dee..64b3568adf 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml
@@ -24,6 +24,8 @@
solution: spray
- type: SolutionTransfer
canChangeTransferAmount: true
+ - type: SolutionItemStatus
+ solution: spray
- type: UseDelay
- type: Spray
transferAmount: 10
diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml b/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml
index 01efcfa950..8034844a82 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml
@@ -32,6 +32,8 @@
solution: beaker
- type: SolutionTransfer
canChangeTransferAmount: true
+ - type: SolutionItemStatus
+ solution: beaker
- type: UserInterface
interfaces:
- key: enum.TransferAmountUiKey.Key
diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml
index 9ac699db5a..fdf58dc484 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml
@@ -41,6 +41,8 @@
solution: beaker
- type: SolutionTransfer
canChangeTransferAmount: true
+ - type: SolutionItemStatus
+ solution: beaker
- type: UserInterface
interfaces:
- key: enum.TransferAmountUiKey.Key
diff --git a/Resources/Prototypes/Entities/Objects/Tools/bucket.yml b/Resources/Prototypes/Entities/Objects/Tools/bucket.yml
index d0a3bb3702..77803a13ec 100644
--- a/Resources/Prototypes/Entities/Objects/Tools/bucket.yml
+++ b/Resources/Prototypes/Entities/Objects/Tools/bucket.yml
@@ -51,6 +51,8 @@
solution: bucket
- type: DrainableSolution
solution: bucket
+ - type: SolutionItemStatus
+ solution: bucket
- type: Appearance
- type: SolutionContainerVisuals
maxFillLevels: 3
diff --git a/Resources/Prototypes/themes.yml b/Resources/Prototypes/themes.yml
index edd2681a62..3952687255 100644
--- a/Resources/Prototypes/themes.yml
+++ b/Resources/Prototypes/themes.yml
@@ -12,6 +12,8 @@
concerningOrangeFore: "#A5762F"
dangerousRedFore: "#BB3232"
disabledFore: "#5A5A5A"
+ _itemstatus_content_margin_right: "#06060404"
+ _itemstatus_content_margin_left: "#04060604"
- type: uiTheme
id: SS14PlasmafireTheme
path: /Textures/Interface/Plasmafire/
@@ -26,6 +28,8 @@
concerningOrangeFore: "#FFF5EE"
dangerousRedFore: "#FFF5EE"
disabledFore: "#FFF5EE"
+ _itemstatus_content_margin_right: "#06060404"
+ _itemstatus_content_margin_left: "#04060604"
- type: uiTheme
id: SS14SlimecoreTheme
path: /Textures/Interface/Slimecore/
@@ -40,6 +44,8 @@
concerningOrangeFore: "#FFF5EE"
dangerousRedFore: "#FFF5EE"
disabledFore: "#FFF5EE"
+ _itemstatus_content_margin_right: "#06060404"
+ _itemstatus_content_margin_left: "#04060604"
- type: uiTheme
id: SS14ClockworkTheme
path: /Textures/Interface/Clockwork/
@@ -54,6 +60,8 @@
concerningOrangeFore: "#FFF5EE"
dangerousRedFore: "#FFF5EE"
disabledFore: "#FFF5EE"
+ _itemstatus_content_margin_right: "#06060404"
+ _itemstatus_content_margin_left: "#04060604"
- type: uiTheme
id: SS14RetroTheme
path: /Textures/Interface/Retro/
@@ -68,6 +76,8 @@
concerningOrangeFore: "#FFF5EE"
dangerousRedFore: "#FFF5EE"
disabledFore: "#FFF5EE"
+ _itemstatus_content_margin_right: "#06060404"
+ _itemstatus_content_margin_left: "#04060604"
- type: uiTheme
id: SS14MinimalistTheme
path: /Textures/Interface/Minimalist/
@@ -82,6 +92,8 @@
concerningOrangeFore: "#A5762F"
dangerousRedFore: "#BB3232"
disabledFore: "#5A5A5A"
+ _itemstatus_content_margin_right: "#06060604"
+ _itemstatus_content_margin_left: "#06060604"
- type: uiTheme
id: SS14AshenTheme
path: /Textures/Interface/Ashen/
@@ -96,3 +108,5 @@
concerningOrangeFore: "#FFF5EE"
dangerousRedFore: "#FFF5EE"
disabledFore: "#FFF5EE"
+ _itemstatus_content_margin_right: "#06060604"
+ _itemstatus_content_margin_left: "#06060604"
diff --git a/Resources/Textures/Interface/Ashen/item_status_left.png b/Resources/Textures/Interface/Ashen/item_status_left.png
new file mode 100644
index 0000000000..fb2bf2b9b4
Binary files /dev/null and b/Resources/Textures/Interface/Ashen/item_status_left.png differ
diff --git a/Resources/Textures/Interface/Ashen/item_status_left_highlight.png b/Resources/Textures/Interface/Ashen/item_status_left_highlight.png
new file mode 100644
index 0000000000..91cd15dd4c
Binary files /dev/null and b/Resources/Textures/Interface/Ashen/item_status_left_highlight.png differ
diff --git a/Resources/Textures/Interface/Ashen/item_status_right.png b/Resources/Textures/Interface/Ashen/item_status_right.png
new file mode 100644
index 0000000000..53f4f362d0
Binary files /dev/null and b/Resources/Textures/Interface/Ashen/item_status_right.png differ
diff --git a/Resources/Textures/Interface/Ashen/item_status_right_highlight.png b/Resources/Textures/Interface/Ashen/item_status_right_highlight.png
new file mode 100644
index 0000000000..ad16bab6d1
Binary files /dev/null and b/Resources/Textures/Interface/Ashen/item_status_right_highlight.png differ
diff --git a/Resources/Textures/Interface/Clockwork/item_status_left.png b/Resources/Textures/Interface/Clockwork/item_status_left.png
new file mode 100644
index 0000000000..1ce950362d
Binary files /dev/null and b/Resources/Textures/Interface/Clockwork/item_status_left.png differ
diff --git a/Resources/Textures/Interface/Clockwork/item_status_left_highlight.png b/Resources/Textures/Interface/Clockwork/item_status_left_highlight.png
new file mode 100644
index 0000000000..f715e06276
Binary files /dev/null and b/Resources/Textures/Interface/Clockwork/item_status_left_highlight.png differ
diff --git a/Resources/Textures/Interface/Clockwork/item_status_right.png b/Resources/Textures/Interface/Clockwork/item_status_right.png
new file mode 100644
index 0000000000..5ea5ffcffa
Binary files /dev/null and b/Resources/Textures/Interface/Clockwork/item_status_right.png differ
diff --git a/Resources/Textures/Interface/Clockwork/item_status_right_highlight.png b/Resources/Textures/Interface/Clockwork/item_status_right_highlight.png
new file mode 100644
index 0000000000..315d595c92
Binary files /dev/null and b/Resources/Textures/Interface/Clockwork/item_status_right_highlight.png differ
diff --git a/Resources/Textures/Interface/Default/item_status_left.png b/Resources/Textures/Interface/Default/item_status_left.png
new file mode 100644
index 0000000000..6c980f226e
Binary files /dev/null and b/Resources/Textures/Interface/Default/item_status_left.png differ
diff --git a/Resources/Textures/Interface/Default/item_status_left_highlight.png b/Resources/Textures/Interface/Default/item_status_left_highlight.png
new file mode 100644
index 0000000000..87dea5cf10
Binary files /dev/null and b/Resources/Textures/Interface/Default/item_status_left_highlight.png differ
diff --git a/Resources/Textures/Interface/Default/item_status_right.png b/Resources/Textures/Interface/Default/item_status_right.png
new file mode 100644
index 0000000000..82ad44b48c
Binary files /dev/null and b/Resources/Textures/Interface/Default/item_status_right.png differ
diff --git a/Resources/Textures/Interface/Default/item_status_right_highlight.png b/Resources/Textures/Interface/Default/item_status_right_highlight.png
new file mode 100644
index 0000000000..0c1c344848
Binary files /dev/null and b/Resources/Textures/Interface/Default/item_status_right_highlight.png differ
diff --git a/Resources/Textures/Interface/Minimalist/item_status_left.png b/Resources/Textures/Interface/Minimalist/item_status_left.png
new file mode 100644
index 0000000000..d70eca2fe9
Binary files /dev/null and b/Resources/Textures/Interface/Minimalist/item_status_left.png differ
diff --git a/Resources/Textures/Interface/Minimalist/item_status_left_highlight.png b/Resources/Textures/Interface/Minimalist/item_status_left_highlight.png
new file mode 100644
index 0000000000..b69872cd89
Binary files /dev/null and b/Resources/Textures/Interface/Minimalist/item_status_left_highlight.png differ
diff --git a/Resources/Textures/Interface/Minimalist/item_status_right.png b/Resources/Textures/Interface/Minimalist/item_status_right.png
new file mode 100644
index 0000000000..89171b9b47
Binary files /dev/null and b/Resources/Textures/Interface/Minimalist/item_status_right.png differ
diff --git a/Resources/Textures/Interface/Minimalist/item_status_right_highlight.png b/Resources/Textures/Interface/Minimalist/item_status_right_highlight.png
new file mode 100644
index 0000000000..d1474cee12
Binary files /dev/null and b/Resources/Textures/Interface/Minimalist/item_status_right_highlight.png differ
diff --git a/Resources/Textures/Interface/Nano/item_status_left.svg b/Resources/Textures/Interface/Nano/item_status_left.svg
deleted file mode 100644
index c97f8de016..0000000000
--- a/Resources/Textures/Interface/Nano/item_status_left.svg
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
diff --git a/Resources/Textures/Interface/Nano/item_status_left.svg.96dpi.png b/Resources/Textures/Interface/Nano/item_status_left.svg.96dpi.png
deleted file mode 100644
index 5169343ea4..0000000000
Binary files a/Resources/Textures/Interface/Nano/item_status_left.svg.96dpi.png and /dev/null differ
diff --git a/Resources/Textures/Interface/Nano/item_status_middle.svg b/Resources/Textures/Interface/Nano/item_status_middle.svg
deleted file mode 100644
index a913981db1..0000000000
--- a/Resources/Textures/Interface/Nano/item_status_middle.svg
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
diff --git a/Resources/Textures/Interface/Nano/item_status_middle.svg.96dpi.png b/Resources/Textures/Interface/Nano/item_status_middle.svg.96dpi.png
deleted file mode 100644
index dc1f84cea8..0000000000
Binary files a/Resources/Textures/Interface/Nano/item_status_middle.svg.96dpi.png and /dev/null differ
diff --git a/Resources/Textures/Interface/Nano/item_status_right.svg b/Resources/Textures/Interface/Nano/item_status_right.svg
deleted file mode 100644
index d898bb2ce0..0000000000
--- a/Resources/Textures/Interface/Nano/item_status_right.svg
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
diff --git a/Resources/Textures/Interface/Nano/item_status_right.svg.96dpi.png b/Resources/Textures/Interface/Nano/item_status_right.svg.96dpi.png
deleted file mode 100644
index 72f7e6de6b..0000000000
Binary files a/Resources/Textures/Interface/Nano/item_status_right.svg.96dpi.png and /dev/null differ
diff --git a/Resources/Textures/Interface/Plasmafire/item_status_left.png b/Resources/Textures/Interface/Plasmafire/item_status_left.png
new file mode 100644
index 0000000000..d5d25c9809
Binary files /dev/null and b/Resources/Textures/Interface/Plasmafire/item_status_left.png differ
diff --git a/Resources/Textures/Interface/Plasmafire/item_status_left_highlight.png b/Resources/Textures/Interface/Plasmafire/item_status_left_highlight.png
new file mode 100644
index 0000000000..afe37513fd
Binary files /dev/null and b/Resources/Textures/Interface/Plasmafire/item_status_left_highlight.png differ
diff --git a/Resources/Textures/Interface/Plasmafire/item_status_right.png b/Resources/Textures/Interface/Plasmafire/item_status_right.png
new file mode 100644
index 0000000000..ca97f81c8f
Binary files /dev/null and b/Resources/Textures/Interface/Plasmafire/item_status_right.png differ
diff --git a/Resources/Textures/Interface/Plasmafire/item_status_right_highlight.png b/Resources/Textures/Interface/Plasmafire/item_status_right_highlight.png
new file mode 100644
index 0000000000..b95822b737
Binary files /dev/null and b/Resources/Textures/Interface/Plasmafire/item_status_right_highlight.png differ
diff --git a/Resources/Textures/Interface/Retro/item_status_left.png b/Resources/Textures/Interface/Retro/item_status_left.png
new file mode 100644
index 0000000000..21b107b84d
Binary files /dev/null and b/Resources/Textures/Interface/Retro/item_status_left.png differ
diff --git a/Resources/Textures/Interface/Retro/item_status_left_highlight.png b/Resources/Textures/Interface/Retro/item_status_left_highlight.png
new file mode 100644
index 0000000000..fdd5a4fe7d
Binary files /dev/null and b/Resources/Textures/Interface/Retro/item_status_left_highlight.png differ
diff --git a/Resources/Textures/Interface/Retro/item_status_right.png b/Resources/Textures/Interface/Retro/item_status_right.png
new file mode 100644
index 0000000000..5e7d54618d
Binary files /dev/null and b/Resources/Textures/Interface/Retro/item_status_right.png differ
diff --git a/Resources/Textures/Interface/Retro/item_status_right_highlight.png b/Resources/Textures/Interface/Retro/item_status_right_highlight.png
new file mode 100644
index 0000000000..c6e12c41e6
Binary files /dev/null and b/Resources/Textures/Interface/Retro/item_status_right_highlight.png differ
diff --git a/Resources/Textures/Interface/Slimecore/item_status_left.png b/Resources/Textures/Interface/Slimecore/item_status_left.png
new file mode 100644
index 0000000000..a7d940f401
Binary files /dev/null and b/Resources/Textures/Interface/Slimecore/item_status_left.png differ
diff --git a/Resources/Textures/Interface/Slimecore/item_status_left_highlight.png b/Resources/Textures/Interface/Slimecore/item_status_left_highlight.png
new file mode 100644
index 0000000000..322355b135
Binary files /dev/null and b/Resources/Textures/Interface/Slimecore/item_status_left_highlight.png differ
diff --git a/Resources/Textures/Interface/Slimecore/item_status_right.png b/Resources/Textures/Interface/Slimecore/item_status_right.png
new file mode 100644
index 0000000000..77b53340a6
Binary files /dev/null and b/Resources/Textures/Interface/Slimecore/item_status_right.png differ
diff --git a/Resources/Textures/Interface/Slimecore/item_status_right_highlight.png b/Resources/Textures/Interface/Slimecore/item_status_right_highlight.png
new file mode 100644
index 0000000000..1e1a631db4
Binary files /dev/null and b/Resources/Textures/Interface/Slimecore/item_status_right_highlight.png differ