Generic Numeric Alerts (#38370)
90
Content.Client/Alerts/GenericCounterAlertSystem.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Content.Shared.Alert.Components;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
|
||||||
|
namespace Content.Client.Alerts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles <see cref="GenericCounterAlertComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GenericCounterAlertSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<GenericCounterAlertComponent, UpdateAlertSpriteEvent>(OnUpdateAlertSprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUpdateAlertSprite(Entity<GenericCounterAlertComponent> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of digits that we can display.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The number of digits.</returns>
|
||||||
|
private int GetMaxDigitCount(Entity<GenericCounterAlertComponent, SpriteComponent> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,11 +11,14 @@ public record struct UpdateAlertSpriteEvent
|
|||||||
{
|
{
|
||||||
public Entity<SpriteComponent> SpriteViewEnt;
|
public Entity<SpriteComponent> SpriteViewEnt;
|
||||||
|
|
||||||
|
public EntityUid ViewerEnt;
|
||||||
|
|
||||||
public AlertPrototype Alert;
|
public AlertPrototype Alert;
|
||||||
|
|
||||||
public UpdateAlertSpriteEvent(Entity<SpriteComponent> spriteViewEnt, AlertPrototype alert)
|
public UpdateAlertSpriteEvent(Entity<SpriteComponent> spriteViewEnt, EntityUid viewerEnt, AlertPrototype alert)
|
||||||
{
|
{
|
||||||
SpriteViewEnt = spriteViewEnt;
|
SpriteViewEnt = spriteViewEnt;
|
||||||
|
ViewerEnt = viewerEnt;
|
||||||
Alert = alert;
|
Alert = alert;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Content.Client.Alerts;
|
using Content.Client.Alerts;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.Alert.Components;
|
||||||
using Content.Shared.Revenant;
|
using Content.Shared.Revenant;
|
||||||
using Content.Shared.Revenant.Components;
|
using Content.Shared.Revenant.Components;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
@@ -15,7 +17,7 @@ public sealed class RevenantSystem : EntitySystem
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<RevenantComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
SubscribeLocalEvent<RevenantComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||||
SubscribeLocalEvent<RevenantComponent, UpdateAlertSpriteEvent>(OnUpdateAlert);
|
SubscribeLocalEvent<RevenantComponent, GetGenericAlertCounterAmountEvent>(OnGetCounterAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref AppearanceChangeEvent args)
|
private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref AppearanceChangeEvent args)
|
||||||
@@ -40,14 +42,14 @@ public sealed class RevenantSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUpdateAlert(Entity<RevenantComponent> ent, ref UpdateAlertSpriteEvent args)
|
private void OnGetCounterAmount(Entity<RevenantComponent> ent, ref GetGenericAlertCounterAmountEvent args)
|
||||||
{
|
{
|
||||||
if (args.Alert.ID != ent.Comp.EssenceAlert)
|
if (args.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var essence = Math.Clamp(ent.Comp.Essence.Int(), 0, 999);
|
if (ent.Comp.EssenceAlert != args.Alert)
|
||||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit1, $"{(essence / 100) % 10}");
|
return;
|
||||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit2, $"{(essence / 10) % 10}");
|
|
||||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit3, $"{essence % 10}");
|
args.Amount = ent.Comp.Essence.Int();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,8 @@ public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayS
|
|||||||
if (!EntityManager.TryGetComponent<SpriteComponent>(spriteViewEnt, out var sprite))
|
if (!EntityManager.TryGetComponent<SpriteComponent>(spriteViewEnt, out var sprite))
|
||||||
return;
|
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(player, ref ev);
|
||||||
|
EntityManager.EventBus.RaiseLocalEvent(spriteViewEnt, ref ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,15 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
|||||||
_sprite = _entityManager.System<SpriteSystem>();
|
_sprite = _entityManager.System<SpriteSystem>();
|
||||||
TooltipSupplier = SupplyTooltip;
|
TooltipSupplier = SupplyTooltip;
|
||||||
Alert = alert;
|
Alert = alert;
|
||||||
|
|
||||||
|
HorizontalAlignment = HAlignment.Left;
|
||||||
_severity = severity;
|
_severity = severity;
|
||||||
_icon = new SpriteView
|
_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();
|
SetupIcon();
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Shared.Alert.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for an alert which simply displays a generic number over a texture.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
public sealed partial class GenericCounterAlertComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int GlyphWidth = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the numbers should be centered on the glyph or just follow a static position.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool CenterGlyph = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether leading zeros should be hidden.
|
||||||
|
/// If true, "005" would display as "5".
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool HideLeadingZeroes = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of the alert sprite.
|
||||||
|
/// Used to calculate offsets.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public Vector2i AlertSize = new(32, 32);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public List<string> DigitKeys = new()
|
||||||
|
{
|
||||||
|
"1",
|
||||||
|
"10",
|
||||||
|
"100",
|
||||||
|
"1000",
|
||||||
|
"10000"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised to gather the amount the alert will display.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Alert">The alert which is currently requesting an update.</param>
|
||||||
|
/// <param name="Amount">The number to display on the alert.</param>
|
||||||
|
[ByRefEvent]
|
||||||
|
public record struct GetGenericAlertCounterAmountEvent(AlertPrototype Alert, int? Amount = null)
|
||||||
|
{
|
||||||
|
public bool Handled => Amount.HasValue;
|
||||||
|
}
|
||||||
@@ -70,11 +70,3 @@ public enum RevenantVisuals : byte
|
|||||||
Stunned,
|
Stunned,
|
||||||
Harvesting,
|
Harvesting,
|
||||||
}
|
}
|
||||||
|
|
||||||
[NetSerializable, Serializable]
|
|
||||||
public enum RevenantVisualLayers : byte
|
|
||||||
{
|
|
||||||
Digit1,
|
|
||||||
Digit2,
|
|
||||||
Digit3
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,12 +18,15 @@
|
|||||||
id: AlertEssenceSpriteView
|
id: AlertEssenceSpriteView
|
||||||
categories: [ HideSpawnMenu ]
|
categories: [ HideSpawnMenu ]
|
||||||
components:
|
components:
|
||||||
|
- type: GenericCounterAlert
|
||||||
|
centerGlyph: false
|
||||||
|
hideLeadingZeroes: false
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
layers:
|
layers:
|
||||||
- map: [ "enum.AlertVisualLayers.Base" ]
|
- map: [ "enum.AlertVisualLayers.Base" ]
|
||||||
- map: [ "enum.RevenantVisualLayers.Digit1" ]
|
- map: [ "1" ]
|
||||||
- map: [ "enum.RevenantVisualLayers.Digit2" ]
|
|
||||||
offset: 0.125, 0
|
|
||||||
- map: [ "enum.RevenantVisualLayers.Digit3" ]
|
|
||||||
offset: 0.25, 0
|
offset: 0.25, 0
|
||||||
|
- map: [ "10" ]
|
||||||
|
offset: 0.125, 0
|
||||||
|
- map: [ "100" ]
|
||||||
|
|||||||
BIN
Resources/Textures/Interface/Alerts/generic_counter.rsi/0.png
Normal file
|
After Width: | Height: | Size: 131 B |
BIN
Resources/Textures/Interface/Alerts/generic_counter.rsi/1.png
Normal file
|
After Width: | Height: | Size: 120 B |
BIN
Resources/Textures/Interface/Alerts/generic_counter.rsi/2.png
Normal file
|
After Width: | Height: | Size: 142 B |
BIN
Resources/Textures/Interface/Alerts/generic_counter.rsi/3.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
Resources/Textures/Interface/Alerts/generic_counter.rsi/4.png
Normal file
|
After Width: | Height: | Size: 135 B |
BIN
Resources/Textures/Interface/Alerts/generic_counter.rsi/5.png
Normal file
|
After Width: | Height: | Size: 141 B |
BIN
Resources/Textures/Interface/Alerts/generic_counter.rsi/6.png
Normal file
|
After Width: | Height: | Size: 142 B |
BIN
Resources/Textures/Interface/Alerts/generic_counter.rsi/7.png
Normal file
|
After Width: | Height: | Size: 137 B |
BIN
Resources/Textures/Interface/Alerts/generic_counter.rsi/8.png
Normal file
|
After Width: | Height: | Size: 134 B |
BIN
Resources/Textures/Interface/Alerts/generic_counter.rsi/9.png
Normal file
|
After Width: | Height: | Size: 144 B |
BIN
Resources/Textures/Interface/Alerts/generic_counter.rsi/base.png
Normal file
|
After Width: | Height: | Size: 265 B |
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Created by EmoGarbage404",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "base"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "9"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||