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