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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
19
Content.Shared/Alert/AlertAutoRemoveComponent.cs
Normal file
19
Content.Shared/Alert/AlertAutoRemoveComponent.cs
Normal 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;
|
||||
}
|
||||
@@ -7,5 +7,7 @@ public struct AlertState
|
||||
{
|
||||
public short? Severity;
|
||||
public (TimeSpan, TimeSpan)? Cooldown;
|
||||
public bool AutoRemove;
|
||||
public bool ShowCooldown;
|
||||
public AlertType Type;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user