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
This commit is contained in:
Errant
2024-03-02 15:07:05 +01:00
committed by GitHub
parent e7a806a443
commit ecd2d5a644
6 changed files with 165 additions and 31 deletions

View File

@@ -92,6 +92,7 @@ public sealed partial class AlertsUI : UIWidget
{
// key is the same, simply update the existing control severity / cooldown
existingAlertControl.SetSeverity(alertState.Severity);
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;

View File

@@ -7,15 +7,27 @@ namespace Content.Server.Chemistry.ReagentEffects;
public sealed partial class AdjustAlert : ReagentEffect
{
/// <summary>
/// The specific Alert that will be adjusted
/// </summary>
[DataField("alertType", required: true)]
public AlertType Type;
/// <summary>
/// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately.
/// </summary>
[DataField]
public bool Clear;
/// <summary>
/// Visually display cooldown progress over the alert icon.
/// </summary>
[DataField]
public bool Cooldown;
public bool ShowCooldown;
/// <summary>
/// The length of the cooldown or delay before removing the alert (in seconds).
/// </summary>
[DataField]
public float Time;
@@ -24,23 +36,24 @@ public sealed partial class AdjustAlert : ReagentEffect
public override void Effect(ReagentEffectArgs args)
{
var alertSys = EntitySystem.Get<AlertsSystem>();
if (args.EntityManager.HasComponent<AlertsComponent>(args.SolutionEntity))
{
if (Clear)
var alertSys = args.EntityManager.EntitySysManager.GetEntitySystem<AlertsSystem>();
if (!args.EntityManager.HasComponent<AlertsComponent>(args.SolutionEntity))
return;
if (Clear && Time <= 0)
{
alertSys.ClearAlert(args.SolutionEntity, Type);
}
else
{
(TimeSpan, TimeSpan)? cooldown = null;
if (Cooldown)
{
var timing = IoCManager.Resolve<IGameTiming>();
(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);
}
alertSys.ShowAlert(args.SolutionEntity, Type, cooldown: cooldown);
}
}
}
}

View File

@@ -0,0 +1,19 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Alert;
/// <summary>
/// Copy of the entity's alerts that are flagged for autoRemove, so that not all of the alerts need to be checked constantly
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class AlertAutoRemoveComponent : Component
{
/// <summary>
/// List of alerts that have to be checked on every tick for automatic removal at a specific time
/// </summary>
[AutoNetworkedField]
[DataField]
public List<AlertKey> AlertKeys = new();
public override bool SendOnlyToOwner => true;
}

View File

@@ -7,5 +7,7 @@ public struct AlertState
{
public short? Severity;
public (TimeSpan, TimeSpan)? Cooldown;
public bool AutoRemove;
public bool ShowCooldown;
public AlertType Type;
}

View File

@@ -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<AlertType, AlertPrototype> _typeToAlert = default!;
@@ -74,7 +76,9 @@ public abstract class AlertsSystem : EntitySystem
/// <param name="severity">severity, if supported by the alert</param>
/// <param name="cooldown">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)</param>
public void ShowAlert(EntityUid euid, AlertType alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null)
/// <param name="autoRemove">if true, the alert will be removed at the end of the cooldown</param>
/// <param name="showCooldown">if true, the cooldown will be visibly shown over the alert icon</param>
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<AlertAutoRemoveComponent>(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<AlertsComponent, ComponentShutdown>(HandleComponentShutdown);
SubscribeLocalEvent<AlertsComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<AlertAutoRemoveComponent, EntityUnpausedEvent>(OnAutoRemoveUnPaused);
SubscribeNetworkEvent<ClickAlertEvent>(HandleClickAlert);
SubscribeLocalEvent<PrototypesReloadedEventArgs>(HandlePrototypesReloaded);
LoadPrototypes();
}
private void OnAutoRemoveUnPaused(EntityUid uid, AlertAutoRemoveComponent comp, EntityUnpausedEvent args)
{
if (!TryComp<AlertsComponent>(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<AlertAutoRemoveComponent>();
while (query.MoveNext(out var uid, out var autoComp))
{
var dirtyComp = false;
if (autoComp.AlertKeys.Count <= 0 || !TryComp<AlertsComponent>(uid, out var alertComp))
{
RemCompDeferred(uid, autoComp);
continue;
}
var removeList = new List<AlertKey>();
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);
}
}

View File

@@ -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