diff --git a/Content.Server/Drowsiness/DrowsinessSystem.cs b/Content.Server/Drowsiness/DrowsinessSystem.cs index 0489b16f51..6de270abcc 100644 --- a/Content.Server/Drowsiness/DrowsinessSystem.cs +++ b/Content.Server/Drowsiness/DrowsinessSystem.cs @@ -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); } } } diff --git a/Content.Server/Traits/Assorted/NarcolepsySystem.cs b/Content.Server/Traits/Assorted/NarcolepsySystem.cs index 9d0ff9470a..b0746fa377 100644 --- a/Content.Server/Traits/Assorted/NarcolepsySystem.cs +++ b/Content.Server/Traits/Assorted/NarcolepsySystem.cs @@ -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)); } } } diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs index 33021ecdab..5ebb8aad1b 100644 --- a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs +++ b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs @@ -20,7 +20,7 @@ public sealed partial class ModifyStatusEffect : EntityEffect public float Time = 2.0f; /// - /// true - refresh status effect time, false - accumulate status effect time. + /// true - refresh status effect time (update to greater value), false - accumulate status effect time. /// [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; } } diff --git a/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs b/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs index a13b6b915c..ca7d73ac83 100644 --- a/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs +++ b/Content.Shared/SSDIndicator/SSDIndicatorSystem.cs @@ -85,7 +85,7 @@ public sealed class SSDIndicatorSystem : EntitySystem ssd.FallAsleepTime <= _timing.CurTime && !TerminatingOrDeleted(uid)) { - _statusEffects.TryAddStatusEffect(uid, StatusEffectSSDSleeping); + _statusEffects.TrySetStatusEffectDuration(uid, StatusEffectSSDSleeping, null); } } } diff --git a/Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs b/Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs index c836b8205c..9e6ed4d7ff 100644 --- a/Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs +++ b/Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs @@ -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 }) - { - (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 - ); - } + ShowAlertIfNeeded(effectComp); } - private void SetStatusEffectTime(EntityUid effect, TimeSpan duration) + private void SetStatusEffectTime(EntityUid effect, TimeSpan? duration) { if (!_effectQuery.TryComp(effect, out var effectComp)) return; - effectComp.EndEffectTime = _timing.CurTime + duration; + if (duration is null) + { + if(effectComp.EndEffectTime is null) + return; + + effectComp.EndEffectTime = null; + } + else + effectComp.EndEffectTime = _timing.CurTime + duration; + Dirty(effect, 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 - ); - } + ShowAlertIfNeeded(effectComp); } + private void UpdateStatusEffectTime(EntityUid effect, TimeSpan? duration) + { + if (!_effectQuery.TryComp(effect, out var effectComp)) + return; + + // It's already infinitely long + if (effectComp.EndEffectTime is null) + return; + + if (duration is null) + effectComp.EndEffectTime = null; + else + { + var newEndTime = _timing.CurTime + duration; + if (effectComp.EndEffectTime >= newEndTime) + return; + + effectComp.EndEffectTime = newEndTime; + } + + Dirty(effect, effectComp); + + ShowAlertIfNeeded(effectComp); + } + + private void OnStatusEffectApplied(Entity 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 ent, ref StatusEffectRemovedEvent args) @@ -154,6 +161,64 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem return true; } + + /// + /// 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. + /// + /// The target entity to which the effect should be added. + /// ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it. + /// Duration of status effect. Leave null and the effect will be permanent until it is removed using TryRemoveStatusEffect. + /// The EntityUid of the status effect we have just created or null if we couldn't create one. + 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(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 + ); + } + } } /// diff --git a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs index 2d1ec89c9d..5e20cea1bb 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs @@ -7,80 +7,94 @@ namespace Content.Shared.StatusEffectNew; public abstract partial class SharedStatusEffectsSystem { /// - /// 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 . + /// Tries to add status effect if it is not yet present on entity. /// /// The target entity to which the effect should be added. /// ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it. /// Duration of status effect. Leave null and the effect will be permanent until it is removed using TryRemoveStatusEffect. - /// - /// 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. - /// /// The EntityUid of the status effect we have just created or null if it doesn't exist. - public bool TryAddStatusEffect( + /// True if effect exists and its duration is set properly, false in case effect cannot be applied. + 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 (!TryGetStatusEffect(target, effectProto, out statusEffect)) + return TryAddStatusEffect(target, effectProto, out statusEffect, duration); - if (resetCooldown) - SetStatusEffectTime(existingEffect.Value, duration.Value); - else - AddStatusEffectTime(existingEffect.Value, duration.Value); - - return true; - } - - if (!CanAddStatusEffect(target, effectProto)) - return false; - - var container = EnsureComp(target); - - //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; - - container.ActiveStatusEffects.Add(effect); - effectComp.AppliedTo = target; - Dirty(target, container); - Dirty(effect, effectComp); - - var ev = new StatusEffectAppliedEvent(target); - RaiseLocalEvent(effect, ref ev); + AddStatusEffectTime(statusEffect.Value, duration); return true; } + + /// + public bool TryAddStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan duration) + { + return TryAddStatusEffectDuration(target, effectProto, out _, duration); + } + /// - /// An overload of - /// that doesn't return a status effect EntityUid. + /// Sets duration of status effect by . + /// Tries to add status effect if it is not yet present on entity. /// - public bool TryAddStatusEffect( + /// The target entity to which the effect should be added. + /// ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it. + /// Duration of status effect. Leave null and the effect will be permanent until it is removed using TryRemoveStatusEffect. + /// The EntityUid of the status effect we have just created or null if it doesn't exist. + /// True if effect exists and its duration is set properly, false in case effect cannot be applied. + public bool TrySetStatusEffectDuration( 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); + + SetStatusEffectTime(statusEffect.Value, duration); + + return true; + } + + /// + public bool TrySetStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null) + { + return TrySetStatusEffectDuration(target, effectProto, out _, duration); + } + + /// + /// Updates duration of effect to larger value between provided and current effect duration. + /// Tries to add status effect if it is not yet present on entity. + /// + /// The target entity to which the effect should be added. + /// ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it. + /// Duration of status effect. Leave null and the effect will be permanent until it is removed using TryRemoveStatusEffect. + /// The EntityUid of the status effect we have just created or null if it doesn't exist. + /// True if effect exists and its duration is set properly, false in case effect cannot be applied. + public bool TryUpdateStatusEffectDuration( + 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); + + UpdateStatusEffectTime(statusEffect.Value, duration); + + return true; + } + + /// + public bool TryUpdateStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null) + { + return TryUpdateStatusEffectDuration(target, effectProto, out _, duration); } /// @@ -190,6 +204,36 @@ public abstract partial class SharedStatusEffectsSystem return false; } + /// + /// Attempts to get the maximum time left for a given Status Effect Component, returns false if no such + /// component exists. + /// + /// The target entity on which the effect is applied. + /// Returns the EntityUid of the status effect with the most time left, and the end effect time + /// of that status effect. + /// True if a status effect entity with the given component exists + public bool TryGetMaxTime(EntityUid uid, out (EntityUid EffectEnt, TimeSpan? EndEffectTime) time) where T : IComponent + { + time = default; + if (!TryEffectsWithComp(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; + } + /// /// Attempts to edit the remaining time for a status effect on an entity. ///