From ecd2d5a644540c6ad28903ee4fb2af87876e3030 Mon Sep 17 00:00:00 2001
From: Errant <35878406+Errant-4@users.noreply.github.com>
Date: Sat, 2 Mar 2024 15:07:05 +0100
Subject: [PATCH] Alerts for breathing plasma/tritium (#24484)
* Alert autoremove v0
* Code cleanup and timing
* comment
* Tritium, code compression
* not resolving manually
* reduced lookups, new comp
* fix-fix yes
* use RemCompDeferred, handle OnUnpaused
* missed a todo
* entitysystem resolve
* remove unnecessary component updates
* remove AlertState from comp, move EntityUnpausedEvent actions to AlertStateComponent's Timespan
* Code cleanup
* comments
* combines AutoRemove input into Clear
* minor logic adjustment that does not really change anything but is less ambiguous
---
.../Systems/Alerts/Widgets/AlertsUI.xaml.cs | 9 +-
.../Chemistry/ReagentEffects/AdjustAlert.cs | 45 +++++----
.../Alert/AlertAutoRemoveComponent.cs | 19 ++++
Content.Shared/Alert/AlertState.cs | 4 +-
Content.Shared/Alert/AlertsSystem.cs | 95 ++++++++++++++++++-
Resources/Prototypes/Reagents/gases.yml | 24 +++--
6 files changed, 165 insertions(+), 31 deletions(-)
create mode 100644 Content.Shared/Alert/AlertAutoRemoveComponent.cs
diff --git a/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs b/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs
index 52dcc7e51d..189de50407 100644
--- a/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs
@@ -92,7 +92,8 @@ public sealed partial class AlertsUI : UIWidget
{
// key is the same, simply update the existing control severity / cooldown
existingAlertControl.SetSeverity(alertState.Severity);
- existingAlertControl.Cooldown = alertState.Cooldown;
+ if (alertState.ShowCooldown)
+ existingAlertControl.Cooldown = alertState.Cooldown;
}
else
{
@@ -133,9 +134,13 @@ public sealed partial class AlertsUI : UIWidget
private AlertControl CreateAlertControl(AlertPrototype alert, AlertState alertState)
{
+ (TimeSpan, TimeSpan)? cooldown = null;
+ if (alertState.ShowCooldown)
+ cooldown = alertState.Cooldown;
+
var alertControl = new AlertControl(alert, alertState.Severity)
{
- Cooldown = alertState.Cooldown
+ Cooldown = cooldown
};
alertControl.OnPressed += AlertControlPressed;
return alertControl;
diff --git a/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs b/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs
index cf3d71405b..8d475570ad 100644
--- a/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs
+++ b/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs
@@ -7,15 +7,27 @@ namespace Content.Server.Chemistry.ReagentEffects;
public sealed partial class AdjustAlert : ReagentEffect
{
+ ///
+ /// The specific Alert that will be adjusted
+ ///
[DataField("alertType", required: true)]
public AlertType Type;
+ ///
+ /// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately.
+ ///
[DataField]
public bool Clear;
+ ///
+ /// Visually display cooldown progress over the alert icon.
+ ///
[DataField]
- public bool Cooldown;
+ public bool ShowCooldown;
+ ///
+ /// The length of the cooldown or delay before removing the alert (in seconds).
+ ///
[DataField]
public float Time;
@@ -24,23 +36,24 @@ public sealed partial class AdjustAlert : ReagentEffect
public override void Effect(ReagentEffectArgs args)
{
- var alertSys = EntitySystem.Get();
- if (args.EntityManager.HasComponent(args.SolutionEntity))
+ var alertSys = args.EntityManager.EntitySysManager.GetEntitySystem();
+ if (!args.EntityManager.HasComponent(args.SolutionEntity))
+ return;
+
+ if (Clear && Time <= 0)
{
- if (Clear)
- {
alertSys.ClearAlert(args.SolutionEntity, Type);
- }
- else
- {
- (TimeSpan, TimeSpan)? cooldown = null;
- if (Cooldown)
- {
- var timing = IoCManager.Resolve();
- cooldown = (timing.CurTime, timing.CurTime + TimeSpan.FromSeconds(Time));
- }
- alertSys.ShowAlert(args.SolutionEntity, Type, cooldown: cooldown);
- }
}
+ else
+ {
+ var timing = IoCManager.Resolve();
+ (TimeSpan, TimeSpan)? cooldown = null;
+
+ if ((ShowCooldown || Clear) && Time > 0)
+ cooldown = (timing.CurTime, timing.CurTime + TimeSpan.FromSeconds(Time));
+
+ alertSys.ShowAlert(args.SolutionEntity, Type, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown);
+ }
+
}
}
diff --git a/Content.Shared/Alert/AlertAutoRemoveComponent.cs b/Content.Shared/Alert/AlertAutoRemoveComponent.cs
new file mode 100644
index 0000000000..44e2dc91dc
--- /dev/null
+++ b/Content.Shared/Alert/AlertAutoRemoveComponent.cs
@@ -0,0 +1,19 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Alert;
+
+///
+/// Copy of the entity's alerts that are flagged for autoRemove, so that not all of the alerts need to be checked constantly
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class AlertAutoRemoveComponent : Component
+{
+ ///
+ /// List of alerts that have to be checked on every tick for automatic removal at a specific time
+ ///
+ [AutoNetworkedField]
+ [DataField]
+ public List AlertKeys = new();
+
+ public override bool SendOnlyToOwner => true;
+}
diff --git a/Content.Shared/Alert/AlertState.cs b/Content.Shared/Alert/AlertState.cs
index b7244d3d4e..effd952203 100644
--- a/Content.Shared/Alert/AlertState.cs
+++ b/Content.Shared/Alert/AlertState.cs
@@ -7,5 +7,7 @@ public struct AlertState
{
public short? Severity;
public (TimeSpan, TimeSpan)? Cooldown;
+ public bool AutoRemove;
+ public bool ShowCooldown;
public AlertType Type;
-}
\ No newline at end of file
+}
diff --git a/Content.Shared/Alert/AlertsSystem.cs b/Content.Shared/Alert/AlertsSystem.cs
index 424a4670ba..d8737a717a 100644
--- a/Content.Shared/Alert/AlertsSystem.cs
+++ b/Content.Shared/Alert/AlertsSystem.cs
@@ -2,12 +2,14 @@ using System.Collections.Frozen;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
namespace Content.Shared.Alert;
public abstract class AlertsSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
private FrozenDictionary _typeToAlert = default!;
@@ -74,7 +76,9 @@ public abstract class AlertsSystem : EntitySystem
/// severity, if supported by the alert
/// cooldown start and end, if null there will be no cooldown (and it will
/// be erased if there is currently a cooldown for the alert)
- public void ShowAlert(EntityUid euid, AlertType alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null)
+ /// if true, the alert will be removed at the end of the cooldown
+ /// if true, the cooldown will be visibly shown over the alert icon
+ public void ShowAlert(EntityUid euid, AlertType alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null, bool autoRemove = false, bool showCooldown = true )
{
if (!TryComp(euid, out AlertsComponent? alertsComponent))
return;
@@ -86,7 +90,9 @@ public abstract class AlertsSystem : EntitySystem
if (alertsComponent.Alerts.TryGetValue(alert.AlertKey, out var alertStateCallback) &&
alertStateCallback.Type == alertType &&
alertStateCallback.Severity == severity &&
- alertStateCallback.Cooldown == cooldown)
+ alertStateCallback.Cooldown == cooldown &&
+ alertStateCallback.AutoRemove == autoRemove &&
+ alertStateCallback.ShowCooldown == showCooldown)
{
return;
}
@@ -94,8 +100,17 @@ public abstract class AlertsSystem : EntitySystem
// In the case we're changing the alert type but not the category, we need to remove it first.
alertsComponent.Alerts.Remove(alert.AlertKey);
- alertsComponent.Alerts[alert.AlertKey] = new AlertState
- { Cooldown = cooldown, Severity = severity, Type = alertType };
+ var state = new AlertState
+ { Cooldown = cooldown, Severity = severity, Type = alertType, AutoRemove = autoRemove, ShowCooldown = showCooldown};
+ alertsComponent.Alerts[alert.AlertKey] = state;
+
+ // Keeping a list of AutoRemove alerts, so Update() doesn't need to check every alert
+ if (autoRemove)
+ {
+ var autoComp = EnsureComp(euid);
+ if (!autoComp.AlertKeys.Contains(alert.AlertKey))
+ autoComp.AlertKeys.Add(alert.AlertKey);
+ }
AfterShowAlert((euid, alertsComponent));
@@ -171,11 +186,81 @@ public abstract class AlertsSystem : EntitySystem
SubscribeLocalEvent(HandleComponentShutdown);
SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnAutoRemoveUnPaused);
+
SubscribeNetworkEvent(HandleClickAlert);
SubscribeLocalEvent(HandlePrototypesReloaded);
LoadPrototypes();
}
+ private void OnAutoRemoveUnPaused(EntityUid uid, AlertAutoRemoveComponent comp, EntityUnpausedEvent args)
+ {
+ if (!TryComp(uid, out var alertComp))
+ {
+ return;
+ }
+
+ var dirty = false;
+
+ foreach (var alert in alertComp.Alerts)
+ {
+ if (alert.Value.Cooldown is null)
+ continue;
+
+ var cooldown = (alert.Value.Cooldown.Value.Item1, alert.Value.Cooldown.Value.Item2 + args.PausedTime);
+
+ var state = new AlertState
+ {
+ Severity = alert.Value.Severity,
+ Cooldown = cooldown,
+ ShowCooldown = alert.Value.ShowCooldown,
+ AutoRemove = alert.Value.AutoRemove,
+ Type = alert.Value.Type
+ };
+ alertComp.Alerts[alert.Key] = state;
+ dirty = true;
+ }
+
+ if (dirty)
+ Dirty(uid, comp);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var autoComp))
+ {
+ var dirtyComp = false;
+ if (autoComp.AlertKeys.Count <= 0 || !TryComp(uid, out var alertComp))
+ {
+ RemCompDeferred(uid, autoComp);
+ continue;
+ }
+
+ var removeList = new List();
+ foreach (var alertKey in autoComp.AlertKeys)
+ {
+ alertComp.Alerts.TryGetValue(alertKey, out var alertState);
+
+ if (alertState.Cooldown is null || alertState.Cooldown.Value.Item2 >= _timing.CurTime)
+ continue;
+ removeList.Add(alertKey);
+ alertComp.Alerts.Remove(alertKey);
+ dirtyComp = true;
+ }
+
+ foreach (var alertKey in removeList)
+ {
+ autoComp.AlertKeys.Remove(alertKey);
+ }
+
+ if (dirtyComp)
+ Dirty(uid, alertComp);
+ }
+ }
+
protected virtual void HandleComponentShutdown(EntityUid uid, AlertsComponent component, ComponentShutdown args)
{
RaiseLocalEvent(uid, new AlertSyncEvent(uid), true);
@@ -200,7 +285,7 @@ public abstract class AlertsSystem : EntitySystem
if (!dict.TryAdd(alert.AlertType, alert))
{
Log.Error("Found alert with duplicate alertType {0} - all alerts must have" +
- " a unique alerttype, this one will be skipped", alert.AlertType);
+ " a unique alertType, this one will be skipped", alert.AlertType);
}
}
diff --git a/Resources/Prototypes/Reagents/gases.yml b/Resources/Prototypes/Reagents/gases.yml
index 2566076be7..9cb73fffb8 100644
--- a/Resources/Prototypes/Reagents/gases.yml
+++ b/Resources/Prototypes/Reagents/gases.yml
@@ -68,9 +68,14 @@
types:
Poison:
1
- # Cant be added until I add metabolism effects on reagent removal
- #- !type:AdjustAlert
- # alertType: Toxins
+ # We need a metabolism effect on reagent removal
+ - !type:AdjustAlert
+ alertType: Toxins
+ conditions:
+ - !type:ReagentThreshold
+ min: 1.5
+ clear: True
+ time: 5
reactiveEffects:
Flammable:
methods: [ Touch ]
@@ -109,9 +114,14 @@
types:
Radiation:
1
- # Cant be added until I add metabolism effects on reagent removal
- #- !type:AdjustAlert
- # alertType: Toxins
+ # We need a metabolism effect on reagent removal
+ - !type:AdjustAlert
+ alertType: Toxins
+ conditions:
+ - !type:ReagentThreshold
+ min: 1.5
+ clear: True
+ time: 5
- type: reagent
id: CarbonDioxide
@@ -148,7 +158,7 @@
type: Plant
shouldHave: false
factor: -4
- # Cant be added until I add metabolism effects on reagent removal
+ # We need a metabolism effect on reagent removal
#- !type:AdjustAlert
# alertType: CarbonDioxide