ECSatize AlertsSystem (#5559)
This commit is contained in:
16
Content.Shared/Alert/AlertCategory.cs
Normal file
16
Content.Shared/Alert/AlertCategory.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
/// <summary>
|
||||
/// Every category of alert. Corresponds to category field in alert prototypes defined in YML
|
||||
/// </summary>
|
||||
public enum AlertCategory
|
||||
{
|
||||
Pressure,
|
||||
Temperature,
|
||||
Breathing,
|
||||
Buckled,
|
||||
Health,
|
||||
Piloting,
|
||||
Hunger,
|
||||
Thirst
|
||||
}
|
||||
62
Content.Shared/Alert/AlertKey.cs
Normal file
62
Content.Shared/Alert/AlertKey.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
/// <summary>
|
||||
/// Key for an alert which is unique (for equality and hashcode purposes) w.r.t category semantics.
|
||||
/// I.e., entirely defined by the category, if a category was specified, otherwise
|
||||
/// falls back to the id.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public struct AlertKey : ISerializationHooks, IPopulateDefaultValues
|
||||
{
|
||||
public AlertType? AlertType { get; private set; }
|
||||
public readonly AlertCategory? AlertCategory;
|
||||
|
||||
/// NOTE: if the alert has a category you must pass the category for this to work
|
||||
/// properly as a key. I.e. if the alert has a category and you pass only the alert type, and you
|
||||
/// compare this to another AlertKey that has both the category and the same alert type, it will not consider them equal.
|
||||
public AlertKey(AlertType? alertType, AlertCategory? alertCategory)
|
||||
{
|
||||
AlertCategory = alertCategory;
|
||||
AlertType = alertType;
|
||||
}
|
||||
|
||||
public bool Equals(AlertKey other)
|
||||
{
|
||||
// compare only on alert category if we have one
|
||||
if (AlertCategory.HasValue)
|
||||
{
|
||||
return other.AlertCategory == AlertCategory;
|
||||
}
|
||||
|
||||
return AlertType == other.AlertType && AlertCategory == other.AlertCategory;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is AlertKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// use only alert category if we have one
|
||||
if (AlertCategory.HasValue) return AlertCategory.GetHashCode();
|
||||
return AlertType.GetHashCode();
|
||||
}
|
||||
|
||||
public void PopulateDefaultValues()
|
||||
{
|
||||
AlertType = Alert.AlertType.Error;
|
||||
}
|
||||
|
||||
/// <param name="category">alert category, must not be null</param>
|
||||
/// <returns>An alert key for the provided alert category. This must only be used for
|
||||
/// queries and never storage, as it is lacking an alert type.</returns>
|
||||
public static AlertKey ForCategory(AlertCategory category)
|
||||
{
|
||||
return new(null, category);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Alert
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to all configured alerts by alert type.
|
||||
/// </summary>
|
||||
public class AlertManager
|
||||
{
|
||||
[Dependency]
|
||||
private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private readonly Dictionary<AlertType, AlertPrototype> _typeToAlert = new();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
foreach (var alert in _prototypeManager.EnumeratePrototypes<AlertPrototype>())
|
||||
{
|
||||
if (!_typeToAlert.TryAdd(alert.AlertType, alert))
|
||||
{
|
||||
Logger.ErrorS("alert",
|
||||
"Found alert with duplicate alertType {0} - all alerts must have" +
|
||||
" a unique alerttype, this one will be skipped", alert.AlertType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the alert of the indicated type
|
||||
/// </summary>
|
||||
/// <returns>true if found</returns>
|
||||
public bool TryGet(AlertType alertType, [NotNullWhen(true)] out AlertPrototype? alert)
|
||||
{
|
||||
return _typeToAlert.TryGetValue(alertType, out alert);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using System.Globalization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -142,61 +141,4 @@ namespace Content.Shared.Alert
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Key for an alert which is unique (for equality and hashcode purposes) w.r.t category semantics.
|
||||
/// I.e., entirely defined by the category, if a category was specified, otherwise
|
||||
/// falls back to the id.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public struct AlertKey : ISerializationHooks, IPopulateDefaultValues
|
||||
{
|
||||
public AlertType? AlertType { get; private set; }
|
||||
public readonly AlertCategory? AlertCategory;
|
||||
|
||||
/// NOTE: if the alert has a category you must pass the category for this to work
|
||||
/// properly as a key. I.e. if the alert has a category and you pass only the alert type, and you
|
||||
/// compare this to another AlertKey that has both the category and the same alert type, it will not consider them equal.
|
||||
public AlertKey(AlertType? alertType, AlertCategory? alertCategory)
|
||||
{
|
||||
AlertCategory = alertCategory;
|
||||
AlertType = alertType;
|
||||
}
|
||||
|
||||
public bool Equals(AlertKey other)
|
||||
{
|
||||
// compare only on alert category if we have one
|
||||
if (AlertCategory.HasValue)
|
||||
{
|
||||
return other.AlertCategory == AlertCategory;
|
||||
}
|
||||
|
||||
return AlertType == other.AlertType && AlertCategory == other.AlertCategory;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is AlertKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// use only alert category if we have one
|
||||
if (AlertCategory.HasValue) return AlertCategory.GetHashCode();
|
||||
return AlertType.GetHashCode();
|
||||
}
|
||||
|
||||
public void PopulateDefaultValues()
|
||||
{
|
||||
AlertType = Alert.AlertType.Error;
|
||||
}
|
||||
|
||||
/// <param name="category">alert category, must not be null</param>
|
||||
/// <returns>An alert key for the provided alert category. This must only be used for
|
||||
/// queries and never storage, as it is lacking an alert type.</returns>
|
||||
public static AlertKey ForCategory(AlertCategory category)
|
||||
{
|
||||
return new(null, category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
Content.Shared/Alert/AlertState.cs
Normal file
12
Content.Shared/Alert/AlertState.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public struct AlertState
|
||||
{
|
||||
public short? Severity;
|
||||
public (TimeSpan, TimeSpan)? Cooldown;
|
||||
public AlertType Type;
|
||||
}
|
||||
16
Content.Shared/Alert/AlertSyncEvent.cs
Normal file
16
Content.Shared/Alert/AlertSyncEvent.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the AlertSystem needs alert sources to recalculate their alert states and set them.
|
||||
/// </summary>
|
||||
public class AlertSyncEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Euid { get; }
|
||||
|
||||
public AlertSyncEvent(EntityUid euid)
|
||||
{
|
||||
Euid = euid;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,5 @@
|
||||
namespace Content.Shared.Alert
|
||||
{
|
||||
/// <summary>
|
||||
/// Every category of alert. Corresponds to category field in alert prototypes defined in YML
|
||||
/// </summary>
|
||||
public enum AlertCategory
|
||||
{
|
||||
Pressure,
|
||||
Temperature,
|
||||
Breathing,
|
||||
Buckled,
|
||||
Health,
|
||||
Piloting,
|
||||
Hunger,
|
||||
Thirst
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Every kind of alert. Corresponds to alertType field in alert prototypes defined in YML
|
||||
/// NOTE: Using byte for a compact encoding when sending this in messages, can upgrade
|
||||
|
||||
18
Content.Shared/Alert/AlertsComponent.cs
Normal file
18
Content.Shared/Alert/AlertsComponent.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the icons on the right side of the screen.
|
||||
/// Should only be used for player-controlled entities.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent]
|
||||
[ComponentProtoName("Alerts")]
|
||||
public class AlertsComponent : Component
|
||||
{
|
||||
[ViewVariables] public Dictionary<AlertKey, AlertState> Alerts = new();
|
||||
}
|
||||
17
Content.Shared/Alert/AlertsComponentState.cs
Normal file
17
Content.Shared/Alert/AlertsComponentState.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class AlertsComponentState : ComponentState
|
||||
{
|
||||
public Dictionary<AlertKey, AlertState> Alerts;
|
||||
|
||||
public AlertsComponentState(Dictionary<AlertKey, AlertState> alerts)
|
||||
{
|
||||
Alerts = alerts;
|
||||
}
|
||||
}
|
||||
237
Content.Shared/Alert/AlertsSystem.cs
Normal file
237
Content.Shared/Alert/AlertsSystem.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
public abstract class AlertsSystem : EntitySystem
|
||||
{
|
||||
[Dependency]
|
||||
private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private readonly Dictionary<AlertType, AlertPrototype> _typeToAlert = new();
|
||||
|
||||
public IReadOnlyDictionary<AlertKey, AlertState>? GetActiveAlerts(EntityUid euid)
|
||||
{
|
||||
return EntityManager.TryGetComponent(euid, out AlertsComponent comp)
|
||||
? comp.Alerts
|
||||
: null;
|
||||
}
|
||||
|
||||
public bool IsShowingAlert(EntityUid euid, AlertType alertType)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent))
|
||||
return false;
|
||||
|
||||
if (TryGet(alertType, out var alert))
|
||||
{
|
||||
return alertsComponent.Alerts.ContainsKey(alert.AlertKey);
|
||||
}
|
||||
|
||||
Logger.DebugS("alert", "unknown alert type {0}", alertType);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <returns>true iff an alert of the indicated alert category is currently showing</returns>
|
||||
public bool IsShowingAlertCategory(EntityUid euid, AlertCategory alertCategory)
|
||||
{
|
||||
return EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent)
|
||||
&& alertsComponent.Alerts.ContainsKey(AlertKey.ForCategory(alertCategory));
|
||||
}
|
||||
|
||||
public bool TryGetAlertState(EntityUid euid, AlertKey key, out AlertState alertState)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent))
|
||||
return alertsComponent.Alerts.TryGetValue(key, out alertState);
|
||||
|
||||
alertState = default;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the alert. If the alert or another alert of the same category is already showing,
|
||||
/// it will be updated / replaced with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="euid"></param>
|
||||
/// <param name="alertType">type of the alert to set</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
|
||||
/// 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)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent))
|
||||
return;
|
||||
|
||||
if (TryGet(alertType, out var alert))
|
||||
{
|
||||
// Check whether the alert category we want to show is already being displayed, with the same type,
|
||||
// severity, and cooldown.
|
||||
if (alertsComponent.Alerts.TryGetValue(alert.AlertKey, out var alertStateCallback) &&
|
||||
alertStateCallback.Type == alertType &&
|
||||
alertStateCallback.Severity == severity &&
|
||||
alertStateCallback.Cooldown == cooldown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 };
|
||||
|
||||
AfterShowAlert(alertsComponent);
|
||||
|
||||
alertsComponent.Dirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS("alert", "Unable to show alert {0}, please ensure this alertType has" +
|
||||
" a corresponding YML alert prototype",
|
||||
alertType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the alert with the given category, if one is currently showing.
|
||||
/// </summary>
|
||||
public void ClearAlertCategory(EntityUid euid, AlertCategory category)
|
||||
{
|
||||
if(!EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent))
|
||||
return;
|
||||
|
||||
var key = AlertKey.ForCategory(category);
|
||||
if (!alertsComponent.Alerts.Remove(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AfterClearAlert(alertsComponent);
|
||||
|
||||
alertsComponent.Dirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the alert of the given type if it is currently showing.
|
||||
/// </summary>
|
||||
public void ClearAlert(EntityUid euid, AlertType alertType)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent))
|
||||
return;
|
||||
|
||||
if (TryGet(alertType, out var alert))
|
||||
{
|
||||
if (!alertsComponent.Alerts.Remove(alert.AlertKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AfterClearAlert(alertsComponent);
|
||||
|
||||
alertsComponent.Dirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS("alert", "unable to clear alert, unknown alertType {0}", alertType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after showing an alert prior to dirtying the component
|
||||
/// </summary>
|
||||
/// <param name="alertsComponent"></param>
|
||||
protected virtual void AfterShowAlert(AlertsComponent alertsComponent) { }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after clearing an alert prior to dirtying the component
|
||||
/// </summary>
|
||||
/// <param name="alertsComponent"></param>
|
||||
protected virtual void AfterClearAlert(AlertsComponent alertsComponent) { }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AlertsComponent, ComponentStartup>((uid, _, _) => RaiseLocalEvent(uid, new AlertSyncEvent(uid)));
|
||||
SubscribeLocalEvent<AlertsComponent, ComponentShutdown>((uid, _, _) => HandleComponentShutdown(uid));
|
||||
|
||||
SubscribeLocalEvent<AlertsComponent, ComponentGetState>(ClientAlertsGetState);
|
||||
SubscribeNetworkEvent<ClickAlertEvent>(HandleClickAlert);
|
||||
|
||||
LoadPrototypes();
|
||||
_prototypeManager.PrototypesReloaded += HandlePrototypesReloaded;
|
||||
}
|
||||
|
||||
protected virtual void HandleComponentShutdown(EntityUid uid)
|
||||
{
|
||||
RaiseLocalEvent(uid, new AlertSyncEvent(uid));
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_prototypeManager.PrototypesReloaded -= HandlePrototypesReloaded;
|
||||
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void HandlePrototypesReloaded(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
LoadPrototypes();
|
||||
}
|
||||
|
||||
protected virtual void LoadPrototypes()
|
||||
{
|
||||
_typeToAlert.Clear();
|
||||
foreach (var alert in _prototypeManager.EnumeratePrototypes<AlertPrototype>())
|
||||
{
|
||||
if (!_typeToAlert.TryAdd(alert.AlertType, alert))
|
||||
{
|
||||
Logger.ErrorS("alert",
|
||||
"Found alert with duplicate alertType {0} - all alerts must have" +
|
||||
" a unique alerttype, this one will be skipped", alert.AlertType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the alert of the indicated type
|
||||
/// </summary>
|
||||
/// <returns>true if found</returns>
|
||||
public bool TryGet(AlertType alertType, [NotNullWhen(true)] out AlertPrototype? alert)
|
||||
{
|
||||
return _typeToAlert.TryGetValue(alertType, out alert);
|
||||
}
|
||||
|
||||
private void HandleClickAlert(ClickAlertEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var player = args.SenderSession.AttachedEntity;
|
||||
if (player is null || !EntityManager.TryGetComponent<AlertsComponent>(player, out var alertComp)) return;
|
||||
|
||||
if (!IsShowingAlert(player.Value, msg.Type))
|
||||
{
|
||||
Logger.DebugS("alert", "user {0} attempted to" +
|
||||
" click alert {1} which is not currently showing for them",
|
||||
EntityManager.GetComponent<MetaDataComponent>(player.Value).EntityName, msg.Type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGet(msg.Type, out var alert))
|
||||
{
|
||||
Logger.WarningS("alert", "unrecognized encoded alert {0}", msg.Type);
|
||||
return;
|
||||
}
|
||||
|
||||
alert.OnClick?.AlertClicked(player.Value);
|
||||
}
|
||||
|
||||
private static void ClientAlertsGetState(EntityUid uid, AlertsComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new AlertsComponentState(component.Alerts);
|
||||
}
|
||||
}
|
||||
19
Content.Shared/Alert/ClickAlertEvent.cs
Normal file
19
Content.Shared/Alert/ClickAlertEvent.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
/// <summary>
|
||||
/// A message that calls the click interaction on a alert
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class ClickAlertEvent : EntityEventArgs
|
||||
{
|
||||
public readonly AlertType Type;
|
||||
|
||||
public ClickAlertEvent(AlertType alertType)
|
||||
{
|
||||
Type = alertType;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.Alert
|
||||
{
|
||||
@@ -11,25 +10,7 @@ namespace Content.Shared.Alert
|
||||
/// <summary>
|
||||
/// Invoked on server side when user clicks an alert.
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
void AlertClicked(ClickAlertEventArgs args);
|
||||
}
|
||||
|
||||
public class ClickAlertEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Player clicking the alert
|
||||
/// </summary>
|
||||
public readonly EntityUid Player;
|
||||
/// <summary>
|
||||
/// Alert that was clicked
|
||||
/// </summary>
|
||||
public readonly AlertPrototype Alert;
|
||||
|
||||
public ClickAlertEventArgs(EntityUid player, AlertPrototype alert)
|
||||
{
|
||||
Player = player;
|
||||
Alert = alert;
|
||||
}
|
||||
/// <param name="player"></param>
|
||||
void AlertClicked(EntityUid player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Alert
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles the icons on the right side of the screen.
|
||||
/// Should only be used for player-controlled entities.
|
||||
/// </summary>
|
||||
[NetworkedComponent()]
|
||||
public abstract class SharedAlertsComponent : Component
|
||||
{
|
||||
[Dependency]
|
||||
protected readonly AlertManager AlertManager = default!;
|
||||
|
||||
public override string Name => "Alerts";
|
||||
|
||||
[ViewVariables] private Dictionary<AlertKey, AlertState> _alerts = new();
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not AlertsComponentState state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_alerts = state.Alerts;
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new AlertsComponentState(_alerts);
|
||||
}
|
||||
|
||||
/// <returns>true iff an alert of the indicated alert category is currently showing</returns>
|
||||
public bool IsShowingAlertCategory(AlertCategory alertCategory)
|
||||
{
|
||||
return IsShowingAlert(AlertKey.ForCategory(alertCategory));
|
||||
}
|
||||
|
||||
/// <returns>true iff an alert of the indicated id is currently showing</returns>
|
||||
public bool IsShowingAlert(AlertType alertType)
|
||||
{
|
||||
if (AlertManager.TryGet(alertType, out var alert))
|
||||
{
|
||||
return IsShowingAlert(alert.AlertKey);
|
||||
}
|
||||
Logger.DebugS("alert", "unknown alert type {0}", alertType);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/// <returns>true iff an alert of the indicated key is currently showing</returns>
|
||||
protected bool IsShowingAlert(AlertKey alertKey)
|
||||
{
|
||||
return _alerts.ContainsKey(alertKey);
|
||||
}
|
||||
|
||||
protected IEnumerable<KeyValuePair<AlertKey, AlertState>> EnumerateAlertStates()
|
||||
{
|
||||
return _alerts;
|
||||
}
|
||||
|
||||
protected bool TryGetAlertState(AlertKey key, out AlertState alertState)
|
||||
{
|
||||
return _alerts.TryGetValue(key, out alertState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the alert. If the alert or another alert of the same category is already showing,
|
||||
/// it will be updated / replaced with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="alertType">type of the alert to set</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
|
||||
/// be erased if there is currently a cooldown for the alert)</param>
|
||||
public void ShowAlert(AlertType alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null)
|
||||
{
|
||||
if (AlertManager.TryGet(alertType, out var alert))
|
||||
{
|
||||
// Check whether the alert category we want to show is already being displayed, with the same type,
|
||||
// severity, and cooldown.
|
||||
if (_alerts.TryGetValue(alert.AlertKey, out var alertStateCallback) &&
|
||||
alertStateCallback.Type == alertType &&
|
||||
alertStateCallback.Severity == severity &&
|
||||
alertStateCallback.Cooldown == cooldown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// In the case we're changing the alert type but not the category, we need to remove it first.
|
||||
_alerts.Remove(alert.AlertKey);
|
||||
|
||||
_alerts[alert.AlertKey] = new AlertState
|
||||
{Cooldown = cooldown, Severity = severity, Type=alertType};
|
||||
|
||||
AfterShowAlert();
|
||||
|
||||
Dirty();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS("alert", "Unable to show alert {0}, please ensure this alertType has" +
|
||||
" a corresponding YML alert prototype",
|
||||
alertType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the alert with the given category, if one is currently showing.
|
||||
/// </summary>
|
||||
public void ClearAlertCategory(AlertCategory category)
|
||||
{
|
||||
var key = AlertKey.ForCategory(category);
|
||||
if (!_alerts.Remove(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AfterClearAlert();
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the alert of the given type if it is currently showing.
|
||||
/// </summary>
|
||||
public void ClearAlert(AlertType alertType)
|
||||
{
|
||||
if (AlertManager.TryGet(alertType, out var alert))
|
||||
{
|
||||
if (!_alerts.Remove(alert.AlertKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AfterClearAlert();
|
||||
|
||||
Dirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS("alert", "unable to clear alert, unknown alertType {0}", alertType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after showing an alert prior to dirtying the component
|
||||
/// </summary>
|
||||
protected virtual void AfterShowAlert() { }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after clearing an alert prior to dirtying the component
|
||||
/// </summary>
|
||||
protected virtual void AfterClearAlert() { }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class AlertsComponentState : ComponentState
|
||||
{
|
||||
public Dictionary<AlertKey, AlertState> Alerts;
|
||||
|
||||
public AlertsComponentState(Dictionary<AlertKey, AlertState> alerts)
|
||||
{
|
||||
Alerts = alerts;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A message that calls the click interaction on a alert
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
#pragma warning disable 618
|
||||
public class ClickAlertMessage : ComponentMessage
|
||||
#pragma warning restore 618
|
||||
{
|
||||
public readonly AlertType Type;
|
||||
|
||||
public ClickAlertMessage(AlertType alertType)
|
||||
{
|
||||
Directed = true;
|
||||
Type = alertType;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public struct AlertState
|
||||
{
|
||||
public short? Severity;
|
||||
public (TimeSpan, TimeSpan)? Cooldown;
|
||||
public AlertType Type;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user