ECSatize AlertsSystem (#5559)

This commit is contained in:
Acruid
2022-01-05 00:19:23 -08:00
committed by GitHub
parent 36d4de5e61
commit 5b1cd2dd96
59 changed files with 1069 additions and 1038 deletions

View 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
}

View 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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View 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;
}

View 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;
}
}

View File

@@ -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

View 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();
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using Content.Shared.Alert;
using Content.Shared.StatusEffect;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
@@ -58,10 +56,8 @@ namespace Content.Shared.Jittering
/// <param name="frequency">Frequency for jittering. See <see cref="MaxFrequency"/> and <see cref="MinFrequency"/>.</param>
/// <param name="forceValueChange">Whether to change any existing jitter value even if they're greater than the ones we're setting.</param>
/// <param name="status">The status effects component to modify.</param>
/// <param name="alerts">The alerts component.</param>
public void DoJitter(EntityUid uid, TimeSpan time, bool refresh, float amplitude = 10f, float frequency = 4f, bool forceValueChange = false,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
StatusEffectsComponent? status = null)
{
if (!Resolve(uid, ref status, false))
return;
@@ -69,7 +65,7 @@ namespace Content.Shared.Jittering
amplitude = Math.Clamp(amplitude, MinAmplitude, MaxAmplitude);
frequency = Math.Clamp(frequency, MinFrequency, MaxFrequency);
if (StatusEffects.TryAddStatusEffect<JitteringComponent>(uid, "Jitter", time, refresh, status, alerts))
if (StatusEffects.TryAddStatusEffect<JitteringComponent>(uid, "Jitter", time, refresh, status))
{
var jittering = EntityManager.GetComponent<JitteringComponent>(uid);

View File

@@ -10,7 +10,6 @@ using Content.Shared.MobState.State;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
@@ -69,10 +68,7 @@ namespace Content.Shared.MobState.Components
protected override void OnRemove()
{
if (_entMan.TryGetComponent(Owner, out SharedAlertsComponent? status))
{
status.ClearAlert(AlertType.HumanHealth);
}
EntitySystem.Get<AlertsSystem>().ClearAlert(Owner, AlertType.HumanHealth);
base.OnRemove();
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.Alert;
using Content.Shared.Alert;
using Content.Shared.Standing;
using Robust.Shared.GameObjects;
@@ -15,10 +15,7 @@ namespace Content.Shared.MobState.State
{
base.EnterState(uid, entityManager);
if (entityManager.TryGetComponent(uid, out SharedAlertsComponent? status))
{
status.ShowAlert(AlertType.HumanCrit); // TODO: combine humancrit-0 and humancrit-1 into a gif and display it
}
EntitySystem.Get<AlertsSystem>().ShowAlert(uid, AlertType.HumanCrit); // TODO: combine humancrit-0 and humancrit-1 into a gif and display it
EntitySystem.Get<StandingStateSystem>().Down(uid);

View File

@@ -14,6 +14,7 @@ namespace Content.Shared.Pulling.Systems
{
[Dependency] private readonly SharedPullingSystem _pullSystem = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifierSystem = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
public override void Initialize()
{
@@ -47,8 +48,7 @@ namespace Content.Shared.Pulling.Systems
if (args.Puller.Owner != uid)
return;
if (EntityManager.TryGetComponent(component.Owner, out SharedAlertsComponent? alerts))
alerts.ShowAlert(AlertType.Pulling);
_alertsSystem.ShowAlert(component.Owner, AlertType.Pulling);
RefreshMovementSpeed(component);
}
@@ -61,8 +61,8 @@ namespace Content.Shared.Pulling.Systems
if (args.Puller.Owner != uid)
return;
if (EntityManager.TryGetComponent(component.Owner, out SharedAlertsComponent? alerts))
alerts.ClearAlert(AlertType.Pulling);
var euid = component.Owner;
_alertsSystem.ClearAlert(euid, AlertType.Pulling);
RefreshMovementSpeed(component);
}

View File

@@ -25,6 +25,7 @@ namespace Content.Shared.Pulling
public abstract partial class SharedPullingSystem : EntitySystem
{
[Dependency] private readonly SharedPullingStateManagementSystem _pullSm = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
/// <summary>
/// A mapping of pullers to the entity that they are pulling.
@@ -105,9 +106,8 @@ namespace Content.Shared.Pulling
{
if (args.Pulled.Owner != uid)
return;
if (EntityManager.TryGetComponent(component.Owner, out SharedAlertsComponent? alerts))
alerts.ShowAlert(AlertType.Pulled);
_alertsSystem.ShowAlert(component.Owner, AlertType.Pulled);
}
private void PullableHandlePullStopped(EntityUid uid, SharedPullableComponent component, PullStoppedMessage args)
@@ -115,8 +115,7 @@ namespace Content.Shared.Pulling
if (args.Pulled.Owner != uid)
return;
if (EntityManager.TryGetComponent(component.Owner, out SharedAlertsComponent? alerts))
alerts.ClearAlert(AlertType.Pulled);
_alertsSystem.ClearAlert(component.Owner, AlertType.Pulled);
}
public override void Update(float frameTime)

View File

@@ -1,5 +1,4 @@
using System;
using Content.Shared.Alert;
using Content.Shared.StatusEffect;
using Robust.Shared.GameObjects;
@@ -8,7 +7,7 @@ namespace Content.Shared.Speech.EntitySystems
public abstract class SharedStutteringSystem : EntitySystem
{
// For code in shared... I imagine we ain't getting accent prediction anytime soon so let's not bother.
public virtual void DoStutter(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null, SharedAlertsComponent? alerts = null)
public virtual void DoStutter(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null)
{
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Alert;
using Robust.Shared.GameObjects;
@@ -15,6 +15,7 @@ namespace Content.Shared.StatusEffect
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
public override void Initialize()
{
@@ -80,20 +81,16 @@ namespace Content.Shared.StatusEffect
/// <param name="time">How long the effect should last for.</param>
/// <param name="refresh">The status effect cooldown should be refreshed (true) or accumulated (false).</param>
/// <param name="status">The status effects component to change, if you already have it.</param>
/// <param name="alerts">The alerts component to modify, if the status effect has an alert.</param>
/// <returns>False if the effect could not be added or the component already exists, true otherwise.</returns>
/// <typeparam name="T">The component type to add and remove from the entity.</typeparam>
public bool TryAddStatusEffect<T>(EntityUid uid, string key, TimeSpan time, bool refresh,
StatusEffectsComponent? status=null,
SharedAlertsComponent? alerts=null)
StatusEffectsComponent? status = null)
where T: Component, new()
{
if (!Resolve(uid, ref status, false))
return false;
Resolve(uid, ref alerts, false);
if (TryAddStatusEffect(uid, key, time, refresh, status, alerts))
if (TryAddStatusEffect(uid, key, time, refresh, status))
{
// If they already have the comp, we just won't bother updating anything.
if (!EntityManager.HasComponent<T>(uid))
@@ -108,15 +105,12 @@ namespace Content.Shared.StatusEffect
}
public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, string component,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
StatusEffectsComponent? status = null)
{
if (!Resolve(uid, ref status, false))
return false;
Resolve(uid, ref alerts, false);
if (TryAddStatusEffect(uid, key, time, refresh, status, alerts))
if (TryAddStatusEffect(uid, key, time, refresh, status))
{
// If they already have the comp, we just won't bother updating anything.
if (!EntityManager.HasComponent(uid, _componentFactory.GetRegistration(component).Type))
@@ -142,26 +136,22 @@ namespace Content.Shared.StatusEffect
/// <param name="time">How long the effect should last for.</param>
/// <param name="refresh">The status effect cooldown should be refreshed (true) or accumulated (false).</param>
/// <param name="status">The status effects component to change, if you already have it.</param>
/// <param name="alerts">The alerts component to modify, if the status effect has an alert.</param>
/// <returns>False if the effect could not be added, or if the effect already existed.</returns>
/// <remarks>
/// This obviously does not add any actual 'effects' on its own. Use the generic overload,
/// which takes in a component type, if you want to automatically add and remove a component.
///
///
/// If the effect already exists, it will simply replace the cooldown with the new one given.
/// If you want special 'effect merging' behavior, do it your own damn self!
/// </remarks>
public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh,
StatusEffectsComponent? status=null,
SharedAlertsComponent? alerts=null)
StatusEffectsComponent? status=null)
{
if (!Resolve(uid, ref status, false))
return false;
if (!CanApplyEffect(uid, key, status))
return false;
Resolve(uid, ref alerts, false);
// we already checked if it has the index in CanApplyEffect so a straight index and not tryindex here
// is fine
var proto = _prototypeManager.Index<StatusEffectPrototype>(key);
@@ -191,9 +181,10 @@ namespace Content.Shared.StatusEffect
status.ActiveEffects.Add(key, new StatusEffectState(cooldown, refresh, null));
}
if (proto.Alert != null && alerts != null)
if (proto.Alert != null)
{
alerts.ShowAlert(proto.Alert.Value, cooldown: GetAlertCooldown(uid, proto.Alert.Value, status));
var cooldown1 = GetAlertCooldown(uid, proto.Alert.Value, status);
_alertsSystem.ShowAlert(uid, proto.Alert.Value, null, cooldown1);
}
status.Dirty();
@@ -233,15 +224,13 @@ namespace Content.Shared.StatusEffect
/// <param name="uid">The entity to remove an effect from.</param>
/// <param name="key">The effect ID to remove.</param>
/// <param name="status">The status effects component to change, if you already have it.</param>
/// <param name="alerts">The alerts component to modify, if the status effect has an alert.</param>
/// <returns>False if the effect could not be removed, true otherwise.</returns>
/// <remarks>
/// Obviously this doesn't automatically clear any effects a status effect might have.
/// That's up to the removed component to handle itself when it's removed.
/// </remarks>
public bool TryRemoveStatusEffect(EntityUid uid, string key,
StatusEffectsComponent? status=null,
SharedAlertsComponent? alerts=null)
StatusEffectsComponent? status=null)
{
if (!Resolve(uid, ref status, false))
return false;
@@ -250,8 +239,6 @@ namespace Content.Shared.StatusEffect
if (!_prototypeManager.TryIndex<StatusEffectPrototype>(key, out var proto))
return false;
Resolve(uid, ref alerts, false);
var state = status.ActiveEffects[key];
// There are cases where a status effect component might be server-only, so TryGetRegistration...
@@ -267,9 +254,9 @@ namespace Content.Shared.StatusEffect
EntityManager.RemoveComponent(uid, type);
}
if (proto.Alert != null && alerts != null)
if (proto.Alert != null)
{
alerts.ClearAlert(proto.Alert.Value);
_alertsSystem.ClearAlert(uid, proto.Alert.Value);
}
status.ActiveEffects.Remove(key);
@@ -284,21 +271,17 @@ namespace Content.Shared.StatusEffect
/// </summary>
/// <param name="uid">The entity to remove effects from.</param>
/// <param name="status">The status effects component to change, if you already have it.</param>
/// <param name="alerts">The alerts component to modify, if the status effect has an alert.</param>
/// <returns>False if any status effects failed to be removed, true if they all did.</returns>
public bool TryRemoveAllStatusEffects(EntityUid uid,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
StatusEffectsComponent? status = null)
{
if (!Resolve(uid, ref status, false))
return false;
Resolve(uid, ref alerts, false);
bool failed = false;
foreach (var effect in status.ActiveEffects)
{
if(!TryRemoveStatusEffect(uid, effect.Key, status, alerts))
if(!TryRemoveStatusEffect(uid, effect.Key, status))
failed = true;
}
@@ -350,14 +333,11 @@ namespace Content.Shared.StatusEffect
/// <param name="time">The amount of time to add.</param>
/// <param name="status">The status effect component, should you already have it.</param>
public bool TryAddTime(EntityUid uid, string key, TimeSpan time,
StatusEffectsComponent? status=null,
SharedAlertsComponent? alert=null)
StatusEffectsComponent? status=null)
{
if (!Resolve(uid, ref status, false))
return false;
Resolve(uid, ref alert, false);
if (!HasStatusEffect(uid, key, status))
return false;
@@ -366,11 +346,10 @@ namespace Content.Shared.StatusEffect
status.ActiveEffects[key].Cooldown = timer;
if (_prototypeManager.TryIndex<StatusEffectPrototype>(key, out var proto)
&& alert != null
&& proto.Alert != null)
{
alert.ShowAlert(proto.Alert.Value, cooldown: GetAlertCooldown(uid, proto.Alert.Value, status));
(TimeSpan, TimeSpan)? cooldown = GetAlertCooldown(uid, proto.Alert.Value, status);
_alertsSystem.ShowAlert(uid, proto.Alert.Value, null, cooldown);
}
return true;
@@ -384,14 +363,11 @@ namespace Content.Shared.StatusEffect
/// <param name="time">The amount of time to add.</param>
/// <param name="status">The status effect component, should you already have it.</param>
public bool TryRemoveTime(EntityUid uid, string key, TimeSpan time,
StatusEffectsComponent? status=null,
SharedAlertsComponent? alert=null)
StatusEffectsComponent? status=null)
{
if (!Resolve(uid, ref status, false))
return false;
Resolve(uid, ref alert, false);
if (!HasStatusEffect(uid, key, status))
return false;
@@ -405,11 +381,10 @@ namespace Content.Shared.StatusEffect
status.ActiveEffects[key].Cooldown = timer;
if (_prototypeManager.TryIndex<StatusEffectPrototype>(key, out var proto)
&& alert != null
&& proto.Alert != null)
{
alert.ShowAlert(proto.Alert.Value, cooldown: GetAlertCooldown(uid, proto.Alert.Value, status));
(TimeSpan, TimeSpan)? cooldown = GetAlertCooldown(uid, proto.Alert.Value, status);
_alertsSystem.ShowAlert(uid, proto.Alert.Value, null, cooldown);
}
return true;

View File

@@ -1,5 +1,4 @@
using System;
using Content.Shared.Alert;
using Content.Shared.Audio;
using Content.Shared.DragDrop;
using Content.Shared.Interaction;
@@ -119,8 +118,7 @@ namespace Content.Shared.Stunnable
/// Stuns the entity, disallowing it from doing many interactions temporarily.
/// </summary>
public bool TryStun(EntityUid uid, TimeSpan time, bool refresh,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
StatusEffectsComponent? status = null)
{
if (time <= TimeSpan.Zero)
return false;
@@ -128,17 +126,14 @@ namespace Content.Shared.Stunnable
if (!Resolve(uid, ref status, false))
return false;
Resolve(uid, ref alerts, false);
return _statusEffectSystem.TryAddStatusEffect<StunnedComponent>(uid, "Stun", time, refresh, alerts: alerts);
return _statusEffectSystem.TryAddStatusEffect<StunnedComponent>(uid, "Stun", time, refresh);
}
/// <summary>
/// Knocks down the entity, making it fall to the ground.
/// </summary>
public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
StatusEffectsComponent? status = null)
{
if (time <= TimeSpan.Zero)
return false;
@@ -146,25 +141,19 @@ namespace Content.Shared.Stunnable
if (!Resolve(uid, ref status, false))
return false;
Resolve(uid, ref alerts, false);
return _statusEffectSystem.TryAddStatusEffect<KnockedDownComponent>(uid, "KnockedDown", time, refresh, alerts: alerts);
return _statusEffectSystem.TryAddStatusEffect<KnockedDownComponent>(uid, "KnockedDown", time, refresh);
}
/// <summary>
/// Applies knockdown and stun to the entity temporarily.
/// </summary>
public bool TryParalyze(EntityUid uid, TimeSpan time, bool refresh,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
StatusEffectsComponent? status = null)
{
if (!Resolve(uid, ref status))
return false;
// Optional component.
Resolve(uid, ref alerts, false);
return TryKnockdown(uid, time, refresh, status, alerts) && TryStun(uid, time, refresh, status, alerts);
return TryKnockdown(uid, time, refresh, status) && TryStun(uid, time, refresh, status);
}
/// <summary>
@@ -172,19 +161,15 @@ namespace Content.Shared.Stunnable
/// </summary>
public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh,
float walkSpeedMultiplier = 1f, float runSpeedMultiplier = 1f,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
StatusEffectsComponent? status = null)
{
if (!Resolve(uid, ref status))
return false;
// "Optional" component.
Resolve(uid, ref alerts, false);
if (time <= TimeSpan.Zero)
return false;
if (_statusEffectSystem.TryAddStatusEffect<SlowedDownComponent>(uid, "SlowedDown", time, refresh, status, alerts))
if (_statusEffectSystem.TryAddStatusEffect<SlowedDownComponent>(uid, "SlowedDown", time, refresh, status))
{
var slowed = EntityManager.GetComponent<SlowedDownComponent>(uid);
// Doesn't make much sense to have the "TrySlowdown" method speed up entities now does it?