]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
refactor: rework the new status effect system to use containers (#38915) (#38943)
authorFildrance <fildrance@gmail.com>
Sat, 12 Jul 2025 17:53:08 +0000 (20:53 +0300)
committerGitHub <noreply@github.com>
Sat, 12 Jul 2025 17:53:08 +0000 (19:53 +0200)
Co-authored-by: Perry Fraser <perryprog@users.noreply.github.com>
18 files changed:
Content.Client/Drowsiness/DrowsinessOverlay.cs
Content.Client/Drowsiness/DrowsinessSystem.cs
Content.Client/Drugs/RainbowOverlay.cs
Content.Client/StatusEffectNew/ClientStatusEffectsSystem.cs [deleted file]
Content.Server/Drowsiness/DrowsinessSystem.cs
Content.Server/StatusEffectNew/StatusEffectsSystem.cs [deleted file]
Content.Server/Traits/Assorted/NarcolepsySystem.cs
Content.Shared/Bed/Sleep/SleepingSystem.cs
Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs
Content.Shared/SSDIndicator/SSDIndicatorSystem.cs
Content.Shared/StatusEffect/StatusEffectsSystem.cs
Content.Shared/StatusEffectNew/Components/StatusEffectAlertComponent.cs [new file with mode: 0644]
Content.Shared/StatusEffectNew/Components/StatusEffectComponent.cs
Content.Shared/StatusEffectNew/Components/StatusEffectContainerComponent.cs
Content.Shared/StatusEffectNew/StatusEffectAlertSystem.cs [new file with mode: 0644]
Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs
Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs
Content.Shared/StatusEffectNew/StatusEffectsSystem.cs [moved from Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs with 56% similarity]

index 1687216b3e1cd35213e3df14fb7473fc64847269..bc176a63b1480023406517b16fa708bbf97928ad 100644 (file)
@@ -15,7 +15,7 @@ public sealed class DrowsinessOverlay : Overlay
     [Dependency] private readonly IPlayerManager _playerManager = default!;
     [Dependency] private readonly IEntitySystemManager _sysMan = default!;
     [Dependency] private readonly IGameTiming _timing = default!;
-    private readonly SharedStatusEffectsSystem _statusEffects = default!;
+    private readonly StatusEffectsSystem _statusEffects = default!;
 
     public override OverlaySpace Space => OverlaySpace.WorldSpace;
     public override bool RequestScreenTexture => true;
@@ -31,7 +31,7 @@ public sealed class DrowsinessOverlay : Overlay
     {
         IoCManager.InjectDependencies(this);
 
-        _statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
+        _statusEffects = _sysMan.GetEntitySystem<StatusEffectsSystem>();
 
         _drowsinessShader = _prototypeManager.Index<ShaderPrototype>("Drowsiness").InstanceUnique();
     }
index 3b351014895224ff496a0e592eb06f9868aeca25..632d2134ca9d8b716299db2290338c37679b443d 100644 (file)
@@ -10,7 +10,7 @@ public sealed class DrowsinessSystem : SharedDrowsinessSystem
 {
     [Dependency] private readonly IPlayerManager _player = default!;
     [Dependency] private readonly IOverlayManager _overlayMan = default!;
-    [Dependency] private readonly SharedStatusEffectsSystem _statusEffects = default!;
+    [Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
 
     private DrowsinessOverlay _overlay = default!;
 
index bfb3815649929ed075f516cbd666a8b47f7e449b..16c34e63ab385ac04d754f8b9252d74fd890ddc7 100644 (file)
@@ -18,7 +18,7 @@ public sealed class RainbowOverlay : Overlay
     [Dependency] private readonly IPlayerManager _playerManager = default!;
     [Dependency] private readonly IEntitySystemManager _sysMan = default!;
     [Dependency] private readonly IGameTiming _timing = default!;
-    private readonly SharedStatusEffectsSystem _statusEffects = default!;
+    private readonly StatusEffectsSystem _statusEffects = default!;
 
     public override OverlaySpace Space => OverlaySpace.WorldSpace;
     public override bool RequestScreenTexture => true;
@@ -39,7 +39,7 @@ public sealed class RainbowOverlay : Overlay
     {
         IoCManager.InjectDependencies(this);
 
-        _statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
+        _statusEffects = _sysMan.GetEntitySystem<StatusEffectsSystem>();
 
         _rainbowShader = _prototypeManager.Index<ShaderPrototype>("Rainbow").InstanceUnique();
         _config.OnValueChanged(CCVars.ReducedMotion, OnReducedMotionChanged, invokeImmediately: true);
diff --git a/Content.Client/StatusEffectNew/ClientStatusEffectsSystem.cs b/Content.Client/StatusEffectNew/ClientStatusEffectsSystem.cs
deleted file mode 100644 (file)
index e35c091..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-using Content.Shared.StatusEffectNew;
-using Content.Shared.StatusEffectNew.Components;
-using Robust.Shared.Collections;
-using Robust.Shared.GameStates;
-
-namespace Content.Client.StatusEffectNew;
-
-/// <inheritdoc/>
-public sealed partial class ClientStatusEffectsSystem : SharedStatusEffectsSystem
-{
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<StatusEffectContainerComponent, ComponentHandleState>(OnHandleState);
-    }
-
-    private void OnHandleState(Entity<StatusEffectContainerComponent> ent, ref ComponentHandleState args)
-    {
-        if (args.Current is not StatusEffectContainerComponentState state)
-            return;
-
-        var toRemove = new ValueList<EntityUid>();
-        foreach (var effect in ent.Comp.ActiveStatusEffects)
-        {
-            if (state.ActiveStatusEffects.Contains(GetNetEntity(effect)))
-                continue;
-
-            toRemove.Add(effect);
-        }
-
-        foreach (var effect in toRemove)
-        {
-            ent.Comp.ActiveStatusEffects.Remove(effect);
-            var ev = new StatusEffectRemovedEvent(ent);
-            RaiseLocalEvent(effect, ref ev);
-        }
-
-        foreach (var effect in state.ActiveStatusEffects)
-        {
-            var effectUid = GetEntity(effect);
-            if (ent.Comp.ActiveStatusEffects.Contains(effectUid))
-                continue;
-
-            ent.Comp.ActiveStatusEffects.Add(effectUid);
-            var ev = new StatusEffectAppliedEvent(ent);
-            RaiseLocalEvent(effectUid, ref ev);
-        }
-    }
-}
index 6de270abcc4e5a3ef92451019c5212786caaccde..13fdc42e109f684377a13f76c6358f1f780d6028 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Server.StatusEffectNew;
 using Content.Shared.Bed.Sleep;
 using Content.Shared.Drowsiness;
 using Content.Shared.StatusEffectNew;
diff --git a/Content.Server/StatusEffectNew/StatusEffectsSystem.cs b/Content.Server/StatusEffectNew/StatusEffectsSystem.cs
deleted file mode 100644 (file)
index e5d7433..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-using Content.Shared.StatusEffectNew;
-using Content.Shared.StatusEffectNew.Components;
-
-namespace Content.Server.StatusEffectNew;
-
-/// <inheritdoc/>
-public sealed partial class StatusEffectsSystem : SharedStatusEffectsSystem
-{
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<StatusEffectContainerComponent, ComponentShutdown>(OnContainerShutdown);
-    }
-
-    private void OnContainerShutdown(Entity<StatusEffectContainerComponent> ent, ref ComponentShutdown args)
-    {
-        foreach (var effect in ent.Comp.ActiveStatusEffects)
-        {
-            QueueDel(effect);
-        }
-    }
-}
index b0746fa3776cfc71e7442fa48d90a1df1381b991..159e953369c3108be2bdf72a03fb4102af492f31 100644 (file)
@@ -1,5 +1,5 @@
-using Content.Server.StatusEffectNew;
 using Content.Shared.Bed.Sleep;
+using Content.Shared.StatusEffectNew;
 using Robust.Shared.Random;
 
 namespace Content.Server.Traits.Assorted;
index 9e1b27cfc90317d99eab1d8cc2f052b4dea0c646..cdff4b7fd702eee5cfa6d5919a3c6119e443b74c 100644 (file)
@@ -38,8 +38,8 @@ public sealed partial class SleepingSystem : EntitySystem
     [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedEmitSoundSystem _emitSound = default!;
-    [Dependency] private readonly StatusEffectsSystem _statusEffectOld = default!;
-    [Dependency] private readonly SharedStatusEffectsSystem _statusEffectNew = default!;
+    [Dependency] private readonly StatusEffect.StatusEffectsSystem _statusEffectOld = default!;
+    [Dependency] private readonly StatusEffectNew.StatusEffectsSystem _statusEffectNew = default!;
 
     public static readonly EntProtoId SleepActionId = "ActionSleep";
     public static readonly EntProtoId WakeActionId = "ActionWake";
index 5ebb8aad1b592bf1554c2395ed92bc65c2d840ad..a232d34925d057f5ddae6feb636331ed750d560e 100644 (file)
@@ -34,7 +34,7 @@ public sealed partial class ModifyStatusEffect : EntityEffect
     /// <inheritdoc />
     public override void Effect(EntityEffectBaseArgs args)
     {
-        var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedStatusEffectsSystem>();
+        var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<StatusEffectsSystem>();
 
         var time = Time;
         if (args is EntityEffectReagentArgs reagentArgs)
index ca7d73ac837dde97ebbc947226e15e6db5f2fbf7..b9c6659c9cc848dd58c1c60ec6a23acd0c50e781 100644 (file)
@@ -16,7 +16,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
 
     [Dependency] private readonly IConfigurationManager _cfg = default!;
     [Dependency] private readonly IGameTiming _timing = default!;
-    [Dependency] private readonly SharedStatusEffectsSystem _statusEffects = default!;
+    [Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
 
     private bool _icSsdSleep;
     private float _icSsdSleepTime;
index f3409d1c2c81ef09459ab406972933b150a6ff76..b56acd3cc5d56749252cc4118bdbb70a15609996 100644 (file)
@@ -9,7 +9,7 @@ using Robust.Shared.Utility;
 
 namespace Content.Shared.StatusEffect
 {
-    [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+    [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
     public sealed class StatusEffectsSystem : EntitySystem
     {
         [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -106,7 +106,7 @@ namespace Content.Shared.StatusEffect
         /// <param name="status">The status effects component to change, if you already have it.</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>
-        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
         public bool TryAddStatusEffect<T>(EntityUid uid, string key, TimeSpan time, bool refresh,
             StatusEffectsComponent? status = null)
             where T : IComponent, new()
@@ -126,7 +126,7 @@ namespace Content.Shared.StatusEffect
 
         }
 
-        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
         public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, string component,
             StatusEffectsComponent? status = null)
         {
@@ -166,7 +166,7 @@ namespace Content.Shared.StatusEffect
         ///     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>
-        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
         public bool TryAddStatusEffect(EntityUid uid,
             string key,
             TimeSpan time,
@@ -260,7 +260,7 @@ namespace Content.Shared.StatusEffect
         ///     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>
-        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
         public bool TryRemoveStatusEffect(EntityUid uid, string key,
             StatusEffectsComponent? status = null, bool remComp = true)
         {
@@ -304,7 +304,7 @@ namespace Content.Shared.StatusEffect
         /// <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>
         /// <returns>False if any status effects failed to be removed, true if they all did.</returns>
-        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
         public bool TryRemoveAllStatusEffects(EntityUid uid,
             StatusEffectsComponent? status = null)
         {
@@ -328,7 +328,7 @@ namespace Content.Shared.StatusEffect
         /// <param name="uid">The entity to check on.</param>
         /// <param name="key">The status effect ID to check for</param>
         /// <param name="status">The status effect component, should you already have it.</param>
-        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
         public bool HasStatusEffect(EntityUid uid, string key,
             StatusEffectsComponent? status = null)
         {
@@ -346,7 +346,7 @@ namespace Content.Shared.StatusEffect
         /// <param name="uid">The entity to check on.</param>
         /// <param name="key">The status effect ID to check for</param>
         /// <param name="status">The status effect component, should you already have it.</param>
-        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
         public bool CanApplyEffect(EntityUid uid, string key, StatusEffectsComponent? status = null)
         {
             // don't log since stuff calling this prolly doesn't care if we don't actually have it
@@ -373,7 +373,7 @@ namespace Content.Shared.StatusEffect
         /// <param name="key">The status effect to add time to.</param>
         /// <param name="time">The amount of time to add.</param>
         /// <param name="status">The status effect component, should you already have it.</param>
-        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
         public bool TryAddTime(EntityUid uid, string key, TimeSpan time,
             StatusEffectsComponent? status = null)
         {
@@ -405,7 +405,7 @@ namespace Content.Shared.StatusEffect
         /// <param name="key">The status effect to remove time from.</param>
         /// <param name="time">The amount of time to add.</param>
         /// <param name="status">The status effect component, should you already have it.</param>
-        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
         public bool TryRemoveTime(EntityUid uid, string key, TimeSpan time,
             StatusEffectsComponent? status = null)
         {
@@ -441,7 +441,7 @@ namespace Content.Shared.StatusEffect
         /// <remarks>
         ///     Not used internally; just sets it itself.
         /// </remarks>
-        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
         public bool TrySetTime(EntityUid uid, string key, TimeSpan time,
             StatusEffectsComponent? status = null)
         {
@@ -465,7 +465,7 @@ namespace Content.Shared.StatusEffect
         /// <param name="time">Out var for the time, if it exists.</param>
         /// <param name="status">The status effects component to use, if any.</param>
         /// <returns>False if the status effect was not active, true otherwise.</returns>
-        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
         public bool TryGetTime(EntityUid uid, string key,
             [NotNullWhen(true)] out (TimeSpan, TimeSpan)? time,
             StatusEffectsComponent? status = null)
diff --git a/Content.Shared/StatusEffectNew/Components/StatusEffectAlertComponent.cs b/Content.Shared/StatusEffectNew/Components/StatusEffectAlertComponent.cs
new file mode 100644 (file)
index 0000000..a389be6
--- /dev/null
@@ -0,0 +1,26 @@
+using Content.Shared.Alert;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.StatusEffectNew.Components;
+
+/// <summary>
+/// Used in conjunction with <see cref="StatusEffectComponent"/> to display an alert when the status effect is present.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[EntityCategory("StatusEffects")]
+public sealed partial class StatusEffectAlertComponent : Component
+{
+    /// <summary>
+    /// Status effect indication for the player.
+    /// </summary>
+    [DataField]
+    public ProtoId<AlertPrototype> Alert;
+
+    /// <summary>
+    /// If the status effect has a set end time and this is true, a duration
+    /// indicator will be displayed with the alert.
+    /// </summary>
+    [DataField]
+    public bool ShowDuration = true;
+}
index 6419874212ec206c45aac68c15f7841a3070c07c..25f40718e9753af0b8b9437a453bd523b09d50db 100644 (file)
@@ -11,7 +11,7 @@ namespace Content.Shared.StatusEffectNew.Components;
 /// Provides a link between the effect and the affected entity, and some data common to all status effects.
 /// </summary>
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
-[Access(typeof(SharedStatusEffectsSystem))]
+[Access(typeof(StatusEffectsSystem))]
 [EntityCategory("StatusEffects")]
 public sealed partial class StatusEffectComponent : Component
 {
@@ -21,12 +21,6 @@ public sealed partial class StatusEffectComponent : Component
     [DataField, AutoNetworkedField]
     public EntityUid? AppliedTo;
 
-    /// <summary>
-    /// Status effect indication for the player. If Null, no Alert will be displayed.
-    /// </summary>
-    [DataField]
-    public ProtoId<AlertPrototype>? Alert;
-
     /// <summary>
     /// When this effect will end. If Null, the effect lasts indefinitely.
     /// </summary>
index 6d9efaf3ac2845c198e24edfbc50ba9e38552467..9c2820653ae04c7ee9df2854311d81b6b326b3bc 100644 (file)
@@ -1,5 +1,5 @@
+using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
 
 namespace Content.Shared.StatusEffectNew.Components;
 
@@ -9,15 +9,14 @@ namespace Content.Shared.StatusEffectNew.Components;
 /// Can be used for tracking currently applied status effects.
 /// </summary>
 [RegisterComponent, NetworkedComponent]
-[Access(typeof(SharedStatusEffectsSystem))]
+[Access(typeof(StatusEffectsSystem))]
 public sealed partial class StatusEffectContainerComponent : Component
 {
-    [DataField]
-    public HashSet<EntityUid> ActiveStatusEffects = new();
-}
+    public const string ContainerId = "status-effects";
 
-[Serializable, NetSerializable]
-public sealed class StatusEffectContainerComponentState(HashSet<NetEntity> activeStatusEffects) : ComponentState
-{
-    public readonly HashSet<NetEntity> ActiveStatusEffects = activeStatusEffects;
+    /// <summary>
+    /// The actual container holding references to the active status effects
+    /// </summary>
+    [ViewVariables]
+    public Container? ActiveStatusEffects;
 }
diff --git a/Content.Shared/StatusEffectNew/StatusEffectAlertSystem.cs b/Content.Shared/StatusEffectNew/StatusEffectAlertSystem.cs
new file mode 100644 (file)
index 0000000..d540f86
--- /dev/null
@@ -0,0 +1,62 @@
+using Content.Shared.Alert;
+using Content.Shared.StatusEffectNew.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.StatusEffectNew;
+
+/// <summary>
+/// Handles displaying status effects that should show an alert, optionally with a duration.
+/// </summary>
+public sealed class StatusEffectAlertSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly AlertsSystem _alerts = default!;
+
+    private EntityQuery<StatusEffectComponent> _effectQuery;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<StatusEffectAlertComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
+        SubscribeLocalEvent<StatusEffectAlertComponent, StatusEffectRemovedEvent>(OnStatusEffectRemoved);
+        SubscribeLocalEvent<StatusEffectAlertComponent, StatusEffectEndTimeUpdatedEvent>(OnEndTimeUpdated);
+
+        _effectQuery = GetEntityQuery<StatusEffectComponent>();
+    }
+
+    private void OnStatusEffectApplied(Entity<StatusEffectAlertComponent> ent, ref StatusEffectAppliedEvent args)
+    {
+        if (!_effectQuery.TryComp(ent, out var effectComp))
+            return;
+
+        RefreshAlert(ent, args.Target, effectComp.EndEffectTime);
+    }
+
+    private void OnStatusEffectRemoved(Entity<StatusEffectAlertComponent> ent, ref StatusEffectRemovedEvent args)
+    {
+        _alerts.ClearAlert(args.Target, ent.Comp.Alert);
+    }
+
+    private void OnEndTimeUpdated(Entity<StatusEffectAlertComponent> ent, ref StatusEffectEndTimeUpdatedEvent args)
+    {
+        RefreshAlert(ent, args.Target, args.EndTime);
+    }
+
+    private void RefreshAlert(Entity<StatusEffectAlertComponent> ent, EntityUid target, TimeSpan? endTime)
+    {
+        (TimeSpan Start, TimeSpan End)? cooldown = null;
+
+        // Make sure the start time of the alert cooldown is still accurate
+        // This ensures the progress wheel doesn't "reset" every duration change.
+        if (ent.Comp.ShowDuration
+            && endTime is not null
+            && _alerts.TryGet(ent.Comp.Alert, out var alert))
+        {
+            _alerts.TryGetAlertState(target, alert.AlertKey, out var alertState);
+            cooldown = (alertState.Cooldown?.Item1 ?? _timing.CurTime, endTime.Value);
+        }
+
+        _alerts.ShowAlert(target, ent.Comp.Alert, cooldown: cooldown);
+    }
+}
index 5e20cea1bb8516a01820835c40b92e938db0502e..d508ea8b739bf0ca6bbd73557804247410c6ed78 100644 (file)
@@ -4,7 +4,7 @@ using Robust.Shared.Prototypes;
 
 namespace Content.Shared.StatusEffectNew;
 
-public abstract partial class SharedStatusEffectsSystem
+public sealed partial class StatusEffectsSystem
 {
     /// <summary>
     /// Increments duration of status effect by <see cref="duration"/>.
@@ -103,28 +103,22 @@ public abstract partial class SharedStatusEffectsSystem
     /// </summary>
     public bool TryRemoveStatusEffect(EntityUid target, EntProtoId effectProto)
     {
-        if (_net.IsClient) //We cant remove the effect on the client (we need someone more robust at networking than me)
-            return false;
-
         if (!_containerQuery.TryComp(target, out var container))
             return false;
 
-        foreach (var effect in container.ActiveStatusEffects)
+        foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
         {
             var meta = MetaData(effect);
-            if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
-            {
-                if (!_effectQuery.TryComp(effect, out var effectComp))
-                    return false;
 
-                var ev = new StatusEffectRemovedEvent(target);
-                RaiseLocalEvent(effect, ref ev);
+            if (meta.EntityPrototype is null
+                || meta.EntityPrototype != effectProto)
+                continue;
 
-                QueueDel(effect);
-                container.ActiveStatusEffects.Remove(effect);
-                Dirty(target, container);
-                return true;
-            }
+            if (!_effectQuery.HasComp(effect))
+                return false;
+
+            PredictedQueueDel(effect);
+            return true;
         }
 
         return false;
@@ -138,7 +132,7 @@ public abstract partial class SharedStatusEffectsSystem
         if (!_containerQuery.TryComp(target, out var container))
             return false;
 
-        foreach (var effect in container.ActiveStatusEffects)
+        foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
         {
             var meta = MetaData(effect);
             if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
@@ -157,7 +151,7 @@ public abstract partial class SharedStatusEffectsSystem
         if (!_containerQuery.TryComp(target, out var container))
             return false;
 
-        foreach (var e in container.ActiveStatusEffects)
+        foreach (var e in container.ActiveStatusEffects?.ContainedEntities ?? [])
         {
             var meta = MetaData(e);
             if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
@@ -188,7 +182,7 @@ public abstract partial class SharedStatusEffectsSystem
         if (!Resolve(uid, ref container))
             return false;
 
-        foreach (var effect in container.ActiveStatusEffects)
+        foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
         {
             var meta = MetaData(effect);
             if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
@@ -218,7 +212,7 @@ public abstract partial class SharedStatusEffectsSystem
         if (!TryEffectsWithComp<T>(uid, out var status))
             return false;
 
-        time.Item2 = TimeSpan.Zero;
+        time.EndEffectTime = TimeSpan.Zero;
 
         foreach (var effect in status)
         {
@@ -228,7 +222,7 @@ public abstract partial class SharedStatusEffectsSystem
                 return true;
             }
 
-            if (effect.Comp2.EndEffectTime > time.Item2)
+            if (effect.Comp2.EndEffectTime > time.EndEffectTime)
                 time = (effect.Owner, effect.Comp2.EndEffectTime);
         }
         return true;
@@ -249,7 +243,7 @@ public abstract partial class SharedStatusEffectsSystem
         if (!_containerQuery.TryComp(uid, out var container))
             return false;
 
-        foreach (var effect in container.ActiveStatusEffects)
+        foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
         {
             var meta = MetaData(effect);
             if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
@@ -273,7 +267,7 @@ public abstract partial class SharedStatusEffectsSystem
         if (!_containerQuery.TryComp(uid, out var container))
             return false;
 
-        foreach (var effect in container.ActiveStatusEffects)
+        foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
         {
             var meta = MetaData(effect);
             if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
@@ -293,7 +287,7 @@ public abstract partial class SharedStatusEffectsSystem
         if (!_containerQuery.TryComp(target, out var container))
             return false;
 
-        foreach (var effect in container.ActiveStatusEffects)
+        foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
         {
             if (HasComp<T>(effect))
                 return true;
@@ -311,7 +305,7 @@ public abstract partial class SharedStatusEffectsSystem
         if (!_containerQuery.TryComp(target, out var container))
             return false;
 
-        foreach (var effect in container.ActiveStatusEffects)
+        foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
         {
             if (!_effectQuery.TryComp(effect, out var statusComp))
                 continue;
@@ -338,7 +332,7 @@ public abstract partial class SharedStatusEffectsSystem
         if (!_containerQuery.TryComp(target, out var container))
             return false;
 
-        foreach (var effect in container.ActiveStatusEffects)
+        foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
         {
             if (!HasComp<T>(effect))
                 continue;
index 6d97a75edd00c138034cca87a651f412e49852cf..224a3dc4a4db5d2219636be08389bc4da0f4548d 100644 (file)
@@ -3,20 +3,20 @@ using Robust.Shared.Player;
 
 namespace Content.Shared.StatusEffectNew;
 
-public abstract partial class SharedStatusEffectsSystem
+public sealed partial class StatusEffectsSystem
 {
-    protected void InitializeRelay()
+    private void InitializeRelay()
     {
         SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerAttachedEvent>(RelayStatusEffectEvent);
         SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerDetachedEvent>(RelayStatusEffectEvent);
     }
 
-    protected void RefRelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct
+    private void RefRelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct
     {
         RelayEvent((uid, component), ref args);
     }
 
-    protected void RelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, T args) where T : class
+    private void RelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, T args) where T : class
     {
         RelayEvent((uid, component), args);
     }
@@ -25,7 +25,7 @@ public abstract partial class SharedStatusEffectsSystem
     {
         // this copies the by-ref event if it is a struct
         var ev = new StatusEffectRelayedEvent<T>(args);
-        foreach (var activeEffect in statusEffect.Comp.ActiveStatusEffects)
+        foreach (var activeEffect in statusEffect.Comp.ActiveStatusEffects?.ContainedEntities ?? [])
         {
             RaiseLocalEvent(activeEffect, ref ev);
         }
@@ -37,7 +37,7 @@ public abstract partial class SharedStatusEffectsSystem
     {
         // this copies the by-ref event if it is a struct
         var ev = new StatusEffectRelayedEvent<T>(args);
-        foreach (var activeEffect in statusEffect.Comp.ActiveStatusEffects)
+        foreach (var activeEffect in statusEffect.Comp.ActiveStatusEffects?.ContainedEntities ?? [])
         {
             RaiseLocalEvent(activeEffect, ref ev);
         }
similarity index 56%
rename from Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs
rename to Content.Shared/StatusEffectNew/StatusEffectsSystem.cs
index 9e6ed4d7ff32560946da1345674ee21eca8ca8ca..7f39a1f7c5431685ae634a1b05af49e544444a15 100644 (file)
@@ -1,9 +1,7 @@
 using System.Diagnostics.CodeAnalysis;
-using Content.Shared.Alert;
 using Content.Shared.StatusEffectNew.Components;
 using Content.Shared.Whitelist;
-using Robust.Shared.GameStates;
-using Robust.Shared.Network;
+using Robust.Shared.Containers;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Timing;
 
@@ -13,15 +11,12 @@ namespace Content.Shared.StatusEffectNew;
 /// This system controls status effects, their lifetime, and provides an API for adding them to entities,
 /// removing them from entities, or getting information about current effects on entities.
 /// </summary>
-public abstract partial class SharedStatusEffectsSystem : EntitySystem
+public sealed partial class StatusEffectsSystem : EntitySystem
 {
-    [Dependency] private readonly AlertsSystem _alerts = default!;
     [Dependency] private readonly IGameTiming _timing = default!;
-    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly SharedContainerSystem _container = default!;
     [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
     [Dependency] private readonly IPrototypeManager _proto = default!;
-    [Dependency] private readonly IComponentFactory _compFactory = default!;
-    [Dependency] private readonly INetManager _net = default!;
 
     private EntityQuery<StatusEffectContainerComponent> _containerQuery;
     private EntityQuery<StatusEffectComponent> _effectQuery;
@@ -32,20 +27,15 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
 
         InitializeRelay();
 
-        SubscribeLocalEvent<StatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
-        SubscribeLocalEvent<StatusEffectComponent, StatusEffectRemovedEvent>(OnStatusEffectRemoved);
-
-        SubscribeLocalEvent<StatusEffectContainerComponent, ComponentGetState>(OnGetState);
+        SubscribeLocalEvent<StatusEffectContainerComponent, ComponentInit>(OnStatusContainerInit);
+        SubscribeLocalEvent<StatusEffectContainerComponent, ComponentShutdown>(OnStatusContainerShutdown);
+        SubscribeLocalEvent<StatusEffectContainerComponent, EntInsertedIntoContainerMessage>(OnEntityInserted);
+        SubscribeLocalEvent<StatusEffectContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
 
         _containerQuery = GetEntityQuery<StatusEffectContainerComponent>();
         _effectQuery = GetEntityQuery<StatusEffectComponent>();
     }
 
-    private void OnGetState(Entity<StatusEffectContainerComponent> ent, ref ComponentGetState args)
-    {
-        args.State = new StatusEffectContainerComponentState(GetNetEntitySet(ent.Comp.ActiveStatusEffects));
-    }
-
     public override void Update(float frameTime)
     {
         base.Update(frameTime);
@@ -70,15 +60,59 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
         }
     }
 
-    private void AddStatusEffectTime(EntityUid effect, TimeSpan delta)
+    private void OnStatusContainerInit(Entity<StatusEffectContainerComponent> ent, ref ComponentInit args)
     {
-        if (!_effectQuery.TryComp(effect, out var effectComp))
+        ent.Comp.ActiveStatusEffects =
+            _container.EnsureContainer<Container>(ent, StatusEffectContainerComponent.ContainerId);
+        // We show the contents of the container to allow status effects to have visible sprites.
+        ent.Comp.ActiveStatusEffects.ShowContents = true;
+    }
+
+    private void OnStatusContainerShutdown(Entity<StatusEffectContainerComponent> ent, ref ComponentShutdown args)
+    {
+        if (ent.Comp.ActiveStatusEffects is { } container)
+            _container.ShutdownContainer(container);
+    }
+
+    private void OnEntityInserted(Entity<StatusEffectContainerComponent> ent, ref EntInsertedIntoContainerMessage args)
+    {
+        if (args.Container.ID != StatusEffectContainerComponent.ContainerId)
             return;
 
-        effectComp.EndEffectTime += delta;
-        Dirty(effect, effectComp);
+        if (!TryComp<StatusEffectComponent>(args.Entity, out var statusComp))
+            return;
+
+        // Make sure AppliedTo is set correctly so events can rely on it
+        if (statusComp.AppliedTo != ent)
+        {
+            statusComp.AppliedTo = ent;
+            Dirty(args.Entity, statusComp);
+        }
+
+        var ev = new StatusEffectAppliedEvent(ent);
+        RaiseLocalEvent(args.Entity, ref ev);
+    }
+
+    private void OnEntityRemoved(Entity<StatusEffectContainerComponent> ent, ref EntRemovedFromContainerMessage args)
+    {
+        if (args.Container.ID != StatusEffectContainerComponent.ContainerId)
+            return;
+
+        if (!TryComp<StatusEffectComponent>(args.Entity, out var statusComp))
+            return;
 
-        ShowAlertIfNeeded(effectComp);
+        var ev = new StatusEffectRemovedEvent(ent);
+        RaiseLocalEvent(args.Entity, ref ev);
+
+        // Clear AppliedTo after events are handled so event handlers can use it.
+        if (statusComp.AppliedTo == null)
+            return;
+
+        // Why not just delete it? Well, that might end up being best, but this
+        // could theoretically allow for moving status effects from one entity
+        // to another. That might be good to have for polymorphs or something.
+        statusComp.AppliedTo = null;
+        Dirty(args.Entity, statusComp);
     }
 
     private void SetStatusEffectTime(EntityUid effect, TimeSpan? duration)
@@ -97,8 +131,6 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
             effectComp.EndEffectTime = _timing.CurTime + duration;
 
         Dirty(effect, effectComp);
-
-        ShowAlertIfNeeded(effectComp);
     }
 
     private void UpdateStatusEffectTime(EntityUid effect, TimeSpan? duration)
@@ -122,24 +154,6 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
         }
 
         Dirty(effect, effectComp);
-
-        ShowAlertIfNeeded(effectComp);
-    }
-
-
-    private void OnStatusEffectApplied(Entity<StatusEffectComponent> ent, ref StatusEffectAppliedEvent args)
-    {
-        StatusEffectComponent statusEffect = ent;
-        ShowAlertIfNeeded(statusEffect);
-    }
-
-    private void OnStatusEffectRemoved(Entity<StatusEffectComponent> ent, ref StatusEffectRemovedEvent args)
-    {
-        if (ent.Comp.AppliedTo is null)
-            return;
-
-        if (ent.Comp is { AppliedTo: not null, Alert: not null })
-            _alerts.ClearAlert(ent.Comp.AppliedTo.Value, ent.Comp.Alert.Value);
     }
 
     private bool CanAddStatusEffect(EntityUid uid, EntProtoId effectProto)
@@ -147,7 +161,7 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
         if (!_proto.TryIndex(effectProto, out var effectProtoData))
             return false;
 
-        if (!effectProtoData.TryGetComponent<StatusEffectComponent>(out var effectProtoComp, _compFactory))
+        if (!effectProtoData.TryGetComponent<StatusEffectComponent>(out var effectProtoComp, Factory))
             return false;
 
         if (!_whitelist.CheckBoth(uid, effectProtoComp.Blacklist, effectProtoComp.Whitelist))
@@ -181,43 +195,50 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
         if (!CanAddStatusEffect(target, effectProto))
             return false;
 
-        var container = EnsureComp<StatusEffectContainerComponent>(target);
+        EnsureComp<StatusEffectContainerComponent>(target);
+
+        // And only if all checks passed we spawn the effect
+        if (!PredictedTrySpawnInContainer(effectProto,
+                target,
+                StatusEffectContainerComponent.ContainerId,
+                out var effect))
+            return false;
 
-        //And only if all checks passed we spawn the effect
-        var effect = PredictedSpawnAttachedTo(effectProto, Transform(target).Coordinates);
-        _transform.SetParent(effect, target);
         if (!_effectQuery.TryComp(effect, out var effectComp))
             return false;
 
         statusEffect = effect;
+        SetStatusEffectEndTime((effect.Value, effectComp), _timing.CurTime + duration);
 
-        if (duration != null)
-            effectComp.EndEffectTime = _timing.CurTime + duration;
-
-        container.ActiveStatusEffects.Add(effect);
-        effectComp.AppliedTo = target;
-        Dirty(target, container);
-        Dirty(effect, effectComp);
+        return true;
+    }
 
-        var ev = new StatusEffectAppliedEvent(target);
-        RaiseLocalEvent(effect, ref ev);
+    private void AddStatusEffectTime(EntityUid effect, TimeSpan delta)
+    {
+        if (!_effectQuery.TryComp(effect, out var effectComp))
+            return;
 
-        return true;
+        // If we don't have an end time set, we want to just make the status effect end in delta time from now.
+        SetStatusEffectEndTime((effect, effectComp), (effectComp.EndEffectTime ?? _timing.CurTime) + delta);
     }
 
-    private void ShowAlertIfNeeded(StatusEffectComponent effectComp)
+    private void SetStatusEffectEndTime(Entity<StatusEffectComponent?> ent, TimeSpan? endTime)
     {
-        if (effectComp is { AppliedTo: not null, Alert: not null })
-        {
-            (TimeSpan, TimeSpan)? cooldown = effectComp.EndEffectTime is null
-                ? null
-                : (_timing.CurTime, effectComp.EndEffectTime.Value);
-            _alerts.ShowAlert(
-                effectComp.AppliedTo.Value,
-                effectComp.Alert.Value,
-                cooldown: cooldown
-            );
-        }
+        if (!_effectQuery.Resolve(ent, ref ent.Comp))
+            return;
+
+        if (ent.Comp.EndEffectTime == endTime)
+            return;
+
+        ent.Comp.EndEffectTime = endTime;
+
+        if (ent.Comp.AppliedTo is not { } appliedTo)
+            return; // Not much we can do!
+
+        var ev = new StatusEffectEndTimeUpdatedEvent(appliedTo, endTime);
+        RaiseLocalEvent(ent, ref ev);
+
+        Dirty(ent);
     }
 }
 
@@ -238,3 +259,11 @@ public readonly record struct StatusEffectRemovedEvent(EntityUid Target);
 /// </summary>
 [ByRefEvent]
 public record struct BeforeStatusEffectAddedEvent(EntProtoId Effect, bool Cancelled = false);
+
+/// <summary>
+/// Raised on an effect entity when its <see cref="StatusEffectComponent.EndEffectTime"/> is updated in any way.
+/// </summary>
+/// <param name="Target">The entity the effect is attached to.</param>
+/// <param name="EndTime">The new end time of the status effect, included for convenience.</param>
+[ByRefEvent]
+public record struct StatusEffectEndTimeUpdatedEvent(EntityUid Target, TimeSpan? EndTime);