[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ private EntityQuery<AlertsComponent> _alertsQuery;
private FrozenDictionary<ProtoId<AlertPrototype>, AlertPrototype> _typeToAlert = default!;
- public IReadOnlyDictionary<AlertKey, AlertState>? GetActiveAlerts(EntityUid euid)
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _alertsQuery = GetEntityQuery<AlertsComponent>();
+
+ SubscribeLocalEvent<AlertsComponent, ComponentStartup>(HandleComponentStartup);
+ SubscribeLocalEvent<AlertsComponent, ComponentShutdown>(HandleComponentShutdown);
+ SubscribeLocalEvent<AlertsComponent, PlayerAttachedEvent>(OnPlayerAttached);
+
+ SubscribeLocalEvent<AlertAutoRemoveComponent, EntityUnpausedEvent>(OnAutoRemoveUnPaused);
+
+ SubscribeAllEvent<ClickAlertEvent>(HandleClickAlert);
+ SubscribeLocalEvent<PrototypesReloadedEventArgs>(HandlePrototypesReloaded);
+ LoadPrototypes();
+ }
+
+ public override void Update(float frameTime)
{
- return TryComp(euid, out AlertsComponent? comp)
- ? comp.Alerts
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator<AlertAutoRemoveComponent>();
+ var curTime = _timing.CurTime;
+ while (query.MoveNext(out var uid, out var autoComp))
+ {
+ var removed = false;
+ if (autoComp.AlertKeys.Count <= 0 || !_alertsQuery.TryComp(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.endTime >= curTime)
+ continue;
+
+ removeList.Add(alertKey);
+ alertComp.Alerts.Remove(alertKey);
+ removed = true;
+ }
+
+ if (!removed)
+ continue;
+
+ foreach (var alertKey in removeList)
+ {
+ autoComp.AlertKeys.Remove(alertKey);
+ }
+
+ Dirty(uid, alertComp);
+ Dirty(uid, autoComp);
+ }
+ }
+
+ public IReadOnlyDictionary<AlertKey, AlertState>? GetActiveAlerts(Entity<AlertsComponent?> entity)
+ {
+ return _alertsQuery.Resolve(entity, ref entity.Comp, false)
+ ? entity.Comp.Alerts
: null;
}
public short GetSeverityRange(ProtoId<AlertPrototype> alertType)
{
var minSeverity = _typeToAlert[alertType].MinSeverity;
- return (short)MathF.Max(minSeverity,_typeToAlert[alertType].MaxSeverity - minSeverity);
+ return (short)MathF.Max(minSeverity, _typeToAlert[alertType].MaxSeverity - minSeverity);
}
public short GetMaxSeverity(ProtoId<AlertPrototype> alertType)
return _typeToAlert[alertType].MinSeverity;
}
- public bool IsShowingAlert(EntityUid euid, ProtoId<AlertPrototype> alertType)
+ public bool IsShowingAlert(Entity<AlertsComponent?> entity, ProtoId<AlertPrototype> alertType)
{
- if (!TryComp(euid, out AlertsComponent? alertsComponent))
+ if (!_alertsQuery.Resolve(entity, ref entity.Comp, false))
return false;
if (TryGet(alertType, out var alert))
- {
- return alertsComponent.Alerts.ContainsKey(alert.AlertKey);
- }
+ return entity.Comp.Alerts.ContainsKey(alert.AlertKey);
- Log.Debug("Unknown alert type {0}", alertType);
+ Log.Debug($"Unknown alert type {alertType}");
return false;
}
/// <returns>true iff an alert of the indicated alert category is currently showing</returns>
- public bool IsShowingAlertCategory(EntityUid euid, ProtoId<AlertCategoryPrototype> alertCategory)
+ public bool IsShowingAlertCategory(Entity<AlertsComponent?> entity, ProtoId<AlertCategoryPrototype> alertCategory)
{
- return TryComp(euid, out AlertsComponent? alertsComponent)
- && alertsComponent.Alerts.ContainsKey(AlertKey.ForCategory(alertCategory));
+ return _alertsQuery.Resolve(entity, ref entity.Comp, false)
+ && entity.Comp.Alerts.ContainsKey(AlertKey.ForCategory(alertCategory));
}
- public bool TryGetAlertState(EntityUid euid, AlertKey key, out AlertState alertState)
+ public bool TryGetAlertState(Entity<AlertsComponent?> entity, AlertKey key, out AlertState alertState)
{
- if (TryComp(euid, out AlertsComponent? alertsComponent))
- return alertsComponent.Alerts.TryGetValue(key, out alertState);
+ if (_alertsQuery.Resolve(entity, ref entity.Comp, false))
+ return entity.Comp.Alerts.TryGetValue(key, out alertState);
alertState = default;
return false;
/// 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="entity">The entity who we are showing the alert for.</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>
/// <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, ProtoId<AlertPrototype> alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null, bool autoRemove = false, bool showCooldown = true )
+ public void ShowAlert(Entity<AlertsComponent?> entity,
+ ProtoId<AlertPrototype> alertType,
+ short? severity = null,
+ (TimeSpan, TimeSpan)? cooldown = null,
+ bool autoRemove = false,
+ bool showCooldown = true )
+ {
+ ShowAlert(entity, new AlertState { Type = alertType, Severity = severity, Cooldown = cooldown, AutoRemove = autoRemove, ShowCooldown = showCooldown});
+ }
+
+ public void ShowAlert(Entity<AlertsComponent?> entity, AlertState state)
{
// This should be handled as part of networking.
if (_timing.ApplyingState)
return;
- if (!TryComp(euid, out AlertsComponent? alertsComponent))
+ if (!_alertsQuery.Resolve(entity, ref entity.Comp, false))
return;
- if (TryGet(alertType, out var alert))
+ if (!TryGet(state.Type, 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 &&
- alertStateCallback.AutoRemove == autoRemove &&
- alertStateCallback.ShowCooldown == showCooldown)
- {
- return;
- }
+ Log.Error($"Unable to show alert {state.Type}, please ensure this alertType has a corresponding YML alert prototype");
+ 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);
+ // Check whether the alert category we want to show is already being displayed, with the same type,
+ // severity, and cooldown.
+ if (entity.Comp.Alerts.TryGetValue(alert.AlertKey, out var alertStateCallback))
+ {
+ if (state == alertStateCallback)
+ return;
- var state = new AlertState
- { Cooldown = cooldown, Severity = severity, Type = alertType, AutoRemove = autoRemove, ShowCooldown = showCooldown};
- alertsComponent.Alerts[alert.AlertKey] = state;
+ // If the alert exists and we're updating it, we need to remove it first before adding it back.
+ entity.Comp.Alerts.Remove(alert.AlertKey);
+ }
- // 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);
- }
+ entity.Comp.Alerts.Add(alert.AlertKey, state);
- AfterShowAlert((euid, alertsComponent));
+ // Keeping a list of AutoRemove alerts, so Update() doesn't need to check every alert
+ if (state.AutoRemove)
+ {
+ EnsureComp<AlertAutoRemoveComponent>(entity, out var autoComp);
- Dirty(euid, alertsComponent);
+ if (autoComp.AlertKeys.Add(alert.AlertKey))
+ Dirty (entity, autoComp);
}
- else
+
+ AfterShowAlert((entity, entity.Comp));
+
+ Dirty(entity);
+ }
+
+ /// <summary>
+ /// An alternative to show alert with different behavior if an alert already exists.
+ /// </summary>
+ /// <param name="entity">Entity whose alert we're updating</param>
+ /// <param name="alertType">Prototype of the alert we're updating</param>
+ /// <param name="severity">Severity we're setting the alert to</param>
+ /// <param name="cooldown">Time left in the alert.</param>
+ /// <param name="autoRemove">Do we want to remove this alert when it expires?</param>
+ /// <param name="showCooldown">Should we show/hide the cooldown?</param>
+ public void UpdateAlert(Entity<AlertsComponent?> entity,
+ ProtoId<AlertPrototype> alertType,
+ short? severity = null,
+ TimeSpan? cooldown = null,
+ bool autoRemove = false,
+ bool showCooldown = true)
+ {
+ if (_timing.ApplyingState)
+ return;
+
+ if (!_alertsQuery.Resolve(entity, ref entity.Comp, false))
+ return;
+
+ if (!TryGet(alertType, out var alert))
+ return;
+
+ if (cooldown == null)
{
- Log.Error("Unable to show alert {0}, please ensure this alertType has" +
- " a corresponding YML alert prototype",
- alertType);
+ ShowAlert(entity, alertType, severity, null, autoRemove, showCooldown);
+ return;
}
+
+ // Keep the progress duration the same but only if we're removing time.
+ // If the next cooldown is greater than our previous one we should reset the timer
+ TryGetAlertState(entity, alert.AlertKey, out var alertState);
+ var down = alertState.Cooldown?.endTime < cooldown.Value
+ ? (_timing.CurTime, cooldown.Value)
+ : (alertState.Cooldown?.startTime ?? _timing.CurTime, cooldown.Value);
+
+ ShowAlert(entity, alertType, severity, down, autoRemove, showCooldown);
}
/// <summary>
/// Clear the alert with the given category, if one is currently showing.
/// </summary>
- public void ClearAlertCategory(EntityUid euid, ProtoId<AlertCategoryPrototype> category)
+ public void ClearAlertCategory(Entity<AlertsComponent?> entity, ProtoId<AlertCategoryPrototype> category)
{
- if(!TryComp(euid, out AlertsComponent? alertsComponent))
+ if(!_alertsQuery.Resolve(entity, ref entity.Comp, false))
return;
var key = AlertKey.ForCategory(category);
- if (!alertsComponent.Alerts.Remove(key))
+ if (!entity.Comp.Alerts.Remove(key))
{
return;
}
- AfterClearAlert((euid, alertsComponent));
+ AfterClearAlert((entity, entity.Comp));
- Dirty(euid, alertsComponent);
+ Dirty(entity);
}
/// <summary>
/// Clear the alert of the given type if it is currently showing.
/// </summary>
- public void ClearAlert(EntityUid euid, ProtoId<AlertPrototype> alertType)
+ public void ClearAlert(Entity<AlertsComponent?> entity, ProtoId<AlertPrototype> alertType)
{
if (_timing.ApplyingState)
return;
- if (!TryComp(euid, out AlertsComponent? alertsComponent))
+ if (!_alertsQuery.Resolve(entity, ref entity.Comp, false))
return;
if (TryGet(alertType, out var alert))
{
- if (!alertsComponent.Alerts.Remove(alert.AlertKey))
+ if (!entity.Comp.Alerts.Remove(alert.AlertKey))
{
return;
}
- AfterClearAlert((euid, alertsComponent));
+ AfterClearAlert((entity, entity.Comp));
- Dirty(euid, alertsComponent);
+ Dirty(entity);
}
else
{
- Log.Error("Unable to clear alert, unknown alertType {0}", alertType);
+ Log.Error($"Unable to clear alert, unknown alertType {alertType}");
}
}
/// </summary>
protected virtual void AfterClearAlert(Entity<AlertsComponent> alerts) { }
- public override void Initialize()
+ private void OnAutoRemoveUnPaused(Entity<AlertAutoRemoveComponent> entity, ref EntityUnpausedEvent args)
{
- base.Initialize();
-
- SubscribeLocalEvent<AlertsComponent, ComponentStartup>(HandleComponentStartup);
- SubscribeLocalEvent<AlertsComponent, ComponentShutdown>(HandleComponentShutdown);
- SubscribeLocalEvent<AlertsComponent, PlayerAttachedEvent>(OnPlayerAttached);
-
- SubscribeLocalEvent<AlertAutoRemoveComponent, EntityUnpausedEvent>(OnAutoRemoveUnPaused);
-
- SubscribeAllEvent<ClickAlertEvent>(HandleClickAlert);
- SubscribeLocalEvent<PrototypesReloadedEventArgs>(HandlePrototypesReloaded);
- LoadPrototypes();
- }
-
- private void OnAutoRemoveUnPaused(EntityUid uid, AlertAutoRemoveComponent comp, EntityUnpausedEvent args)
- {
- if (!TryComp<AlertsComponent>(uid, out var alertComp))
- {
+ if (!_alertsQuery.TryComp(entity, out var alertComp))
return;
- }
var dirty = false;
if (alert.Value.Cooldown is null)
continue;
- var cooldown = (alert.Value.Cooldown.Value.Item1, alert.Value.Cooldown.Value.Item2 + args.PausedTime);
+ var (start, end) = alert.Value.Cooldown.Value;
+ var cooldown = (start, end + args.PausedTime);
- var state = new AlertState
- {
- Severity = alert.Value.Severity,
- Cooldown = cooldown,
- ShowCooldown = alert.Value.ShowCooldown,
- AutoRemove = alert.Value.AutoRemove,
- Type = alert.Value.Type
- };
+ var state = alert.Value with { Cooldown = cooldown };
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);
- }
+ Dirty(entity, alertComp);
}
protected virtual void HandleComponentShutdown(EntityUid uid, AlertsComponent component, ComponentShutdown args)
foreach (var alert in _prototypeManager.EnumeratePrototypes<AlertPrototype>())
{
if (!dict.TryAdd(alert.ID, alert))
- {
- Log.Error("Found alert with duplicate alertType {0} - all alerts must have" +
- " a unique alertType, this one will be skipped", alert.ID);
- }
+ Log.Error($"Found alert with duplicate alertType {alert.ID} - all alerts must have a unique alertType, this one will be skipped");
}
_typeToAlert = dict.ToFrozenDictionary();
if (!IsShowingAlert(player.Value, msg.Type))
{
- Log.Debug("User {0} attempted to" +
- " click alert {1} which is not currently showing for them",
- Comp<MetaDataComponent>(player.Value).EntityName, msg.Type);
+ Log.Debug($"User {ToPrettyString(player.Value)} attempted to click alert {msg.Type} which is not currently showing for them");
return;
}
if (!TryGet(msg.Type, out var alert))
{
- Log.Warning("Unrecognized encoded alert {0}", msg.Type);
+ Log.Warning($"Unrecognized encoded alert {msg.Type}");
return;
}