]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Improvements and fixups for New Status Effect API (#38660)
authorPrincess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com>
Wed, 2 Jul 2025 17:46:30 +0000 (10:46 -0700)
committerGitHub <noreply@github.com>
Wed, 2 Jul 2025 17:46:30 +0000 (20:46 +0300)
Content.Server/Drowsiness/DrowsinessSystem.cs
Content.Server/Traits/Assorted/NarcolepsySystem.cs
Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs
Content.Shared/SSDIndicator/SSDIndicatorSystem.cs
Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs
Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs

index 0489b16f51512ad74fc5697a96e775404d2f28d7..6de270abcc4e5a3ef92451019c5212786caaccde 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Server.StatusEffectNew;
+using Content.Server.StatusEffectNew;
 using Content.Shared.Bed.Sleep;
 using Content.Shared.Drowsiness;
 using Content.Shared.StatusEffectNew;
@@ -47,7 +47,7 @@ public sealed class DrowsinessSystem : SharedDrowsinessSystem
             // Make sure the sleep time doesn't cut into the time to next incident.
             drowsiness.NextIncidentTime += duration;
 
-            _statusEffects.TryAddStatusEffect(statusEffect.AppliedTo.Value, SleepingSystem.StatusEffectForcedSleeping, duration);
+            _statusEffects.TryAddStatusEffectDuration(statusEffect.AppliedTo.Value, SleepingSystem.StatusEffectForcedSleeping, duration);
         }
     }
 }
index 9d0ff9470a79b1773921207d6d6322747a58247c..b0746fa3776cfc71e7442fa48d90a1df1381b991 100644 (file)
@@ -53,7 +53,7 @@ public sealed class NarcolepsySystem : EntitySystem
             // Make sure the sleep time doesn't cut into the time to next incident.
             narcolepsy.NextIncidentTime += duration;
 
-            _statusEffects.TryAddStatusEffect(uid, SleepingSystem.StatusEffectForcedSleeping, TimeSpan.FromSeconds(duration));
+            _statusEffects.TryAddStatusEffectDuration(uid, SleepingSystem.StatusEffectForcedSleeping, TimeSpan.FromSeconds(duration));
         }
     }
 }
index 33021ecdabddd8dcc7f27cc59da970b802591451..5ebb8aad1b592bf1554c2395ed92bc65c2d840ad 100644 (file)
@@ -20,7 +20,7 @@ public sealed partial class ModifyStatusEffect : EntityEffect
     public float Time = 2.0f;
 
     /// <remarks>
-    /// true - refresh status effect time, false - accumulate status effect time.
+    /// true - refresh status effect time (update to greater value), false - accumulate status effect time.
     /// </remarks>
     [DataField]
     public bool Refresh = true;
@@ -40,16 +40,20 @@ public sealed partial class ModifyStatusEffect : EntityEffect
         if (args is EntityEffectReagentArgs reagentArgs)
             time *= reagentArgs.Scale.Float();
 
+        var duration = TimeSpan.FromSeconds(time);
         switch (Type)
         {
             case StatusEffectMetabolismType.Add:
-                statusSys.TryAddStatusEffect(args.TargetEntity, EffectProto, TimeSpan.FromSeconds(time), Refresh);
+                if (Refresh)
+                    statusSys.TryUpdateStatusEffectDuration(args.TargetEntity, EffectProto, duration);
+                else
+                    statusSys.TryAddStatusEffectDuration(args.TargetEntity, EffectProto, duration);
                 break;
             case StatusEffectMetabolismType.Remove:
-                statusSys.TryAddTime(args.TargetEntity, EffectProto, -TimeSpan.FromSeconds(time));
+                statusSys.TryAddTime(args.TargetEntity, EffectProto, -duration);
                 break;
             case StatusEffectMetabolismType.Set:
-                statusSys.TrySetTime(args.TargetEntity, EffectProto, TimeSpan.FromSeconds(time));
+                statusSys.TrySetStatusEffectDuration(args.TargetEntity, EffectProto, duration);
                 break;
         }
     }
index a13b6b915c13f1e3d2a10b4e95cbc2941915f6ff..ca7d73ac837dde97ebbc947226e15e6db5f2fbf7 100644 (file)
@@ -85,7 +85,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
                 ssd.FallAsleepTime <= _timing.CurTime &&
                 !TerminatingOrDeleted(uid))
             {
-                _statusEffects.TryAddStatusEffect(uid, StatusEffectSSDSleeping);
+                _statusEffects.TrySetStatusEffectDuration(uid, StatusEffectSSDSleeping, null);
             }
         }
     }
