From 6b8842c44d17b6a0d870880fa9d82a33ec7a266d Mon Sep 17 00:00:00 2001
From: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Date: Sat, 21 Jun 2025 19:59:37 +0200
Subject: [PATCH] Generic Numeric Alerts (#38370)
---
.../Alerts/GenericCounterAlertSystem.cs | 90 ++++++++++++++++++
.../Alerts/UpdateAlertSpriteEvent.cs | 5 +-
Content.Client/Revenant/RevenantSystem.cs | 16 ++--
.../Systems/Alerts/AlertsUIController.cs | 3 +-
.../Systems/Alerts/Controls/AlertControl.cs | 7 +-
.../GenericCounterAlertComponent.cs | 63 ++++++++++++
Content.Shared/Revenant/SharedRevenant.cs | 8 --
Resources/Prototypes/Alerts/revenant.yml | 11 ++-
.../Alerts/generic_counter.rsi/0.png | Bin 0 -> 131 bytes
.../Alerts/generic_counter.rsi/1.png | Bin 0 -> 120 bytes
.../Alerts/generic_counter.rsi/2.png | Bin 0 -> 142 bytes
.../Alerts/generic_counter.rsi/3.png | Bin 0 -> 139 bytes
.../Alerts/generic_counter.rsi/4.png | Bin 0 -> 135 bytes
.../Alerts/generic_counter.rsi/5.png | Bin 0 -> 141 bytes
.../Alerts/generic_counter.rsi/6.png | Bin 0 -> 142 bytes
.../Alerts/generic_counter.rsi/7.png | Bin 0 -> 137 bytes
.../Alerts/generic_counter.rsi/8.png | Bin 0 -> 134 bytes
.../Alerts/generic_counter.rsi/9.png | Bin 0 -> 144 bytes
.../Alerts/generic_counter.rsi/base.png | Bin 0 -> 265 bytes
.../Alerts/generic_counter.rsi/meta.json | 44 +++++++++
20 files changed, 225 insertions(+), 22 deletions(-)
create mode 100644 Content.Client/Alerts/GenericCounterAlertSystem.cs
create mode 100644 Content.Shared/Alert/Components/GenericCounterAlertComponent.cs
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/0.png
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/1.png
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/2.png
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/3.png
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/4.png
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/5.png
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/6.png
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/7.png
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/8.png
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/9.png
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/base.png
create mode 100644 Resources/Textures/Interface/Alerts/generic_counter.rsi/meta.json
diff --git a/Content.Client/Alerts/GenericCounterAlertSystem.cs b/Content.Client/Alerts/GenericCounterAlertSystem.cs
new file mode 100644
index 0000000000..de9d97d063
--- /dev/null
+++ b/Content.Client/Alerts/GenericCounterAlertSystem.cs
@@ -0,0 +1,90 @@
+using System.Numerics;
+using Content.Shared.Alert.Components;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+
+namespace Content.Client.Alerts;
+
+///
+/// This handles
+///
+public sealed class GenericCounterAlertSystem : EntitySystem
+{
+ [Dependency] private readonly SpriteSystem _sprite = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnUpdateAlertSprite);
+ }
+
+ private void OnUpdateAlertSprite(Entity ent, ref UpdateAlertSpriteEvent args)
+ {
+ var sprite = args.SpriteViewEnt.Comp;
+
+ var ev = new GetGenericAlertCounterAmountEvent(args.Alert);
+ RaiseLocalEvent(args.ViewerEnt, ref ev);
+
+ if (!ev.Handled)
+ return;
+
+ // It cannot be null if its handled, but good to check to avoid ugly null ignores.
+ if (ev.Amount == null)
+ return;
+
+ // How many digits can we display
+ var maxDigitCount = GetMaxDigitCount((ent, ent, sprite));
+
+ // Clamp it to a positive number that we can actually display in full (no rollover to 0)
+ var amount = (int) Math.Clamp(ev.Amount.Value, 0, Math.Pow(10, maxDigitCount) - 1);
+
+ // This is super wack but ig it works?
+ var digitCount = ent.Comp.HideLeadingZeroes
+ ? amount.ToString().Length
+ : maxDigitCount;
+
+ if (ent.Comp.HideLeadingZeroes)
+ {
+ for (var i = 0; i < ent.Comp.DigitKeys.Count; i++)
+ {
+ if (!_sprite.LayerMapTryGet(ent.Owner, ent.Comp.DigitKeys[i], out var layer, false))
+ continue;
+
+ _sprite.LayerSetVisible(ent.Owner, layer, i <= digitCount - 1);
+ }
+ }
+
+ // ReSharper disable once PossibleLossOfFraction
+ var baseOffset = (ent.Comp.AlertSize.X - digitCount * ent.Comp.GlyphWidth) / 2 * (1f / EyeManager.PixelsPerMeter);
+
+ for (var i = 0; i < ent.Comp.DigitKeys.Count; i++)
+ {
+ if (!_sprite.LayerMapTryGet(ent.Owner, ent.Comp.DigitKeys[i], out var layer, false))
+ continue;
+
+ var result = amount / (int) Math.Pow(10, i) % 10;
+ _sprite.LayerSetRsiState(ent.Owner, layer, result.ToString());
+
+ if (ent.Comp.CenterGlyph)
+ {
+ var offset = baseOffset + (digitCount - 1 - i) * ent.Comp.GlyphWidth * (1f / EyeManager.PixelsPerMeter);
+ _sprite.LayerSetOffset(ent.Owner, layer, new Vector2(offset, 0));
+ }
+ }
+ }
+
+ ///
+ /// Gets the number of digits that we can display.
+ ///
+ /// The number of digits.
+ private int GetMaxDigitCount(Entity ent)
+ {
+ for (var i = ent.Comp1.DigitKeys.Count - 1; i >= 0; i--)
+ {
+ if (_sprite.LayerExists((ent.Owner, ent.Comp2), ent.Comp1.DigitKeys[i]))
+ return i + 1;
+ }
+
+ return 0;
+ }
+}
diff --git a/Content.Client/Alerts/UpdateAlertSpriteEvent.cs b/Content.Client/Alerts/UpdateAlertSpriteEvent.cs
index 4f182c458c..d8222c2340 100644
--- a/Content.Client/Alerts/UpdateAlertSpriteEvent.cs
+++ b/Content.Client/Alerts/UpdateAlertSpriteEvent.cs
@@ -11,11 +11,14 @@ public record struct UpdateAlertSpriteEvent
{
public Entity SpriteViewEnt;
+ public EntityUid ViewerEnt;
+
public AlertPrototype Alert;
- public UpdateAlertSpriteEvent(Entity spriteViewEnt, AlertPrototype alert)
+ public UpdateAlertSpriteEvent(Entity spriteViewEnt, EntityUid viewerEnt, AlertPrototype alert)
{
SpriteViewEnt = spriteViewEnt;
+ ViewerEnt = viewerEnt;
Alert = alert;
}
}
diff --git a/Content.Client/Revenant/RevenantSystem.cs b/Content.Client/Revenant/RevenantSystem.cs
index 0534522b40..21d2d7888d 100644
--- a/Content.Client/Revenant/RevenantSystem.cs
+++ b/Content.Client/Revenant/RevenantSystem.cs
@@ -1,4 +1,6 @@
using Content.Client.Alerts;
+using Content.Shared.Alert;
+using Content.Shared.Alert.Components;
using Content.Shared.Revenant;
using Content.Shared.Revenant.Components;
using Robust.Client.GameObjects;
@@ -15,7 +17,7 @@ public sealed class RevenantSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent(OnAppearanceChange);
- SubscribeLocalEvent(OnUpdateAlert);
+ SubscribeLocalEvent(OnGetCounterAmount);
}
private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref AppearanceChangeEvent args)
@@ -40,14 +42,14 @@ public sealed class RevenantSystem : EntitySystem
}
}
- private void OnUpdateAlert(Entity ent, ref UpdateAlertSpriteEvent args)
+ private void OnGetCounterAmount(Entity ent, ref GetGenericAlertCounterAmountEvent args)
{
- if (args.Alert.ID != ent.Comp.EssenceAlert)
+ if (args.Handled)
return;
- var essence = Math.Clamp(ent.Comp.Essence.Int(), 0, 999);
- _sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit1, $"{(essence / 100) % 10}");
- _sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit2, $"{(essence / 10) % 10}");
- _sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit3, $"{essence % 10}");
+ if (ent.Comp.EssenceAlert != args.Alert)
+ return;
+
+ args.Amount = ent.Comp.Essence.Int();
}
}
diff --git a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs
index 5c19512038..3fe553be3b 100644
--- a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs
+++ b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs
@@ -98,7 +98,8 @@ public sealed class AlertsUIController : UIController, IOnStateEntered(spriteViewEnt, out var sprite))
return;
- var ev = new UpdateAlertSpriteEvent((spriteViewEnt, sprite), alert);
+ var ev = new UpdateAlertSpriteEvent((spriteViewEnt, sprite), player, alert);
EntityManager.EventBus.RaiseLocalEvent(player, ref ev);
+ EntityManager.EventBus.RaiseLocalEvent(spriteViewEnt, ref ev);
}
}
diff --git a/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs b/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs
index 847b253586..fe22ebba40 100644
--- a/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs
+++ b/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs
@@ -57,10 +57,15 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
_sprite = _entityManager.System();
TooltipSupplier = SupplyTooltip;
Alert = alert;
+
+ HorizontalAlignment = HAlignment.Left;
_severity = severity;
_icon = new SpriteView
{
- Scale = new Vector2(2, 2)
+ Scale = new Vector2(2, 2),
+ MaxSize = new Vector2(64, 64),
+ Stretch = SpriteView.StretchMode.None,
+ HorizontalAlignment = HAlignment.Left
};
SetupIcon();
diff --git a/Content.Shared/Alert/Components/GenericCounterAlertComponent.cs b/Content.Shared/Alert/Components/GenericCounterAlertComponent.cs
new file mode 100644
index 0000000000..d0c7fc1ad1
--- /dev/null
+++ b/Content.Shared/Alert/Components/GenericCounterAlertComponent.cs
@@ -0,0 +1,63 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Alert.Components;
+
+///
+/// This is used for an alert which simply displays a generic number over a texture.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class GenericCounterAlertComponent : Component
+{
+ ///
+ /// The width, in pixels, of an individual glyph, accounting for the space between glyphs.
+ /// A 3 pixel wide glyph with one pixel of space between it and the next would be a width of 4.
+ ///
+ [DataField]
+ public int GlyphWidth = 6;
+
+ ///
+ /// Whether the numbers should be centered on the glyph or just follow a static position.
+ ///
+ [DataField]
+ public bool CenterGlyph = true;
+
+ ///
+ /// Whether leading zeros should be hidden.
+ /// If true, "005" would display as "5".
+ ///
+ [DataField]
+ public bool HideLeadingZeroes = true;
+
+ ///
+ /// The size of the alert sprite.
+ /// Used to calculate offsets.
+ ///
+ [DataField]
+ public Vector2i AlertSize = new(32, 32);
+
+ ///
+ /// Digits that can be displayed by the alert, represented by their sprite layer.
+ /// Order defined corresponds to the digit it affects. 1st defined will affect 1st digit, 2nd affect 2nd digit and so on.
+ /// In this case ones would be on layer "1", tens on layer "10" etc.
+ ///
+ [DataField]
+ public List DigitKeys = new()
+ {
+ "1",
+ "10",
+ "100",
+ "1000",
+ "10000"
+ };
+}
+
+///
+/// Event raised to gather the amount the alert will display.
+///
+/// The alert which is currently requesting an update.
+/// The number to display on the alert.
+[ByRefEvent]
+public record struct GetGenericAlertCounterAmountEvent(AlertPrototype Alert, int? Amount = null)
+{
+ public bool Handled => Amount.HasValue;
+}
diff --git a/Content.Shared/Revenant/SharedRevenant.cs b/Content.Shared/Revenant/SharedRevenant.cs
index 485ad26dd2..c44e4408aa 100644
--- a/Content.Shared/Revenant/SharedRevenant.cs
+++ b/Content.Shared/Revenant/SharedRevenant.cs
@@ -70,11 +70,3 @@ public enum RevenantVisuals : byte
Stunned,
Harvesting,
}
-
-[NetSerializable, Serializable]
-public enum RevenantVisualLayers : byte
-{
- Digit1,
- Digit2,
- Digit3
-}
diff --git a/Resources/Prototypes/Alerts/revenant.yml b/Resources/Prototypes/Alerts/revenant.yml
index 38933df4fe..ab2b13905d 100644
--- a/Resources/Prototypes/Alerts/revenant.yml
+++ b/Resources/Prototypes/Alerts/revenant.yml
@@ -18,12 +18,15 @@
id: AlertEssenceSpriteView
categories: [ HideSpawnMenu ]
components:
+ - type: GenericCounterAlert
+ centerGlyph: false
+ hideLeadingZeroes: false
- type: Sprite
sprite: /Textures/Interface/Alerts/essence_counter.rsi
layers:
- map: [ "enum.AlertVisualLayers.Base" ]
- - map: [ "enum.RevenantVisualLayers.Digit1" ]
- - map: [ "enum.RevenantVisualLayers.Digit2" ]
- offset: 0.125, 0
- - map: [ "enum.RevenantVisualLayers.Digit3" ]
+ - map: [ "1" ]
offset: 0.25, 0
+ - map: [ "10" ]
+ offset: 0.125, 0
+ - map: [ "100" ]
diff --git a/Resources/Textures/Interface/Alerts/generic_counter.rsi/0.png b/Resources/Textures/Interface/Alerts/generic_counter.rsi/0.png
new file mode 100644
index 0000000000000000000000000000000000000000..8eb85f36dc347a26e25526f1d7ea1cfc8a18cc29
GIT binary patch
literal 131
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}ww^AIArY-_
zuNd+HRk0kj{{R2^W@E8!O`X=VCONGv3_s?oYIhXa!t|JE}w
bT*zfQ*(Ja}A?JM*&>#j+S3j3^P67$V-*sza4Q>Q+6z#N3l|BlP&M
p`!^k4e=KeLEh6`ym7&2;IOLW5dxsYHEkH9FJYD@<);T3K0RT!wGxh)g
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Interface/Alerts/generic_counter.rsi/3.png b/Resources/Textures/Interface/Alerts/generic_counter.rsi/3.png
new file mode 100644
index 0000000000000000000000000000000000000000..181f425acc5c5e9308e58c580b381f1ffd3488fe
GIT binary patch
literal 139
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}uAVNAArY-_
zuPE{{7;rdWJoNYdotddKBh(x}%x89H<`iN`*fl}rwq0+BhHn9X{ne+dm{Xo^(9geX
l`E0Jj?(6sdvokQ<6|)voe(NcCt`%q?gQu&X%Q~loCIHerE;#@I
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Interface/Alerts/generic_counter.rsi/4.png b/Resources/Textures/Interface/Alerts/generic_counter.rsi/4.png
new file mode 100644
index 0000000000000000000000000000000000000000..fe5680eb412dca6dadb2a6f4c29781fed24c950f
GIT binary patch
literal 135
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}j-D=#ArY-_
zFCF9ss&c+~?Qi+9$((BQULO1}ropPhz%Xmos+VV`^6Y2puzI^#z2oItVd)RrSGFx@
hpZMi9KLf)hGj}HWcNaGQxdAkc!PC{xWt~$(699TLFDC#1
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Interface/Alerts/generic_counter.rsi/5.png b/Resources/Textures/Interface/Alerts/generic_counter.rsi/5.png
new file mode 100644
index 0000000000000000000000000000000000000000..2361ca7f1d84086b6acd1d66b428d6f89eed079a
GIT binary patch
literal 141
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}?w&4=ArY-_
zuNd+jFyLT0xcATh^GnL+Na<8AU-GcTsX^ss8RLpoQEi@4!8OcxHp`qS{OWIhN$$bR
om~YYvyFR_;-y*+ICG!^d`Q6GN7A;ymA7~_lr>mdKI;Vst03L)ia{vGU
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Interface/Alerts/generic_counter.rsi/6.png b/Resources/Textures/Interface/Alerts/generic_counter.rsi/6.png
new file mode 100644
index 0000000000000000000000000000000000000000..a636e7890b890384fdc70f9087af998a8c69472f
GIT binary patch
literal 142
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}9-c0aArY-_
zuPE{{7;vy0Z2td$=B~C0S$-wQ0}pcraWZ_^Jz>eJRlfxUSF_gs%#P!~G0pafkg<5a
p`;rukJ(3Sn_&@w-W?<
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Interface/Alerts/generic_counter.rsi/7.png b/Resources/Textures/Interface/Alerts/generic_counter.rsi/7.png
new file mode 100644
index 0000000000000000000000000000000000000000..d29d6192ded80be417a454b2f24732264b8fdfc6
GIT binary patch
literal 137
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}&YmugArY-_
zFKiTKP~0WH#vh!I
fSU=e_FnG*9nj`(k;6?gTe~DWM4fT8=J`
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Interface/Alerts/generic_counter.rsi/9.png b/Resources/Textures/Interface/Alerts/generic_counter.rsi/9.png
new file mode 100644
index 0000000000000000000000000000000000000000..6325fbdabc40f4f58e287a92f4aa144c8520c401
GIT binary patch
literal 144
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}UY;(FArY-_
zuNd+HRXJZg^!I(wWH#aOgGc_G#YD+WU|`s9?Ag^3!Jig(OQC)Kv)g-&-uz{{dVfFb
q`48KKcKW2wE6=}E&(6Sbm(43i{q+aCpWlF{GI+ZBxvXxW4ZO5X@rY;Gi_uUMECfA}0_=^ADGJpN+-j%4;`yZZmYSWOKc4H62
z;qMJ$3pZ!1Jz#l8bLpo`zp^%Kul#DqVIwOjz=(tzn;lNe@0SdFxmQr)`1FU(zprvx
zy*|j}C+Q>Ltk#t_>sa)0