From dbfe05d5cc90c825411fef9129e431733af35e2d Mon Sep 17 00:00:00 2001 From: Perry Fraser Date: Sat, 12 Jul 2025 12:49:58 -0400 Subject: [PATCH] refactor: rework the new status effect system to use containers (#38915) --- .../Drowsiness/DrowsinessOverlay.cs | 4 +- Content.Client/Drowsiness/DrowsinessSystem.cs | 2 +- Content.Client/Drugs/RainbowOverlay.cs | 4 +- .../ClientStatusEffectsSystem.cs | 50 ------ Content.Server/Drowsiness/DrowsinessSystem.cs | 1 - .../StatusEffectNew/StatusEffectsSystem.cs | 23 --- .../Traits/Assorted/NarcolepsySystem.cs | 2 +- Content.Shared/Bed/Sleep/SleepingSystem.cs | 4 +- .../StatusEffects/ModifyStatusEffect.cs | 2 +- .../SSDIndicator/SSDIndicatorSystem.cs | 2 +- .../StatusEffect/StatusEffectsSystem.cs | 24 +-- .../Components/StatusEffectAlertComponent.cs | 26 +++ .../Components/StatusEffectComponent.cs | 8 +- .../StatusEffectContainerComponent.cs | 17 +- .../StatusEffectAlertSystem.cs | 62 +++++++ .../StatusEffectNew/StatusEffectSystem.API.cs | 46 +++-- .../StatusEffectSystem.Relay.cs | 12 +- ...ffectsSystem.cs => StatusEffectsSystem.cs} | 169 ++++++++++-------- 18 files changed, 244 insertions(+), 214 deletions(-) delete mode 100644 Content.Client/StatusEffectNew/ClientStatusEffectsSystem.cs delete mode 100644 Content.Server/StatusEffectNew/StatusEffectsSystem.cs create mode 100644 Content.Shared/StatusEffectNew/Components/StatusEffectAlertComponent.cs create mode 100644 Content.Shared/StatusEffectNew/StatusEffectAlertSystem.cs rename Content.Shared/StatusEffectNew/{SharedStatusEffectsSystem.cs => StatusEffectsSystem.cs} (56%) diff --git a/Content.Client/Drowsiness/DrowsinessOverlay.cs b/Content.Client/Drowsiness/DrowsinessOverlay.cs index 9c47ec2fbe..3770802302 100644 --- a/Content.Client/Drowsiness/DrowsinessOverlay.cs +++ b/Content.Client/Drowsiness/DrowsinessOverlay.cs @@ -17,7 +17,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; @@ -33,7 +33,7 @@ public sealed class DrowsinessOverlay : Overlay { IoCManager.InjectDependencies(this); - _statusEffects = _sysMan.GetEntitySystem(); + _statusEffects = _sysMan.GetEntitySystem(); _drowsinessShader = _prototypeManager.Index(Shader).InstanceUnique(); } diff --git a/Content.Client/Drowsiness/DrowsinessSystem.cs b/Content.Client/Drowsiness/DrowsinessSystem.cs index 3b35101489..632d2134ca 100644 --- a/Content.Client/Drowsiness/DrowsinessSystem.cs +++ b/Content.Client/Drowsiness/DrowsinessSystem.cs @@ -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!; diff --git a/Content.Client/Drugs/RainbowOverlay.cs b/Content.Client/Drugs/RainbowOverlay.cs index a2a431feb6..e40fcdfca2 100644 --- a/Content.Client/Drugs/RainbowOverlay.cs +++ b/Content.Client/Drugs/RainbowOverlay.cs @@ -20,7 +20,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; @@ -41,7 +41,7 @@ public sealed class RainbowOverlay : Overlay { IoCManager.InjectDependencies(this); - _statusEffects = _sysMan.GetEntitySystem(); + _statusEffects = _sysMan.GetEntitySystem(); _rainbowShader = _prototypeManager.Index(Shader).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 index e35c09190e..0000000000 --- a/Content.Client/StatusEffectNew/ClientStatusEffectsSystem.cs +++ /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; - -/// -public sealed partial class ClientStatusEffectsSystem : SharedStatusEffectsSystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnHandleState); - } - - private void OnHandleState(Entity ent, ref ComponentHandleState args) - { - if (args.Current is not StatusEffectContainerComponentState state) - return; - - var toRemove = new ValueList(); - 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); - } - } -} diff --git a/Content.Server/Drowsiness/DrowsinessSystem.cs b/Content.Server/Drowsiness/DrowsinessSystem.cs index 6de270abcc..13fdc42e10 100644 --- a/Content.Server/Drowsiness/DrowsinessSystem.cs +++ b/Content.Server/Drowsiness/DrowsinessSystem.cs @@ -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 index e5d7433396..0000000000 --- a/Content.Server/StatusEffectNew/StatusEffectsSystem.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Shared.StatusEffectNew; -using Content.Shared.StatusEffectNew.Components; - -namespace Content.Server.StatusEffectNew; - -/// -public sealed partial class StatusEffectsSystem : SharedStatusEffectsSystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnContainerShutdown); - } - - private void OnContainerShutdown(Entity ent, ref ComponentShutdown args) - { - foreach (var effect in ent.Comp.ActiveStatusEffects) - { - QueueDel(effect); - } - } -} diff --git a/Content.Server/Traits/Assorted/NarcolepsySystem.cs b/Content.Server/Traits/Assorted/NarcolepsySystem.cs index b0746fa377..159e953369 100644 --- a/Content.Server/Traits/Assorted/NarcolepsySystem.cs +++ b/Content.Server/Traits/Assorted/NarcolepsySystem.cs @@ -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; diff --git a/Content.Shared/Bed/Sleep/SleepingSystem.cs b/Content.Shared/Bed/Sleep/SleepingSystem.cs index 9e1b27cfc9..cdff4b7fd7 100644 --- a/Content.Shared/Bed/Sleep/SleepingSystem.cs +++ b/Content.Shared/Bed/Sleep/SleepingSystem.cs @@ -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"; diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs index 5ebb8aad1b..a232d34925 100644 --- a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs +++ b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs @@ -34,7 +34,7 @@ public sealed partial class ModifyStatusEffect : EntityEffect /// public override void Effect(EntityEffectBaseArgs args) { - var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem(); + var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem(); var time = Time; if (args is EntityEffectReagentArgs reagentArgs) diff --git a/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs b/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs index ca7d73ac83..b9c6659c9c 100644 --- a/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs +++ b/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs @@ -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; diff --git a/Content.Shared/StatusEffect/StatusEffectsSystem.cs b/Content.Shared/StatusEffect/StatusEffectsSystem.cs index f3409d1c2c..b56acd3cc5 100644 --- a/Content.Shared/StatusEffect/StatusEffectsSystem.cs +++ b/Content.Shared/StatusEffect/StatusEffectsSystem.cs @@ -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 /// The status effects component to change, if you already have it. /// False if the effect could not be added or the component already exists, true otherwise. /// The component type to add and remove from the entity. - [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, 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! /// - [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. /// - [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 /// The entity to remove effects from. /// The status effects component to change, if you already have it. /// False if any status effects failed to be removed, true if they all did. - [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 /// The entity to check on. /// The status effect ID to check for /// The status effect component, should you already have it. - [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 /// The entity to check on. /// The status effect ID to check for /// The status effect component, should you already have it. - [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 /// The status effect to add time to. /// The amount of time to add. /// The status effect component, should you already have it. - [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 /// The status effect to remove time from. /// The amount of time to add. /// The status effect component, should you already have it. - [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 /// /// Not used internally; just sets it itself. /// - [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 /// Out var for the time, if it exists. /// The status effects component to use, if any. /// False if the status effect was not active, true otherwise. - [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 index 0000000000..a389be6947 --- /dev/null +++ b/Content.Shared/StatusEffectNew/Components/StatusEffectAlertComponent.cs @@ -0,0 +1,26 @@ +using Content.Shared.Alert; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.StatusEffectNew.Components; + +/// +/// Used in conjunction with to display an alert when the status effect is present. +/// +[RegisterComponent, NetworkedComponent] +[EntityCategory("StatusEffects")] +public sealed partial class StatusEffectAlertComponent : Component +{ + /// + /// Status effect indication for the player. + /// + [DataField] + public ProtoId Alert; + + /// + /// If the status effect has a set end time and this is true, a duration + /// indicator will be displayed with the alert. + /// + [DataField] + public bool ShowDuration = true; +} diff --git a/Content.Shared/StatusEffectNew/Components/StatusEffectComponent.cs b/Content.Shared/StatusEffectNew/Components/StatusEffectComponent.cs index 6419874212..25f40718e9 100644 --- a/Content.Shared/StatusEffectNew/Components/StatusEffectComponent.cs +++ b/Content.Shared/StatusEffectNew/Components/StatusEffectComponent.cs @@ -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. /// [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; - /// - /// Status effect indication for the player. If Null, no Alert will be displayed. - /// - [DataField] - public ProtoId? Alert; - /// /// When this effect will end. If Null, the effect lasts indefinitely. /// diff --git a/Content.Shared/StatusEffectNew/Components/StatusEffectContainerComponent.cs b/Content.Shared/StatusEffectNew/Components/StatusEffectContainerComponent.cs index 6d9efaf3ac..9c2820653a 100644 --- a/Content.Shared/StatusEffectNew/Components/StatusEffectContainerComponent.cs +++ b/Content.Shared/StatusEffectNew/Components/StatusEffectContainerComponent.cs @@ -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. /// [RegisterComponent, NetworkedComponent] -[Access(typeof(SharedStatusEffectsSystem))] +[Access(typeof(StatusEffectsSystem))] public sealed partial class StatusEffectContainerComponent : Component { - [DataField] - public HashSet ActiveStatusEffects = new(); -} + public const string ContainerId = "status-effects"; -[Serializable, NetSerializable] -public sealed class StatusEffectContainerComponentState(HashSet activeStatusEffects) : ComponentState -{ - public readonly HashSet ActiveStatusEffects = activeStatusEffects; + /// + /// The actual container holding references to the active status effects + /// + [ViewVariables] + public Container? ActiveStatusEffects; } diff --git a/Content.Shared/StatusEffectNew/StatusEffectAlertSystem.cs b/Content.Shared/StatusEffectNew/StatusEffectAlertSystem.cs new file mode 100644 index 0000000000..d540f865c0 --- /dev/null +++ b/Content.Shared/StatusEffectNew/StatusEffectAlertSystem.cs @@ -0,0 +1,62 @@ +using Content.Shared.Alert; +using Content.Shared.StatusEffectNew.Components; +using Robust.Shared.Timing; + +namespace Content.Shared.StatusEffectNew; + +/// +/// Handles displaying status effects that should show an alert, optionally with a duration. +/// +public sealed class StatusEffectAlertSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly AlertsSystem _alerts = default!; + + private EntityQuery _effectQuery; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStatusEffectApplied); + SubscribeLocalEvent(OnStatusEffectRemoved); + SubscribeLocalEvent(OnEndTimeUpdated); + + _effectQuery = GetEntityQuery(); + } + + private void OnStatusEffectApplied(Entity ent, ref StatusEffectAppliedEvent args) + { + if (!_effectQuery.TryComp(ent, out var effectComp)) + return; + + RefreshAlert(ent, args.Target, effectComp.EndEffectTime); + } + + private void OnStatusEffectRemoved(Entity ent, ref StatusEffectRemovedEvent args) + { + _alerts.ClearAlert(args.Target, ent.Comp.Alert); + } + + private void OnEndTimeUpdated(Entity ent, ref StatusEffectEndTimeUpdatedEvent args) + { + RefreshAlert(ent, args.Target, args.EndTime); + } + + private void RefreshAlert(Entity 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); + } +} diff --git a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs index 5e20cea1bb..d508ea8b73 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs @@ -4,7 +4,7 @@ using Robust.Shared.Prototypes; namespace Content.Shared.StatusEffectNew; -public abstract partial class SharedStatusEffectsSystem +public sealed partial class StatusEffectsSystem { /// /// Increments duration of status effect by . @@ -103,28 +103,22 @@ public abstract partial class SharedStatusEffectsSystem /// 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(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(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(effect)) continue; diff --git a/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs b/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs index 6d97a75edd..224a3dc4a4 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs @@ -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(RelayStatusEffectEvent); SubscribeLocalEvent(RelayStatusEffectEvent); } - protected void RefRelayStatusEffectEvent(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct + private void RefRelayStatusEffectEvent(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct { RelayEvent((uid, component), ref args); } - protected void RelayStatusEffectEvent(EntityUid uid, StatusEffectContainerComponent component, T args) where T : class + private void RelayStatusEffectEvent(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(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(args); - foreach (var activeEffect in statusEffect.Comp.ActiveStatusEffects) + foreach (var activeEffect in statusEffect.Comp.ActiveStatusEffects?.ContainedEntities ?? []) { RaiseLocalEvent(activeEffect, ref ev); } diff --git a/Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs b/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs similarity index 56% rename from Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs rename to Content.Shared/StatusEffectNew/StatusEffectsSystem.cs index 9e6ed4d7ff..7f39a1f7c5 100644 --- a/Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs @@ -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. /// -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 _containerQuery; private EntityQuery _effectQuery; @@ -32,20 +27,15 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem InitializeRelay(); - SubscribeLocalEvent(OnStatusEffectApplied); - SubscribeLocalEvent(OnStatusEffectRemoved); - - SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnStatusContainerInit); + SubscribeLocalEvent(OnStatusContainerShutdown); + SubscribeLocalEvent(OnEntityInserted); + SubscribeLocalEvent(OnEntityRemoved); _containerQuery = GetEntityQuery(); _effectQuery = GetEntityQuery(); } - private void OnGetState(Entity 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 ent, ref ComponentInit args) { - if (!_effectQuery.TryComp(effect, out var effectComp)) + ent.Comp.ActiveStatusEffects = + _container.EnsureContainer(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 ent, ref ComponentShutdown args) + { + if (ent.Comp.ActiveStatusEffects is { } container) + _container.ShutdownContainer(container); + } + + private void OnEntityInserted(Entity ent, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != StatusEffectContainerComponent.ContainerId) return; - effectComp.EndEffectTime += delta; - Dirty(effect, effectComp); + if (!TryComp(args.Entity, out var statusComp)) + return; - ShowAlertIfNeeded(effectComp); + // 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 ent, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != StatusEffectContainerComponent.ContainerId) + return; + + if (!TryComp(args.Entity, out var statusComp)) + return; + + 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 ent, ref StatusEffectAppliedEvent args) - { - StatusEffectComponent statusEffect = ent; - ShowAlertIfNeeded(statusEffect); - } - - private void OnStatusEffectRemoved(Entity 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(out var effectProtoComp, _compFactory)) + if (!effectProtoData.TryGetComponent(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(target); + EnsureComp(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; - - if (duration != null) - effectComp.EndEffectTime = _timing.CurTime + duration; - - container.ActiveStatusEffects.Add(effect); - effectComp.AppliedTo = target; - Dirty(target, container); - Dirty(effect, effectComp); - - var ev = new StatusEffectAppliedEvent(target); - RaiseLocalEvent(effect, ref ev); + SetStatusEffectEndTime((effect.Value, effectComp), _timing.CurTime + duration); return true; } - private void ShowAlertIfNeeded(StatusEffectComponent effectComp) + private void AddStatusEffectTime(EntityUid effect, TimeSpan delta) { - 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.TryComp(effect, out var effectComp)) + return; + + // 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 SetStatusEffectEndTime(Entity ent, TimeSpan? endTime) + { + 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); /// [ByRefEvent] public record struct BeforeStatusEffectAddedEvent(EntProtoId Effect, bool Cancelled = false); + +/// +/// Raised on an effect entity when its is updated in any way. +/// +/// The entity the effect is attached to. +/// The new end time of the status effect, included for convenience. +[ByRefEvent] +public record struct StatusEffectEndTimeUpdatedEvent(EntityUid Target, TimeSpan? EndTime);