index c836b8205c83b3f6227ec42c81780bee74c02625..9e6ed4d7ff32560946da1345674ee21eca8ca8ca 100644 (file)
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
 using Content.Shared.Alert;
 using Content.Shared.StatusEffectNew.Components;
 using Content.Shared.Whitelist;
@@ -77,53 +78,59 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
         effectComp.EndEffectTime += delta;
         Dirty(effect, effectComp);
 
-        if (effectComp is { AppliedTo: not null, Alert: not null })
+        ShowAlertIfNeeded(effectComp);
+    }
+
+    private void SetStatusEffectTime(EntityUid effect, TimeSpan? duration)
+    {
+        if (!_effectQuery.TryComp(effect, out var effectComp))
+            return;
+
+        if (duration is null)
         {
-            (TimeSpan Start, TimeSpan End)? cooldown = effectComp.EndEffectTime is null
-                ? null
-                : (_timing.CurTime, effectComp.EndEffectTime.Value);
-            _alerts.ShowAlert(
-                effectComp.AppliedTo.Value,
-                effectComp.Alert.Value,
-                cooldown: cooldown
-            );
+            if(effectComp.EndEffectTime is null)
+                return;
+
+            effectComp.EndEffectTime = null;
         }
+        else
+            effectComp.EndEffectTime = _timing.CurTime + duration;
+
+        Dirty(effect, effectComp);
+
+        ShowAlertIfNeeded(effectComp);
     }
 
-    private void SetStatusEffectTime(EntityUid effect, TimeSpan duration)
+    private void UpdateStatusEffectTime(EntityUid effect, TimeSpan? duration)
     {
         if (!_effectQuery.TryComp(effect, out var effectComp))
             return;
 
-        effectComp.EndEffectTime = _timing.CurTime + duration;
-        Dirty(effect, effectComp);
+        // It's already infinitely long
+        if (effectComp.EndEffectTime is null)
+            return;
 
-        if (effectComp is { AppliedTo: not null, Alert: not null })
+        if (duration is null)
+            effectComp.EndEffectTime = null;
+        else
         {
-            (TimeSpan, TimeSpan)? cooldown = effectComp.EndEffectTime is null
-                ? null
-                : (_timing.CurTime, effectComp.EndEffectTime.Value);
-            _alerts.ShowAlert(
-                effectComp.AppliedTo.Value,
-                effectComp.Alert.Value,
-                cooldown: cooldown
-            );
+            var newEndTime = _timing.CurTime + duration;
+            if (effectComp.EndEffectTime >= newEndTime)
+                return;
+
+            effectComp.EndEffectTime = newEndTime;
         }
+
+        Dirty(effect, effectComp);
+
+        ShowAlertIfNeeded(effectComp);
     }
 
+
     private void OnStatusEffectApplied(Entity<StatusEffectComponent> ent, ref StatusEffectAppliedEvent args)
     {
-        if (ent.Comp is { AppliedTo: not null, Alert: not null })
-        {
-            (TimeSpan, TimeSpan)? cooldown = ent.Comp.EndEffectTime is null
-                ? null
-                : (_timing.CurTime, ent.Comp.EndEffectTime.Value);
-            _alerts.ShowAlert(
-                ent.Comp.AppliedTo.Value,
-                ent.Comp.Alert.Value,
-                cooldown: cooldown
-            );
-        }
+        StatusEffectComponent statusEffect = ent;
+        ShowAlertIfNeeded(statusEffect);
     }
 
     private void OnStatusEffectRemoved(Entity<StatusEffectComponent> ent, ref StatusEffectRemovedEvent args)
@@ -154,6 +161,64 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
 
         return true;
     }
+
+    /// <summary>
+    /// Attempts to add a status effect to the specified entity. Returns True if the effect is added, does not check if one
+    /// already exists as it's intended to be called after a check for an existing effect has already failed.
+    /// </summary>
+    /// <param name="target">The target entity to which the effect should be added.</param>
+    /// <param name="effectProto">ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it.</param>
+    /// <param name="duration">Duration of status effect. Leave null and the effect will be permanent until it is removed using <c>TryRemoveStatusEffect</c>.</param>
+    /// <param name="statusEffect">The EntityUid of the status effect we have just created or null if we couldn't create one.</param>
+    private bool TryAddStatusEffect(
+        EntityUid target,
+        EntProtoId effectProto,
+        [NotNullWhen(true)] out EntityUid? statusEffect,
+        TimeSpan? duration = null
+    )
+    {
+        statusEffect = null;
+        if (!CanAddStatusEffect(target, effectProto))
+            return false;
+
+        var container = EnsureComp<StatusEffectContainerComponent>(target);
+
+        //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);
+
+        return true;
+    }
+
+    private void ShowAlertIfNeeded(StatusEffectComponent effectComp)
+    {
+        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
+            );
+        }
+    }
 }
 
 /// <summary>
