using System; using System.Collections.Generic; using System.Linq; using Content.Shared.Alert; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Serialization; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Content.Shared.GameObjects.Components.Mobs { /// /// Handles the icons on the right side of the screen. /// Should only be used for player-controlled entities. /// public abstract class SharedAlertsComponent : Component { private static readonly AlertState[] NO_ALERTS = new AlertState[0]; [Dependency] protected readonly AlertManager AlertManager = default!; public override string Name => "AlertsUI"; public override uint? NetID => ContentNetIDs.ALERTS; [ViewVariables] private Dictionary _alerts = new Dictionary(); /// true iff an alert of the indicated alert category is currently showing public bool IsShowingAlertCategory(AlertCategory alertCategory) { return IsShowingAlert(AlertKey.ForCategory(alertCategory)); } /// true iff an alert of the indicated id is currently showing 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; } /// true iff an alert of the indicated key is currently showing protected bool IsShowingAlert(AlertKey alertKey) { return _alerts.ContainsKey(alertKey); } protected IEnumerable EnumerateAlertStates() { return _alerts.Values.Select(alertData => alertData.AlertState); } /// /// Invokes the alert's specified callback if there is one. /// Not intended to be used on clientside. /// protected void PerformAlertClickCallback(AlertPrototype alert, IEntity owner) { if (_alerts.TryGetValue(alert.AlertKey, out var alertStateCallback)) { alertStateCallback.OnClickAlert?.Invoke(new ClickAlertEventArgs(owner, alert)); } else { Logger.DebugS("alert", "player {0} attempted to invoke" + " alert click for {1} but that alert is not currently" + " showing", owner.Name, alert.AlertType); } } /// /// Creates a new array containing all of the current alert states. /// /// protected AlertState[] CreateAlertStatesArray() { if (_alerts.Count == 0) return NO_ALERTS; var states = new AlertState[_alerts.Count]; // because I don't trust LINQ var idx = 0; foreach (var alertData in _alerts.Values) { states[idx++] = alertData.AlertState; } return states; } protected bool TryGetAlertState(AlertKey key, out AlertState alertState) { if (_alerts.TryGetValue(key, out var alertData)) { alertState = alertData.AlertState; return true; } alertState = default; return false; } /// /// Replace the current active alerts with the specified alerts. Any /// OnClickAlert callbacks on the active alerts will be erased. /// protected void SetAlerts(AlertState[] alerts) { var newAlerts = new Dictionary(); foreach (var alertState in alerts) { if (AlertManager.TryDecode(alertState.AlertEncoded, out var alert)) { newAlerts[alert.AlertKey] = new ClickableAlertState { AlertState = alertState }; } else { Logger.ErrorS("alert", "unrecognized encoded alert {0}", alertState.AlertEncoded); } } _alerts = newAlerts; } /// /// 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. /// /// type of the alert to set /// callback to invoke when ClickAlertMessage is received by the server /// after being clicked by client. Has no effect when specified on the clientside. /// 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(AlertType alertType, short? severity = null, OnClickAlert onClickAlert = null, ValueTuple? cooldown = null) { if (AlertManager.TryGetWithEncoded(alertType, out var alert, out var encoded)) { if (_alerts.TryGetValue(alert.AlertKey, out var alertStateCallback) && alertStateCallback.AlertState.AlertEncoded == encoded && alertStateCallback.AlertState.Severity == severity && alertStateCallback.AlertState.Cooldown == cooldown) { alertStateCallback.OnClickAlert = onClickAlert; return; } _alerts[alert.AlertKey] = new ClickableAlertState { AlertState = new AlertState {Cooldown = cooldown, AlertEncoded = encoded, Severity = severity}, OnClickAlert = onClickAlert }; Dirty(); } else { Logger.ErrorS("alert", "Unable to show alert {0}, please ensure this alertType has" + " a corresponding YML alert prototype", alertType); } } /// /// Clear the alert with the given category, if one is currently showing. /// public void ClearAlertCategory(AlertCategory category) { var key = AlertKey.ForCategory(category); if (!_alerts.Remove(key)) { return; } AfterClearAlert(); Dirty(); } /// /// Clear the alert of the given type if it is currently showing. /// 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); } } /// /// Invoked after clearing an alert prior to dirtying the control /// protected virtual void AfterClearAlert() { } } [Serializable, NetSerializable] public class AlertsComponentState : ComponentState { public AlertState[] Alerts; public AlertsComponentState(AlertState[] alerts) : base(ContentNetIDs.ALERTS) { Alerts = alerts; } } /// /// A message that calls the click interaction on a alert /// [Serializable, NetSerializable] public class ClickAlertMessage : ComponentMessage { public readonly byte EncodedAlert; public ClickAlertMessage(byte encodedAlert) { Directed = true; EncodedAlert = encodedAlert; } } [Serializable, NetSerializable] public struct AlertState { public byte AlertEncoded; public short? Severity; public ValueTuple? Cooldown; } public struct ClickableAlertState { public AlertState AlertState; public OnClickAlert OnClickAlert; } public delegate void OnClickAlert(ClickAlertEventArgs args); public class ClickAlertEventArgs : EventArgs { /// /// Player clicking the alert /// public readonly IEntity Player; /// /// Alert that was clicked /// public readonly AlertPrototype Alert; public ClickAlertEventArgs(IEntity player, AlertPrototype alert) { Player = player; Alert = alert; } } }