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,7 +92,8 @@ public sealed partial class AlertsUI : UIWidget
|
|||||||
{
|
{
|
||||||
// key is the same, simply update the existing control severity / cooldown
|
// key is the same, simply update the existing control severity / cooldown
|
||||||
existingAlertControl.SetSeverity(alertState.Severity);
|
existingAlertControl.SetSeverity(alertState.Severity);
|
||||||
existingAlertControl.Cooldown = alertState.Cooldown;
|
if (alertState.ShowCooldown)
|
||||||
|
existingAlertControl.Cooldown = alertState.Cooldown;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -133,9 +134,13 @@ public sealed partial class AlertsUI : UIWidget
|
|||||||
|
|
||||||
private AlertControl CreateAlertControl(AlertPrototype alert, AlertState alertState)
|
private AlertControl CreateAlertControl(AlertPrototype alert, AlertState alertState)
|
||||||
{
|
{
|
||||||
|
(TimeSpan, TimeSpan)? cooldown = null;
|
||||||
|
if (alertState.ShowCooldown)
|
||||||
|
cooldown = alertState.Cooldown;
|
||||||
|
|
||||||
var alertControl = new AlertControl(alert, alertState.Severity)
|
var alertControl = new AlertControl(alert, alertState.Severity)
|
||||||
{
|
{
|
||||||
Cooldown = alertState.Cooldown
|
Cooldown = cooldown
|
||||||
};
|
};
|
||||||
alertControl.OnPressed += AlertControlPressed;
|
alertControl.OnPressed += AlertControlPressed;
|
||||||
return alertControl;
|
return alertControl;
|
||||||
|
|||||||
@@ -7,15 +7,27 @@ namespace Content.Server.Chemistry.ReagentEffects;
|
|||||||
|
|
||||||
public sealed partial class AdjustAlert : ReagentEffect
|
public sealed partial class AdjustAlert : ReagentEffect
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The specific Alert that will be adjusted
|
||||||
|
/// </summary>
|
||||||
[DataField("alertType", required: true)]
|
[DataField("alertType", required: true)]
|
||||||
public AlertType Type;
|
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]
|
[DataField]
|
||||||
public bool Clear;
|
public bool Clear;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Visually display cooldown progress over the alert icon.
|
||||||
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public bool Cooldown;
|
public bool ShowCooldown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The length of the cooldown or delay before removing the alert (in seconds).
|
||||||
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public float Time;
|
public float Time;
|
||||||
|
|
||||||
@@ -24,23 +36,24 @@ public sealed partial class AdjustAlert : ReagentEffect
|
|||||||
|
|
||||||
public override void Effect(ReagentEffectArgs args)
|
public override void Effect(ReagentEffectArgs args)
|
||||||
{
|
{
|
||||||
var alertSys = EntitySystem.Get<AlertsSystem>();
|
var alertSys = args.EntityManager.EntitySysManager.GetEntitySystem<AlertsSystem>();
|
||||||
if (args.EntityManager.HasComponent<AlertsComponent>(args.SolutionEntity))
|
if (!args.EntityManager.HasComponent<AlertsComponent>(args.SolutionEntity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Clear && Time <= 0)
|
||||||
{
|
{
|
||||||
if (Clear)
|
|
||||||
{
|
|
||||||
alertSys.ClearAlert(args.SolutionEntity, Type);
|
alertSys.ClearAlert(args.SolutionEntity, Type);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
(TimeSpan, TimeSpan)? cooldown = null;
|
|
||||||
if (Cooldown)
|
|
||||||
{
|
|
||||||
var timing = IoCManager.Resolve<IGameTiming>();
|
|
||||||
cooldown = (timing.CurTime, timing.CurTime + TimeSpan.FromSeconds(Time));
|
|
||||||
}
|
|
||||||
alertSys.ShowAlert(args.SolutionEntity, Type, cooldown: cooldown);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 short? Severity;
|
||||||
public (TimeSpan, TimeSpan)? Cooldown;
|
public (TimeSpan, TimeSpan)? Cooldown;
|
||||||
|
public bool AutoRemove;
|
||||||
|
public bool ShowCooldown;
|
||||||
public AlertType Type;
|
public AlertType Type;
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,14 @@ using System.Collections.Frozen;
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Shared.Alert;
|
namespace Content.Shared.Alert;
|
||||||
|
|
||||||
public abstract class AlertsSystem : EntitySystem
|
public abstract class AlertsSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
|
||||||
private FrozenDictionary<AlertType, AlertPrototype> _typeToAlert = 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="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
|
/// <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>
|
/// 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))
|
if (!TryComp(euid, out AlertsComponent? alertsComponent))
|
||||||
return;
|
return;
|
||||||
@@ -86,7 +90,9 @@ public abstract class AlertsSystem : EntitySystem
|
|||||||
if (alertsComponent.Alerts.TryGetValue(alert.AlertKey, out var alertStateCallback) &&
|
if (alertsComponent.Alerts.TryGetValue(alert.AlertKey, out var alertStateCallback) &&
|
||||||
alertStateCallback.Type == alertType &&
|
alertStateCallback.Type == alertType &&
|
||||||
alertStateCallback.Severity == severity &&
|
alertStateCallback.Severity == severity &&
|
||||||
alertStateCallback.Cooldown == cooldown)
|
alertStateCallback.Cooldown == cooldown &&
|
||||||
|
alertStateCallback.AutoRemove == autoRemove &&
|
||||||
|
alertStateCallback.ShowCooldown == showCooldown)
|
||||||
{
|
{
|
||||||
return;
|
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.
|
// 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.Remove(alert.AlertKey);
|
||||||
|
|
||||||
alertsComponent.Alerts[alert.AlertKey] = new AlertState
|
var state = new AlertState
|
||||||
{ Cooldown = cooldown, Severity = severity, Type = alertType };
|
{ 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));
|
AfterShowAlert((euid, alertsComponent));
|
||||||
|
|
||||||
@@ -171,11 +186,81 @@ public abstract class AlertsSystem : EntitySystem
|
|||||||
SubscribeLocalEvent<AlertsComponent, ComponentShutdown>(HandleComponentShutdown);
|
SubscribeLocalEvent<AlertsComponent, ComponentShutdown>(HandleComponentShutdown);
|
||||||
SubscribeLocalEvent<AlertsComponent, PlayerAttachedEvent>(OnPlayerAttached);
|
SubscribeLocalEvent<AlertsComponent, PlayerAttachedEvent>(OnPlayerAttached);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<AlertAutoRemoveComponent, EntityUnpausedEvent>(OnAutoRemoveUnPaused);
|
||||||
|
|
||||||
SubscribeNetworkEvent<ClickAlertEvent>(HandleClickAlert);
|
SubscribeNetworkEvent<ClickAlertEvent>(HandleClickAlert);
|
||||||
SubscribeLocalEvent<PrototypesReloadedEventArgs>(HandlePrototypesReloaded);
|
SubscribeLocalEvent<PrototypesReloadedEventArgs>(HandlePrototypesReloaded);
|
||||||
LoadPrototypes();
|
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)
|
protected virtual void HandleComponentShutdown(EntityUid uid, AlertsComponent component, ComponentShutdown args)
|
||||||
{
|
{
|
||||||
RaiseLocalEvent(uid, new AlertSyncEvent(uid), true);
|
RaiseLocalEvent(uid, new AlertSyncEvent(uid), true);
|
||||||
@@ -200,7 +285,7 @@ public abstract class AlertsSystem : EntitySystem
|
|||||||
if (!dict.TryAdd(alert.AlertType, alert))
|
if (!dict.TryAdd(alert.AlertType, alert))
|
||||||
{
|
{
|
||||||
Log.Error("Found alert with duplicate alertType {0} - all alerts must have" +
|
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:
|
types:
|
||||||
Poison:
|
Poison:
|
||||||
1
|
1
|
||||||
# Cant be added until I add metabolism effects on reagent removal
|
# We need a metabolism effect on reagent removal
|
||||||
#- !type:AdjustAlert
|
- !type:AdjustAlert
|
||||||
# alertType: Toxins
|
alertType: Toxins
|
||||||
|
conditions:
|
||||||
|
- !type:ReagentThreshold
|
||||||
|
min: 1.5
|
||||||
|
clear: True
|
||||||
|
time: 5
|
||||||
reactiveEffects:
|
reactiveEffects:
|
||||||
Flammable:
|
Flammable:
|
||||||
methods: [ Touch ]
|
methods: [ Touch ]
|
||||||
@@ -109,9 +114,14 @@
|
|||||||
types:
|
types:
|
||||||
Radiation:
|
Radiation:
|
||||||
1
|
1
|
||||||
# Cant be added until I add metabolism effects on reagent removal
|
# We need a metabolism effect on reagent removal
|
||||||
#- !type:AdjustAlert
|
- !type:AdjustAlert
|
||||||
# alertType: Toxins
|
alertType: Toxins
|
||||||
|
conditions:
|
||||||
|
- !type:ReagentThreshold
|
||||||
|
min: 1.5
|
||||||
|
clear: True
|
||||||
|
time: 5
|
||||||
|
|
||||||
- type: reagent
|
- type: reagent
|
||||||
id: CarbonDioxide
|
id: CarbonDioxide
|
||||||
@@ -148,7 +158,7 @@
|
|||||||
type: Plant
|
type: Plant
|
||||||
shouldHave: false
|
shouldHave: false
|
||||||
factor: -4
|
factor: -4
|
||||||
# Cant be added until I add metabolism effects on reagent removal
|
# We need a metabolism effect on reagent removal
|
||||||
#- !type:AdjustAlert
|
#- !type:AdjustAlert
|
||||||
# alertType: CarbonDioxide
|
# alertType: CarbonDioxide
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user