index 2d1ec89c9def0e8535581e0b14a07fec01eccd96..5e20cea1bb8516a01820835c40b92e938db0502e 100644 (file)
@@ -7,80 +7,94 @@ namespace Content.Shared.StatusEffectNew;
 public abstract partial class SharedStatusEffectsSystem
 {
     /// <summary>
-    /// Attempts to add a status effect to the specified entity. Returns True if the effect is added or it already exists
-    /// and has been successfully extended in time, returns False if the status effect cannot be applied to this entity,
-    /// or for any other reason.
+    /// Increments duration of status effect by <see cref="duration"/>.
+    /// Tries to add status effect if it is not yet present on entity.
     /// </summary>
     /// <param name="target">The target entity to which the effect should be added.</param>
     /// <param name="effectProto">ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it.</param>
     /// <param name="duration">Duration of status effect. Leave null and the effect will be permanent until it is removed using <c>TryRemoveStatusEffect</c>.</param>
-    /// <param name="resetCooldown">
-    /// If True, the effect duration time will be reset and reapplied. If False, the effect duration time will be overlaid with the existing one.
-    /// In the other case, the effect will either be added for the specified time or its time will be extended for the specified time.
-    /// </param>
     /// <param name="statusEffect">The EntityUid of the status effect we have just created or null if it doesn't exist.</param>
-    public bool TryAddStatusEffect(
+    /// <returns>True if effect exists and its duration is set properly, false in case effect cannot be applied.</returns>
+    public bool TryAddStatusEffectDuration(
         EntityUid target,
         EntProtoId effectProto,
-        out EntityUid? statusEffect,
-        TimeSpan? duration = null,
-        bool resetCooldown = false
+        [NotNullWhen(true)] out EntityUid? statusEffect,
+        TimeSpan duration
     )
     {
-        statusEffect = null;
-        if (TryGetStatusEffect(target, effectProto, out var existingEffect))
-        {
-            statusEffect = existingEffect;
-            //We don't need to add the effect if it already exists
-            if (duration is null)
-                return true;
-
-            if (resetCooldown)
-                SetStatusEffectTime(existingEffect.Value, duration.Value);
-            else
-                AddStatusEffectTime(existingEffect.Value, duration.Value);
+        if (!TryGetStatusEffect(target, effectProto, out statusEffect))
+            return TryAddStatusEffect(target, effectProto, out statusEffect, duration);
 
-            return true;
-        }
-
-        if (!CanAddStatusEffect(target, effectProto))
-            return false;
+        AddStatusEffectTime(statusEffect.Value, duration);
 
-        var container = EnsureComp<StatusEffectContainerComponent>(target);
+        return true;
+    }
 
-        //And only if all checks passed we spawn the effect
-        var effect = PredictedSpawnAttachedTo(effectProto, Transform(target).Coordinates);
-        statusEffect = effect;
-        _transform.SetParent(effect, target);
-        if (!_effectQuery.TryComp(effect, out var effectComp))
-            return false;
 
-        if (duration != null)
-            effectComp.EndEffectTime = _timing.CurTime + duration;
+    ///<inheritdoc cref="TryAddStatusEffectDuration(Robust.Shared.GameObjects.EntityUid,Robust.Shared.Prototypes.EntProtoId,out Robust.Shared.GameObjects.EntityUid?,System.TimeSpan)"/>
+    public bool TryAddStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan duration)
+    {
+        return TryAddStatusEffectDuration(target, effectProto, out _, duration);
+    }
 
-        container.ActiveStatusEffects.Add(effect);
-        effectComp.AppliedTo = target;
-        Dirty(target, container);
-        Dirty(effect, effectComp);
+    /// <summary>
+    /// Sets duration of status effect by <see cref="duration"/>.
+    /// Tries to add status effect if it is not yet present on entity.
+    /// </summary>
+    /// <param name="target">The target entity to which the effect should be added.</param>
+    /// <param name="effectProto">ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it.</param>
+    /// <param name="duration">Duration of status effect. Leave null and the effect will be permanent until it is removed using <c>TryRemoveStatusEffect</c>.</param>
+    /// <param name="statusEffect">The EntityUid of the status effect we have just created or null if it doesn't exist.</param>
+    /// <returns>True if effect exists and its duration is set properly, false in case effect cannot be applied.</returns>
+    public bool TrySetStatusEffectDuration(
+        EntityUid target,
+        EntProtoId effectProto,
+        [NotNullWhen(true)] out EntityUid? statusEffect,
+        TimeSpan? duration = null
+    )
+    {
+        if (!TryGetStatusEffect(target, effectProto, out statusEffect))
+            return TryAddStatusEffect(target, effectProto, out statusEffect, duration);
 
-        var ev = new StatusEffectAppliedEvent(target);
-        RaiseLocalEvent(effect, ref ev);
+        SetStatusEffectTime(statusEffect.Value, duration);
 
         return true;
     }
 
+    /// <inheritdoc cref="TrySetStatusEffectDuration(Robust.Shared.GameObjects.EntityUid,Robust.Shared.Prototypes.EntProtoId,out Robust.Shared.GameObjects.EntityUid?,System.TimeSpan?)"/>
+    public bool TrySetStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null)
+    {
+        return TrySetStatusEffectDuration(target, effectProto, out _, duration);
+    }
+
     /// <summary>
-    /// An overload of <see cref="TryAddStatusEffect(EntityUid,EntProtoId,out EntityUid?,TimeSpan?,bool)"/>
-    /// that doesn't return a status effect EntityUid.
+    /// Updates duration of effect to larger value between provided <see cref="duration"/> and current effect duration.
+    /// Tries to add status effect if it is not yet present on entity.
     /// </summary>
-    public bool TryAddStatusEffect(
+    /// <param name="target">The target entity to which the effect should be added.</param>
+    /// <param name="effectProto">ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it.</param>
+    /// <param name="duration">Duration of status effect. Leave null and the effect will be permanent until it is removed using <c>TryRemoveStatusEffect</c>.</param>
+    /// <param name="statusEffect">The EntityUid of the status effect we have just created or null if it doesn't exist.</param>
+    /// <returns>True if effect exists and its duration is set properly, false in case effect cannot be applied.</returns>
+    public bool TryUpdateStatusEffectDuration(
         EntityUid target,
         EntProtoId effectProto,
-        TimeSpan? duration = null,
-        bool resetCooldown = false
+        [NotNullWhen(true)] out EntityUid? statusEffect,
+        TimeSpan? duration = null
     )
     {
-        return TryAddStatusEffect(target, effectProto, out _, duration, resetCooldown);
+        if (!TryGetStatusEffect(target, effectProto, out statusEffect))
+            return TryAddStatusEffect(target, effectProto, out statusEffect, duration);
+
+        UpdateStatusEffectTime(statusEffect.Value, duration);
+
+        return true;
+    }
+
+    /// <inheritdoc cref="TryUpdateStatusEffectDuration(Robust.Shared.GameObjects.EntityUid,Robust.Shared.Prototypes.EntProtoId,out Robust.Shared.GameObjects.EntityUid?,System.TimeSpan?)"/>
+    public bool TryUpdateStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null)
+    {
+        return TryUpdateStatusEffectDuration(target, effectProto, out _, duration);
     }
 
     /// <summary>
@@ -190,6 +204,36 @@ public abstract partial class SharedStatusEffectsSystem
         return false;
     }
 
+    /// <summary>
+    /// Attempts to get the maximum time left for a given Status Effect Component, returns false if no such
+    /// component exists.
+    /// </summary>
+    /// <param name="uid">The target entity on which the effect is applied.</param>
+    /// <param name="time">Returns the EntityUid of the status effect with the most time left, and the end effect time
+    /// of that status effect.</param>
+    /// <returns> True if a status effect entity with the given component exists</returns>
+    public bool TryGetMaxTime<T>(EntityUid uid, out (EntityUid EffectEnt, TimeSpan? EndEffectTime) time) where T : IComponent
+    {
+        time = default;
+        if (!TryEffectsWithComp<T>(uid, out var status))
+            return false;
+
+        time.Item2 = TimeSpan.Zero;
+
+        foreach (var effect in status)
+        {
+            if (effect.Comp2.EndEffectTime == null)
+            {
+                time = (effect.Owner, null);
+                return true;
+            }
+
+            if (effect.Comp2.EndEffectTime > time.Item2)
+                time = (effect.Owner, effect.Comp2.EndEffectTime);
+        }
+        return true;
+    }
+
     /// <summary>
     /// Attempts to edit the remaining time for a status effect on an entity.
     /// </summary>