]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
New status effect system (#37238)
authorRed <96445749+TheShuEd@users.noreply.github.com>
Wed, 25 Jun 2025 11:41:35 +0000 (14:41 +0300)
committerGitHub <noreply@github.com>
Wed, 25 Jun 2025 11:41:35 +0000 (07:41 -0400)
* spectra

* documentation

* added into liquid anomaly

* Update TemporaryStealthComponent.cs

* Update TemporaryStealthComponent.cs

* integrated

* new system

* mark old status effect system as obsolete

* ForcedSleeping new status effect

* work with reagents

* networking???

* Revert "integrated"

This reverts commit bca02b82bae18ae131af593d7eb86e6de2745157.

* Revert "Update TemporaryStealthComponent.cs"

This reverts commit 4a5be8c4b704a0d1ff9544b2e245d8b2701ec580.

* Revert "Update TemporaryStealthComponent.cs"

This reverts commit a4875bcb41347638854bd723d96a51c3e6d38034.

* Revert "added into liquid anomaly"

This reverts commit df5086b14bb35f1467158a36807c0f2163a16d99.

* Revert "documentation"

This reverts commit 3629b9466758cbdfa4dd5e67ece122fa2f181138.

* Revert "spectra"

This reverts commit 2d03d88c16d16ad6831c19a7921b84600daeb284.

* drowsiness status effect remove

* reagents work

* polish, remove test changes

* first Fildrance review part

* Update misc.yml

* more fildrance review

* final part

* fix trailing spaces

* sleeping status effect

* drowsiness status effect

* Create ModifyStatusEffect.cs

* some tweak

* Yay!!! Manual networking

* minor nitpick

* oopsie

* refactor: xml-docs, notnullwhen attributes, whitespaces

* fildrance and emo review

* refactor: simplify check in SharedStatusEffectsSystem by using pattern matching, TryEffectsWithComp now returns set of Entity<T, StatusEffectComponent>

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
35 files changed:
Content.Client/Drowsiness/DrowsinessOverlay.cs
Content.Client/Drowsiness/DrowsinessSystem.cs
Content.Client/StatusEffectNew/ClientStatusEffectsSystem.cs [new file with mode: 0644]
Content.Server/Drowsiness/DrowsinessSystem.cs
Content.Server/StatusEffectNew/StatusEffectsSystem.cs [new file with mode: 0644]
Content.Server/Traits/Assorted/NarcolepsySystem.cs
Content.Shared/Bed/Sleep/ForcedSleepingComponent.cs [deleted file]
Content.Shared/Bed/Sleep/ForcedSleepingStatusEffectComponent.cs [new file with mode: 0644]
Content.Shared/Bed/Sleep/SleepingSystem.cs
Content.Shared/Damage/Systems/SharedGodmodeSystem.cs
Content.Shared/Drowsiness/DrowsinessStatusEffectComponent.cs [moved from Content.Shared/Drowsiness/DrowsinessComponent.cs with 56% similarity]
Content.Shared/Drowsiness/DrowsinessSystem.cs
Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffect.cs
Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs [new file with mode: 0644]
Content.Shared/SSDIndicator/SSDIndicatorSystem.cs
Content.Shared/StatusEffect/StatusEffectsSystem.cs
Content.Shared/StatusEffectNew/Components/StatusEffectComponent.cs [new file with mode: 0644]
Content.Shared/StatusEffectNew/Components/StatusEffectContainerComponent.cs [new file with mode: 0644]
Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs [new file with mode: 0644]
Content.Shared/StatusEffectNew/StatusEffectNewSystem.API.cs [new file with mode: 0644]
Resources/Locale/en-US/entity-categories.ftl
Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml
Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml
Resources/Prototypes/Entities/Mobs/Player/dragon.yml
Resources/Prototypes/Entities/Mobs/Species/base.yml
Resources/Prototypes/Entities/StatusEffects/misc.yml [new file with mode: 0644]
Resources/Prototypes/Entities/categories.yml
Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml
Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml
Resources/Prototypes/Reagents/Consumable/Drink/soda.yml
Resources/Prototypes/Reagents/gases.yml
Resources/Prototypes/Reagents/medicine.yml
Resources/Prototypes/Reagents/narcotics.yml
Resources/Prototypes/Reagents/toxins.yml
Resources/Prototypes/status_effects.yml

index a316f31ae645919c06dc3eec8612f66bcdf00172..9705aa73139498554887dfd2df1a8dea8348c04f 100644 (file)
@@ -1,5 +1,7 @@
+using Content.Shared.Bed.Sleep;
 using Content.Shared.Drowsiness;
-using Content.Shared.StatusEffect;
+using Content.Shared.StatusEffectNew;
+using Content.Shared.StatusEffectNew.Components;
 using Robust.Client.Graphics;
 using Robust.Client.Player;
 using Robust.Shared.Enums;
@@ -15,11 +17,14 @@ 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!;
 
     public override OverlaySpace Space => OverlaySpace.WorldSpace;
     public override bool RequestScreenTexture => true;
     private readonly ShaderInstance _drowsinessShader;
 
+    private EntityQuery<StatusEffectComponent> _statusQuery;
+
     public float CurrentPower = 0.0f;
 
     private const float PowerDivisor = 250.0f;
@@ -29,6 +34,9 @@ public sealed class DrowsinessOverlay : Overlay
     public DrowsinessOverlay()
     {
         IoCManager.InjectDependencies(this);
+        _statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
+
+        _statusQuery = _entityManager.GetEntityQuery<StatusEffectComponent>();
         _drowsinessShader = _prototypeManager.Index<ShaderPrototype>("Drowsiness").InstanceUnique();
     }
 
@@ -39,16 +47,21 @@ public sealed class DrowsinessOverlay : Overlay
         if (playerEntity == null)
             return;
 
-        if (!_entityManager.HasComponent<DrowsinessComponent>(playerEntity)
-            || !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
+        if (!_statusEffects.TryEffectsWithComp<DrowsinessStatusEffectComponent>(playerEntity, out var drowsinessEffects))
             return;
 
-        var statusSys = _sysMan.GetEntitySystem<StatusEffectsSystem>();
-        if (!statusSys.TryGetTime(playerEntity.Value, SharedDrowsinessSystem.DrowsinessKey, out var time, status))
+        TimeSpan? remainingTime = TimeSpan.Zero;
+        foreach (var (_, _, statusEffectComp) in drowsinessEffects)
+        {
+            if (statusEffectComp.EndEffectTime > remainingTime)
+                remainingTime = statusEffectComp.EndEffectTime;
+        }
+
+        if (remainingTime is null)
             return;
 
         var curTime = _timing.CurTime;
-        var timeLeft = (float)(time.Value.Item2 - curTime).TotalSeconds;
+        var timeLeft = (float)(remainingTime - curTime).Value.TotalSeconds;
 
         CurrentPower += 8f * (0.5f * timeLeft - CurrentPower) * args.DeltaSeconds / (timeLeft + 1);
     }
index bc8862b19d281bb22a8ec4f5dd058be1694dc2d0..152d6ebdf8c1e978a27d578477d8c43c0a7042b3 100644 (file)
@@ -1,7 +1,7 @@
 using Content.Shared.Drowsiness;
+using Content.Shared.StatusEffectNew;
 using Robust.Client.Graphics;
 using Robust.Client.Player;
-using Robust.Shared.Player;
 
 namespace Content.Client.Drowsiness;
 
@@ -9,6 +9,7 @@ public sealed class DrowsinessSystem : SharedDrowsinessSystem
 {
     [Dependency] private readonly IPlayerManager _player = default!;
     [Dependency] private readonly IOverlayManager _overlayMan = default!;
+    [Dependency] private readonly SharedStatusEffectsSystem _statusEffects = default!;
 
     private DrowsinessOverlay _overlay = default!;
 
@@ -16,35 +17,47 @@ public sealed class DrowsinessSystem : SharedDrowsinessSystem
     {
         base.Initialize();
 
-        SubscribeLocalEvent<DrowsinessComponent, ComponentInit>(OnDrowsinessInit);
-        SubscribeLocalEvent<DrowsinessComponent, ComponentShutdown>(OnDrowsinessShutdown);
+        SubscribeLocalEvent<DrowsinessStatusEffectComponent, StatusEffectAppliedEvent>(OnDrowsinessApply);
+        SubscribeLocalEvent<DrowsinessStatusEffectComponent, StatusEffectRemovedEvent>(OnDrowsinessShutdown);
 
-        SubscribeLocalEvent<DrowsinessComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
-        SubscribeLocalEvent<DrowsinessComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
+        SubscribeLocalEvent<DrowsinessStatusEffectComponent, StatusEffectPlayerAttachedEvent>(OnStatusEffectPlayerAttached);
+        SubscribeLocalEvent<DrowsinessStatusEffectComponent, StatusEffectPlayerDetachedEvent>(OnStatusEffectPlayerDetached);
 
         _overlay = new();
     }
 
-    private void OnPlayerAttached(EntityUid uid, DrowsinessComponent component, LocalPlayerAttachedEvent args)
+    private void OnDrowsinessApply(Entity<DrowsinessStatusEffectComponent> ent, ref StatusEffectAppliedEvent args)
     {
-        _overlayMan.AddOverlay(_overlay);
+        if (_player.LocalEntity == args.Target)
+            _overlayMan.AddOverlay(_overlay);
     }
 
-    private void OnPlayerDetached(EntityUid uid, DrowsinessComponent component, LocalPlayerDetachedEvent args)
+    private void OnDrowsinessShutdown(Entity<DrowsinessStatusEffectComponent> ent, ref StatusEffectRemovedEvent args)
     {
-        _overlay.CurrentPower = 0;
-        _overlayMan.RemoveOverlay(_overlay);
+        if (_player.LocalEntity != args.Target)
+            return;
+
+        if (!_statusEffects.HasEffectComp<DrowsinessStatusEffectComponent>(_player.LocalEntity.Value))
+        {
+            _overlay.CurrentPower = 0;
+            _overlayMan.RemoveOverlay(_overlay);
+        }
     }
 
-    private void OnDrowsinessInit(EntityUid uid, DrowsinessComponent component, ComponentInit args)
+    private void OnStatusEffectPlayerAttached(Entity<DrowsinessStatusEffectComponent> ent, ref StatusEffectPlayerAttachedEvent args)
     {
-        if (_player.LocalEntity == uid)
-            _overlayMan.AddOverlay(_overlay);
+        if (_player.LocalEntity != args.Target)
+            return;
+
+        _overlayMan.AddOverlay(_overlay);
     }
 
-    private void OnDrowsinessShutdown(EntityUid uid, DrowsinessComponent component, ComponentShutdown args)
+    private void OnStatusEffectPlayerDetached(Entity<DrowsinessStatusEffectComponent> ent, ref StatusEffectPlayerDetachedEvent args)
     {
-        if (_player.LocalEntity == uid)
+        if (_player.LocalEntity != args.Target)
+            return;
+
+        if (!_statusEffects.HasEffectComp<DrowsinessStatusEffectComponent>(_player.LocalEntity.Value))
         {
             _overlay.CurrentPower = 0;
             _overlayMan.RemoveOverlay(_overlay);
diff --git a/Content.Client/StatusEffectNew/ClientStatusEffectsSystem.cs b/Content.Client/StatusEffectNew/ClientStatusEffectsSystem.cs
new file mode 100644 (file)
index 0000000..e35c091
--- /dev/null
@@ -0,0 +1,50 @@
+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 2511bc790e9bc902472a6a212c8ae2dc9840a2fe..0489b16f51512ad74fc5697a96e775404d2f28d7 100644 (file)
@@ -1,6 +1,8 @@
-using Content.Shared.Bed.Sleep;
+using Content.Server.StatusEffectNew;
+using Content.Shared.Bed.Sleep;
 using Content.Shared.Drowsiness;
-using Content.Shared.StatusEffect;
+using Content.Shared.StatusEffectNew;
+using Content.Shared.StatusEffectNew.Components;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
 
@@ -8,9 +10,6 @@ namespace Content.Server.Drowsiness;
 
 public sealed class DrowsinessSystem : SharedDrowsinessSystem
 {
-    [ValidatePrototypeId<StatusEffectPrototype>]
-    private const string SleepKey = "ForcedSleep"; // Same one used by N2O and other sleep chems.
-
     [Dependency] private readonly IGameTiming _timing = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
@@ -18,33 +17,37 @@ public sealed class DrowsinessSystem : SharedDrowsinessSystem
     /// <inheritdoc/>
     public override void Initialize()
     {
-        SubscribeLocalEvent<DrowsinessComponent, ComponentStartup>(OnInit);
+        SubscribeLocalEvent<DrowsinessStatusEffectComponent, StatusEffectAppliedEvent>(OnEffectApplied);
     }
 
-    private void OnInit(EntityUid uid, DrowsinessComponent component, ComponentStartup args)
+    private void OnEffectApplied(Entity<DrowsinessStatusEffectComponent> ent, ref StatusEffectAppliedEvent args)
     {
-        component.NextIncidentTime = _timing.CurTime + TimeSpan.FromSeconds(_random.NextFloat(component.TimeBetweenIncidents.X, component.TimeBetweenIncidents.Y));
+        ent.Comp.NextIncidentTime = _timing.CurTime + TimeSpan.FromSeconds(_random.NextFloat(ent.Comp.TimeBetweenIncidents.X, ent.Comp.TimeBetweenIncidents.Y));
     }
+
     public override void Update(float frameTime)
     {
         base.Update(frameTime);
 
-        var query = EntityQueryEnumerator<DrowsinessComponent>();
-        while (query.MoveNext(out var uid, out var component))
+        var query = EntityQueryEnumerator<DrowsinessStatusEffectComponent, StatusEffectComponent>();
+        while (query.MoveNext(out var uid, out var drowsiness, out var statusEffect))
         {
-            if (_timing.CurTime < component.NextIncidentTime)
+            if (_timing.CurTime < drowsiness.NextIncidentTime)
+                continue;
+
+            if (statusEffect.AppliedTo is null)
                 continue;
 
             // Set the new time.
-            component.NextIncidentTime = _timing.CurTime + TimeSpan.FromSeconds(_random.NextFloat(component.TimeBetweenIncidents.X, component.TimeBetweenIncidents.Y));
+            drowsiness.NextIncidentTime = _timing.CurTime + TimeSpan.FromSeconds(_random.NextFloat(drowsiness.TimeBetweenIncidents.X, drowsiness.TimeBetweenIncidents.Y));
 
             // sleep duration
-            var duration = TimeSpan.FromSeconds(_random.NextFloat(component.DurationOfIncident.X, component.DurationOfIncident.Y));
+            var duration = TimeSpan.FromSeconds(_random.NextFloat(drowsiness.DurationOfIncident.X, drowsiness.DurationOfIncident.Y));
 
             // Make sure the sleep time doesn't cut into the time to next incident.
-            component.NextIncidentTime += duration;
+            drowsiness.NextIncidentTime += duration;
 
-            _statusEffects.TryAddStatusEffect<ForcedSleepingComponent>(uid, SleepKey, duration, false);
+            _statusEffects.TryAddStatusEffect(statusEffect.AppliedTo.Value, SleepingSystem.StatusEffectForcedSleeping, duration);
         }
     }
 }
diff --git a/Content.Server/StatusEffectNew/StatusEffectsSystem.cs b/Content.Server/StatusEffectNew/StatusEffectsSystem.cs
new file mode 100644 (file)
index 0000000..e5d7433
--- /dev/null
@@ -0,0 +1,23 @@
+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 e4fa1ccbc73c0aad02880e33542a2d993c4eccbc..9d0ff9470a79b1773921207d6d6322747a58247c 100644 (file)
@@ -1,5 +1,5 @@
+using Content.Server.StatusEffectNew;
 using Content.Shared.Bed.Sleep;
-using Content.Shared.StatusEffect;
 using Robust.Shared.Random;
 
 namespace Content.Server.Traits.Assorted;
@@ -9,9 +9,6 @@ namespace Content.Server.Traits.Assorted;
 /// </summary>
 public sealed class NarcolepsySystem : EntitySystem
 {
-    [ValidatePrototypeId<StatusEffectPrototype>]
-    private const string StatusEffectKey = "ForcedSleep"; // Same one used by N2O and other sleep chems.
-
     [Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
 
@@ -56,8 +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<ForcedSleepingComponent>(uid, StatusEffectKey,
-                TimeSpan.FromSeconds(duration), false);
+            _statusEffects.TryAddStatusEffect(uid, SleepingSystem.StatusEffectForcedSleeping, TimeSpan.FromSeconds(duration));
         }
     }
 }
diff --git a/Content.Shared/Bed/Sleep/ForcedSleepingComponent.cs b/Content.Shared/Bed/Sleep/ForcedSleepingComponent.cs
deleted file mode 100644 (file)
index 197e8cc..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Bed.Sleep
-{
-    /// <summary>
-    /// Prevents waking up. Use as a status effect.
-    /// </summary>
-    [NetworkedComponent, RegisterComponent]
-    public sealed partial class ForcedSleepingComponent : Component
-    {}
-}
diff --git a/Content.Shared/Bed/Sleep/ForcedSleepingStatusEffectComponent.cs b/Content.Shared/Bed/Sleep/ForcedSleepingStatusEffectComponent.cs
new file mode 100644 (file)
index 0000000..b94c1d3
--- /dev/null
@@ -0,0 +1,10 @@
+using Content.Shared.StatusEffectNew.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Bed.Sleep;
+
+/// <summary>
+/// Prevents waking up. Use only in conjunction with <see cref="StatusEffectComponent"/>, on the status effect entity.
+/// </summary>
+[NetworkedComponent, RegisterComponent]
+public sealed partial class ForcedSleepingStatusEffectComponent : Component;
index d1ca13843154abb8455f93b4874467a5736a7d95..9e1b27cfc90317d99eab1d8cc2f052b4dea0c646 100644 (file)
@@ -19,6 +19,7 @@ using Content.Shared.Sound;
 using Content.Shared.Sound.Components;
 using Content.Shared.Speech;
 using Content.Shared.StatusEffect;
+using Content.Shared.StatusEffectNew;
 using Content.Shared.Stunnable;
 using Content.Shared.Traits.Assorted;
 using Content.Shared.Verbs;
@@ -37,10 +38,12 @@ 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 _statusEffectsSystem = default!;
+    [Dependency] private readonly StatusEffectsSystem _statusEffectOld = default!;
+    [Dependency] private readonly SharedStatusEffectsSystem _statusEffectNew = default!;
 
     public static readonly EntProtoId SleepActionId = "ActionSleep";
     public static readonly EntProtoId WakeActionId = "ActionWake";
+    public static readonly EntProtoId StatusEffectForcedSleeping = "StatusEffectForcedSleeping";
 
     public override void Initialize()
     {
@@ -65,7 +68,7 @@ public sealed partial class SleepingSystem : EntitySystem
         SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb);
         SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
 
-        SubscribeLocalEvent<ForcedSleepingComponent, ComponentInit>(OnInit);
+        SubscribeLocalEvent<ForcedSleepingStatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
         SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
         SubscribeLocalEvent<SleepingComponent, EmoteAttemptEvent>(OnEmoteAttempt);
 
@@ -104,8 +107,8 @@ public sealed partial class SleepingSystem : EntitySystem
         if (args.FellAsleep)
         {
             // Expiring status effects would remove the components needed for sleeping
-            _statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "Stun");
-            _statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "KnockedDown");
+            _statusEffectOld.TryRemoveStatusEffect(ent.Owner, "Stun");
+            _statusEffectOld.TryRemoveStatusEffect(ent.Owner, "KnockedDown");
 
             EnsureComp<StunnedComponent>(ent);
             EnsureComp<KnockedDownComponent>(ent);
@@ -248,9 +251,9 @@ public sealed partial class SleepingSystem : EntitySystem
             _emitSound.SetEnabled((ent, spam), args.NewMobState == MobState.Alive);
     }
 
-    private void OnInit(Entity<ForcedSleepingComponent> ent, ref ComponentInit args)
+    private void OnStatusEffectApplied(Entity<ForcedSleepingStatusEffectComponent> ent, ref StatusEffectAppliedEvent args)
     {
-        TrySleeping(ent.Owner);
+        TrySleeping(args.Target);
     }
 
     private void Wake(Entity<SleepingComponent> ent)
@@ -307,7 +310,7 @@ public sealed partial class SleepingSystem : EntitySystem
         if (!Resolve(ent, ref ent.Comp, false))
             return false;
 
-        if (!force && HasComp<ForcedSleepingComponent>(ent))
+        if (!force && _statusEffectNew.HasEffectComp<ForcedSleepingStatusEffectComponent>(ent))
         {
             if (user != null)
             {
index b6b487430b74327ecc2764971dbb0213391fda7c..d10600ab563109e3e45ff94f76d7ead52bbf9d2a 100644 (file)
@@ -3,7 +3,7 @@ using Content.Shared.Damage.Events;
 using Content.Shared.Destructible;
 using Content.Shared.Rejuvenate;
 using Content.Shared.Slippery;
-using Content.Shared.StatusEffect;
+using Content.Shared.StatusEffectNew;
 
 namespace Content.Shared.Damage.Systems;
 
similarity index 56%
rename from Content.Shared/Drowsiness/DrowsinessComponent.cs
rename to Content.Shared/Drowsiness/DrowsinessStatusEffectComponent.cs
index 7e170ed232ef61c16c29afdfeeec89a17210f719..e509ed18ccaea2190bb0bc31eddcab16a74c8b19 100644 (file)
@@ -1,26 +1,28 @@
 using System.Numerics;
+using Content.Shared.StatusEffectNew.Components;
 using Robust.Shared.GameStates;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
 namespace Content.Shared.Drowsiness;
 
 /// <summary>
-///     Exists for use as a status effect. Adds a shader to the client that scales with the effect duration.
+/// Exists for use as a status effect. Adds a shader to the client that scales with the effect duration.
+/// Use only in conjunction with <see cref="StatusEffectComponent"/>, on the status effect entity.
 /// </summary>
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentPause]
-public sealed partial class DrowsinessComponent : Component
+public sealed partial class DrowsinessStatusEffectComponent : Component
 {
     /// <summary>
     /// The random time between sleeping incidents, (min, max).
     /// </summary>
-    [DataField(required: true)]
-    public Vector2 TimeBetweenIncidents = new Vector2(5f, 60f);
+    [DataField]
+    public Vector2 TimeBetweenIncidents = new(5f, 60f);
 
     /// <summary>
     /// The duration of sleeping incidents, (min, max).
     /// </summary>
-    [DataField(required: true)]
-    public Vector2 DurationOfIncident = new Vector2(2, 5);
+    [DataField]
+    public Vector2 DurationOfIncident = new(2, 5);
 
     [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
     [AutoPausedField]
index 97d7c0952dc3fa136992df30beccb3b06aa17f6d..d1e84f5ff6075e292596f03f8f7f35d1298774cf 100644 (file)
@@ -1,9 +1,5 @@
-using Content.Shared.StatusEffect;
-
 namespace Content.Shared.Drowsiness;
 
 public abstract class SharedDrowsinessSystem : EntitySystem
 {
-    [ValidatePrototypeId<StatusEffectPrototype>]
-    public const string DrowsinessKey = "Drowsiness";
 }
index c6b162a82f89ff256556c0db4e4b027d37a3565a..b770023604074db354d82a8965bab3eebb5b523a 100644 (file)
@@ -13,6 +13,7 @@ namespace Content.Shared.EntityEffects.Effects.StatusEffects;
 /// <remarks>
 ///     Can be used for things like adding accents or something. I don't know. Go wild.
 /// </remarks>
+[Obsolete("Use ModifyStatusEffect with StatusEffectNewSystem instead")]
 public sealed partial class GenericStatusEffect : EntityEffect
 {
     [DataField(required: true)]
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs
new file mode 100644 (file)
index 0000000..33021ec
--- /dev/null
@@ -0,0 +1,66 @@
+using Content.Shared.StatusEffectNew;
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Changes status effects on entities: Adds, removes or sets time.
+/// </summary>
+[UsedImplicitly]
+public sealed partial class ModifyStatusEffect : EntityEffect
+{
+    [DataField(required: true)]
+    public EntProtoId EffectProto;
+
+    /// <summary>
+    /// Time for which status effect should be applied. Behaviour changes according to <see cref="Refresh" />.
+    /// </summary>
+    [DataField]
+    public float Time = 2.0f;
+
+    /// <remarks>
+    /// true - refresh status effect time, false - accumulate status effect time.
+    /// </remarks>
+    [DataField]
+    public bool Refresh = true;
+
+    /// <summary>
+    /// Should this effect add the status effect, remove time from it, or set its cooldown?
+    /// </summary>
+    [DataField]
+    public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Add;
+
+    /// <inheritdoc />
+    public override void Effect(EntityEffectBaseArgs args)
+    {
+        var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedStatusEffectsSystem>();
+
+        var time = Time;
+        if (args is EntityEffectReagentArgs reagentArgs)
+            time *= reagentArgs.Scale.Float();
+
+        switch (Type)
+        {
+            case StatusEffectMetabolismType.Add:
+                statusSys.TryAddStatusEffect(args.TargetEntity, EffectProto, TimeSpan.FromSeconds(time), Refresh);
+                break;
+            case StatusEffectMetabolismType.Remove:
+                statusSys.TryAddTime(args.TargetEntity, EffectProto, -TimeSpan.FromSeconds(time));
+                break;
+            case StatusEffectMetabolismType.Set:
+                statusSys.TrySetTime(args.TargetEntity, EffectProto, TimeSpan.FromSeconds(time));
+                break;
+        }
+    }
+
+    /// <inheritdoc />
+    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString(
+            "reagent-effect-guidebook-status-effect",
+            ("chance", Probability),
+            ("type", Type),
+            ("time", Time),
+            ("key", prototype.Index(EffectProto).Name)
+        );
+}
index dfba833bcf71cb25191696cfb8f68440b5027f42..850ea8e8da46a9bae4b28cfc48e1860794ec130a 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Shared.Bed.Sleep;
 using Content.Shared.CCVar;
+using Content.Shared.StatusEffectNew;
 using Robust.Shared.Configuration;
 using Robust.Shared.Player;
 using Robust.Shared.Timing;
@@ -13,6 +14,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
 {
     [Dependency] private readonly IConfigurationManager _cfg = default!;
     [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly SharedStatusEffectsSystem _statusEffects = default!;
 
     private bool _icSsdSleep;
     private float _icSsdSleepTime;
@@ -37,10 +39,11 @@ public sealed class SSDIndicatorSystem : EntitySystem
             component.FallAsleepTime = TimeSpan.Zero;
             if (component.ForcedSleepAdded) // Remove component only if it has been added by this system
             {
-                EntityManager.RemoveComponent<ForcedSleepingComponent>(uid);
+                _statusEffects.TryRemoveStatusEffect(uid, SleepingSystem.StatusEffectForcedSleeping);
                 component.ForcedSleepAdded = false;
             }
         }
+
         Dirty(uid, component);
     }
 
@@ -53,6 +56,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
         {
             component.FallAsleepTime = _timing.CurTime + TimeSpan.FromSeconds(_icSsdSleepTime);
         }
+
         Dirty(uid, component);
     }
 
@@ -79,12 +83,11 @@ public sealed class SSDIndicatorSystem : EntitySystem
         while (query.MoveNext(out var uid, out var ssd))
         {
             // Forces the entity to sleep when the time has come
-            if(ssd.IsSSD &&
+            if (ssd.IsSSD &&
                 ssd.FallAsleepTime <= _timing.CurTime &&
-                !TerminatingOrDeleted(uid) &&
-                !HasComp<ForcedSleepingComponent>(uid)) // Don't add the component if the entity has it from another sources
+                !TerminatingOrDeleted(uid))
             {
-                EnsureComp<ForcedSleepingComponent>(uid);
+                _statusEffects.TryAddStatusEffect(uid, SleepingSystem.StatusEffectForcedSleeping);
                 ssd.ForcedSleepAdded = true;
             }
         }
index 5fa634351d02abccfc19ab2ec640a9d215f1bf8b..9a73f83d34e9891b4f9db4054de767f5fd551b7e 100644 (file)
@@ -1,6 +1,7 @@
 using System.Diagnostics.CodeAnalysis;
 using Content.Shared.Alert;
 using Content.Shared.Rejuvenate;
+using Content.Shared.StatusEffectNew;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Timing;
@@ -8,6 +9,7 @@ using Robust.Shared.Utility;
 
 namespace Content.Shared.StatusEffect
 {
+    [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
     public sealed class StatusEffectsSystem : EntitySystem
     {
         [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -104,6 +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")]
         public bool TryAddStatusEffect<T>(EntityUid uid, string key, TimeSpan time, bool refresh,
             StatusEffectsComponent? status = null)
             where T : IComponent, new()
@@ -123,6 +126,7 @@ namespace Content.Shared.StatusEffect
 
         }
 
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
         public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, string component,
             StatusEffectsComponent? status = null)
         {
@@ -162,6 +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")]
         public bool TryAddStatusEffect(EntityUid uid,
             string key,
             TimeSpan time,
@@ -255,6 +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")]
         public bool TryRemoveStatusEffect(EntityUid uid, string key,
             StatusEffectsComponent? status = null, bool remComp = true)
         {
@@ -298,6 +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")]
         public bool TryRemoveAllStatusEffects(EntityUid uid,
             StatusEffectsComponent? status = null)
         {
@@ -321,6 +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")]
         public bool HasStatusEffect(EntityUid uid, string key,
             StatusEffectsComponent? status = null)
         {
@@ -338,6 +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")]
         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
@@ -364,6 +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")]
         public bool TryAddTime(EntityUid uid, string key, TimeSpan time,
             StatusEffectsComponent? status = null)
         {
@@ -395,6 +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")]
         public bool TryRemoveTime(EntityUid uid, string key, TimeSpan time,
             StatusEffectsComponent? status = null)
         {
@@ -430,6 +441,7 @@ namespace Content.Shared.StatusEffect
         /// <remarks>
         ///     Not used internally; just sets it itself.
         /// </remarks>
+        [Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
         public bool TrySetTime(EntityUid uid, string key, TimeSpan time,
             StatusEffectsComponent? status = null)
         {
@@ -453,6 +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")]
         public bool TryGetTime(EntityUid uid, string key,
             [NotNullWhen(true)] out (TimeSpan, TimeSpan)? time,
             StatusEffectsComponent? status = null)
@@ -468,12 +481,6 @@ namespace Content.Shared.StatusEffect
         }
     }
 
-    /// <summary>
-    ///     Raised on an entity before a status effect is added to determine if adding it should be cancelled.
-    /// </summary>
-    [ByRefEvent]
-    public record struct BeforeStatusEffectAddedEvent(string Key, bool Cancelled=false);
-
     public readonly struct StatusEffectAddedEvent
     {
         public readonly EntityUid Uid;
diff --git a/Content.Shared/StatusEffectNew/Components/StatusEffectComponent.cs b/Content.Shared/StatusEffectNew/Components/StatusEffectComponent.cs
new file mode 100644 (file)
index 0000000..6419874
--- /dev/null
@@ -0,0 +1,47 @@
+using Content.Shared.Alert;
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.StatusEffectNew.Components;
+
+/// <summary>
+/// Marker component for all status effects - every status effect entity should have it.
+/// 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))]
+[EntityCategory("StatusEffects")]
+public sealed partial class StatusEffectComponent : Component
+{
+    /// <summary>
+    /// The entity that this status effect is applied to.
+    /// </summary>
+    [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>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField, AutoNetworkedField]
+    public TimeSpan? EndEffectTime;
+
+    /// <summary>
+    /// Whitelist, by which it is determined whether this status effect can be imposed on a particular entity.
+    /// </summary>
+    [DataField]
+    public EntityWhitelist? Whitelist;
+
+    /// <summary>
+    /// Blacklist, by which it is determined whether this status effect can be imposed on a particular entity.
+    /// </summary>
+    [DataField]
+    public EntityWhitelist? Blacklist;
+}
diff --git a/Content.Shared/StatusEffectNew/Components/StatusEffectContainerComponent.cs b/Content.Shared/StatusEffectNew/Components/StatusEffectContainerComponent.cs
new file mode 100644 (file)
index 0000000..6d9efaf
--- /dev/null
@@ -0,0 +1,23 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.StatusEffectNew.Components;
+
+/// <summary>
+/// Adds container for status effect entities that are applied to entity.
+/// Is applied automatically upon adding any status effect.
+/// Can be used for tracking currently applied status effects.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedStatusEffectsSystem))]
+public sealed partial class StatusEffectContainerComponent : Component
+{
+    [DataField]
+    public HashSet<EntityUid> ActiveStatusEffects = new();
+}
+
+[Serializable, NetSerializable]
+public sealed class StatusEffectContainerComponentState(HashSet<NetEntity> activeStatusEffects) : ComponentState
+{
+    public readonly HashSet<NetEntity> ActiveStatusEffects = activeStatusEffects;
+}
diff --git a/Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs b/Content.Shared/StatusEffectNew/SharedStatusEffectsSystem.cs
new file mode 100644 (file)
index 0000000..df27dca
--- /dev/null
@@ -0,0 +1,208 @@
+using Content.Shared.Alert;
+using Content.Shared.StatusEffectNew.Components;
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+using Robust.Shared.Network;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.StatusEffectNew;
+
+/// <summary>
+/// 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
+{
+    [Dependency] private readonly AlertsSystem _alerts = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = 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;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<StatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
+        SubscribeLocalEvent<StatusEffectComponent, StatusEffectRemovedEvent>(OnStatusEffectRemoved);
+
+        SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerAttachedEvent>(OnStatusEffectContainerAttached);
+        SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerDetachedEvent>(OnStatusEffectContainerDetached);
+        SubscribeLocalEvent<StatusEffectContainerComponent, ComponentGetState>(OnGetState);
+
+        _containerQuery = GetEntityQuery<StatusEffectContainerComponent>();
+        _effectQuery = GetEntityQuery<StatusEffectComponent>();
+    }
+
+    private void OnGetState(Entity<StatusEffectContainerComponent> ent, ref ComponentGetState args)
+    {
+        args.State = new StatusEffectContainerComponentState(GetNetEntitySet(ent.Comp.ActiveStatusEffects));
+    }
+
+    private void OnStatusEffectContainerAttached(Entity<StatusEffectContainerComponent> ent, ref LocalPlayerAttachedEvent args)
+    {
+        foreach (var effect in ent.Comp.ActiveStatusEffects)
+        {
+            var ev = new StatusEffectPlayerAttachedEvent(ent);
+            RaiseLocalEvent(effect, ref ev);
+        }
+    }
+
+    private void OnStatusEffectContainerDetached(Entity<StatusEffectContainerComponent> ent, ref LocalPlayerDetachedEvent args)
+    {
+        foreach (var effect in ent.Comp.ActiveStatusEffects)
+        {
+            var ev = new StatusEffectPlayerDetachedEvent(ent);
+            RaiseLocalEvent(effect, ref ev);
+        }
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var query = EntityQueryEnumerator<StatusEffectComponent>();
+        while (query.MoveNext(out var ent, out var effect))
+        {
+            if (effect.EndEffectTime is null)
+                continue;
+
+            if (!(_timing.CurTime >= effect.EndEffectTime))
+                continue;
+
+            if (effect.AppliedTo is null)
+                continue;
+
+            var meta = MetaData(ent);
+            if (meta.EntityPrototype is null)
+                continue;
+
+            TryRemoveStatusEffect(effect.AppliedTo.Value, meta.EntityPrototype);
+        }
+    }
+
+    private void AddStatusEffectTime(EntityUid effect, TimeSpan delta)
+    {
+        if (!_effectQuery.TryComp(effect, out var effectComp))
+            return;
+
+        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
+            );
+        }
+    }
+
+    private void SetStatusEffectTime(EntityUid effect, TimeSpan duration)
+    {
+        if (!_effectQuery.TryComp(effect, out var effectComp))
+            return;
+
+        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
+            );
+        }
+    }
+
+    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
+            );
+        }
+    }
+
+    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)
+    {
+        if (!_proto.TryIndex(effectProto, out var effectProtoData))
+            return false;
+
+        if (!effectProtoData.TryGetComponent<StatusEffectComponent>(out var effectProtoComp, _compFactory))
+            return false;
+
+        if (!_whitelist.CheckBoth(uid, effectProtoComp.Blacklist, effectProtoComp.Whitelist))
+            return false;
+
+        var ev = new BeforeStatusEffectAddedEvent(effectProto);
+        RaiseLocalEvent(uid, ref ev);
+
+        if (ev.Cancelled)
+            return false;
+
+        return true;
+    }
+}
+
+/// <summary>
+/// Calls on effect entity, when a status effect is applied.
+/// </summary>
+[ByRefEvent]
+public readonly record struct StatusEffectAppliedEvent(EntityUid Target);
+
+/// <summary>
+/// Calls on effect entity, when a status effect is removed.
+/// </summary>
+[ByRefEvent]
+public readonly record struct StatusEffectRemovedEvent(EntityUid Target);
+
+/// <summary>
+/// Called on a status effect entity inside <see cref="StatusEffectContainerComponent"/>
+/// after a player has been <see cref="LocalPlayerAttachedEvent"/> to this container entity.
+/// </summary>
+[ByRefEvent]
+public readonly record struct StatusEffectPlayerAttachedEvent(EntityUid Target);
+
+/// <summary>
+/// Called on a status effect entity inside <see cref="StatusEffectContainerComponent"/>
+/// after a player has been <see cref="LocalPlayerDetachedEvent"/> to this container entity.
+/// </summary>
+[ByRefEvent]
+public readonly record struct StatusEffectPlayerDetachedEvent(EntityUid Target);
+
+/// <summary>
+/// Raised on an entity before a status effect is added to determine if adding it should be cancelled.
+/// </summary>
+[ByRefEvent]
+public record struct BeforeStatusEffectAddedEvent(EntProtoId Effect, bool Cancelled = false);
diff --git a/Content.Shared/StatusEffectNew/StatusEffectNewSystem.API.cs b/Content.Shared/StatusEffectNew/StatusEffectNewSystem.API.cs
new file mode 100644 (file)
index 0000000..8f34f97
--- /dev/null
@@ -0,0 +1,265 @@
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.StatusEffectNew.Components;
+using Robust.Shared.Prototypes;
+
+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.
+    /// </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>
+    public bool TryAddStatusEffect(
+        EntityUid target,
+        EntProtoId effectProto,
+        TimeSpan? duration = null,
+        bool resetCooldown = false
+    )
+    {
+        if (TryGetStatusEffect(target, effectProto, out var existedEffect))
+        {
+            //We don't need to add the effect if it already exists
+            if (duration is null)
+                return true;
+
+            if (resetCooldown)
+                SetStatusEffectTime(existedEffect.Value, duration.Value);
+            else
+                AddStatusEffectTime(existedEffect.Value, duration.Value);
+
+            return true;
+        }
+
+        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;
+
+        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;
+    }
+
+    /// <summary>
+    /// Attempting to remove a status effect from an entity.
+    /// Returns True if the status effect existed on the entity and was successfully removed, and False in otherwise.
+    /// </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)
+        {
+            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);
+
+                QueueDel(effect);
+                container.ActiveStatusEffects.Remove(effect);
+                Dirty(target, container);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    /// Checks whether the specified entity is under a specific status effect.
+    /// </summary>
+    public bool HasStatusEffect(EntityUid target, EntProtoId effectProto)
+    {
+        if (!_containerQuery.TryComp(target, out var container))
+            return false;
+
+        foreach (var effect in container.ActiveStatusEffects)
+        {
+            var meta = MetaData(effect);
+            if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
+                return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    /// Attempting to retrieve the EntityUid of a status effect from an entity.
+    /// </summary>
+    public bool TryGetStatusEffect(EntityUid target, EntProtoId effectProto, [NotNullWhen(true)] out EntityUid? effect)
+    {
+        effect = null;
+        if (!_containerQuery.TryComp(target, out var container))
+            return false;
+
+        foreach (var e in container.ActiveStatusEffects)
+        {
+            var meta = MetaData(e);
+            if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
+            {
+                effect = e;
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    /// Attempting to retrieve the time of a status effect from an entity.
+    /// </summary>
+    /// <param name="uid">The target entity on which the effect is applied.</param>
+    /// <param name="effectProto">The prototype ID of the status effect to retrieve.</param>
+    /// <param name="time">The output tuple containing the effect entity and its remaining time.</param>
+    /// <param name="container">Optional. The status effect container component of the entity.</param>
+    public bool TryGetTime(
+        EntityUid uid,
+        EntProtoId effectProto,
+        out (EntityUid EffectEnt, TimeSpan? EndEffectTime) time,
+        StatusEffectContainerComponent? container = null
+    )
+    {
+        time = default;
+        if (!Resolve(uid, ref container))
+            return false;
+
+        foreach (var effect in container.ActiveStatusEffects)
+        {
+            var meta = MetaData(effect);
+            if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
+            {
+                if (!_effectQuery.TryComp(effect, out var effectComp))
+                    return false;
+
+                time = (effect, effectComp.EndEffectTime);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    /// Attempts to edit the remaining time for a status effect on an entity.
+    /// </summary>
+    /// <param name="uid">The target entity on which the effect is applied.</param>
+    /// <param name="effectProto">The prototype ID of the status effect to modify.</param>
+    /// <param name="time">
+    /// The time adjustment to apply to the status effect. Positive values extend the duration,
+    /// while negative values reduce it.
+    /// </param>
+    /// <returns> True if duration was edited successfully, false otherwise.</returns>
+    public bool TryAddTime(EntityUid uid, EntProtoId effectProto, TimeSpan time)
+    {
+        if (!_containerQuery.TryComp(uid, out var container))
+            return false;
+
+        foreach (var effect in container.ActiveStatusEffects)
+        {
+            var meta = MetaData(effect);
+            if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
+            {
+                AddStatusEffectTime(effect, time);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /// <summary>
+    /// Attempts to set the remaining time for a status effect on an entity.
+    /// </summary>
+    /// <param name="uid">The target entity on which the effect is applied.</param>
+    /// <param name="effectProto">The prototype ID of the status effect to modify.</param>
+    /// <param name="time">The new duration for the status effect.</param>
+    /// <returns> True if duration was set successfully, false otherwise.</returns>
+    public bool TrySetTime(EntityUid uid, EntProtoId effectProto, TimeSpan time)
+    {
+        if (!_containerQuery.TryComp(uid, out var container))
+            return false;
+
+        foreach (var effect in container.ActiveStatusEffects)
+        {
+            var meta = MetaData(effect);
+            if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
+            {
+                SetStatusEffectTime(effect, time);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /// <summary>
+    /// Checks if the specified component is present on any of the entity's status effects.
+    /// </summary>
+    public bool HasEffectComp<T>(EntityUid? target) where T : IComponent
+    {
+        if (!_containerQuery.TryComp(target, out var container))
+            return false;
+
+        foreach (var effect in container.ActiveStatusEffects)
+        {
+            if (HasComp<T>(effect))
+                return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    /// Returns all status effects that have the specified component.
+    /// </summary>
+    public bool TryEffectsWithComp<T>(EntityUid? target, [NotNullWhen(true)] out HashSet<Entity<T, StatusEffectComponent>>? effects) where T : IComponent
+    {
+        effects = null;
+        if (!_containerQuery.TryComp(target, out var container))
+            return false;
+
+        foreach (var effect in container.ActiveStatusEffects)
+        {
+            if (!TryComp<StatusEffectComponent>(effect, out var statusComp))
+                continue;
+
+            if (TryComp<T>(effect, out var comp))
+            {
+                effects ??= [];
+                effects.Add((effect, comp, statusComp));
+            }
+        }
+
+        return effects != null;
+    }
+}
index a5ed66dd0118624c0685f3db9b27c4ad5a183248..8a4ea73d92f68ad41f0452bcec37526fd915f7e8 100644 (file)
@@ -4,5 +4,6 @@ entity-category-name-objectives = Objectives
 entity-category-name-roles = Mind Roles
 entity-category-name-mapping = Mapping
 entity-category-name-donotmap = Do not map
+entity-category-name-status-effects = Status Effects
 
 entity-category-suffix-donotmap = DO NOT MAP
index 3ba17c33197dd7a0be17001a5f82f058df35ec3a..de8f15f4c0a997c1a0e00c6c837f3414bf7dfe50 100644 (file)
@@ -25,7 +25,6 @@
     - Electrocution
     - TemporaryBlindness
     - RadiationProtection
-    - Drowsiness
     - Adrenaline
   - type: StandingState
   - type: Tag
index 1143f8be6cc0743012a0ab0f6a32792cc7395aba..bb095bfffd696f715f0f9a546e7a2e186948d8ca 100644 (file)
     - SlowedDown
     - Stutter
     - Electrocution
-    - ForcedSleep
     - TemporaryBlindness
     - Pacified
     - Flashed
     - RadiationProtection
-    - Drowsiness
     - Adrenaline
   - type: Buckle
   - type: StandingState
     - SlowedDown
     - Stutter
     - Electrocution
-    - ForcedSleep
     - TemporaryBlindness
     - Pacified
     - StaminaModifier
     - Flashed
     - RadiationProtection
-    - Drowsiness
     - Adrenaline
   - type: Bloodstream
     bloodMaxVolume: 150
index 7ba6acf50dc7389184a705d923f79f59c66ccf11..fc2dddb23dd089683550404b7854833bd96db659 100644 (file)
     - SlowedDown
     - Stutter
     - Electrocution
-    - ForcedSleep
     - TemporaryBlindness
     - Pacified
     - RadiationProtection
-    - Drowsiness
     - Adrenaline
   - type: Temperature
     heatDamageThreshold: 800
index ed5d53aa97d75a026185e35cc31db22987fe0519..b93ed915e284e29ea2117e5d8c9a4a0337691602 100644 (file)
     - RatvarianLanguage
     - PressureImmunity
     - Muted
-    - ForcedSleep
     - TemporaryBlindness
     - Pacified
     - StaminaModifier
     - Flashed
     - RadiationProtection
-    - Drowsiness
     - Adrenaline
   - type: Body
     prototype: Human
diff --git a/Resources/Prototypes/Entities/StatusEffects/misc.yml b/Resources/Prototypes/Entities/StatusEffects/misc.yml
new file mode 100644 (file)
index 0000000..254d160
--- /dev/null
@@ -0,0 +1,36 @@
+- type: entity
+  id: StatusEffectBase
+  abstract: true
+  components:
+  - type: StatusEffect
+  - type: Sprite
+    drawdepth: Effects
+  - type: Tag
+    tags:
+    - HideContextMenu
+
+- type: entity
+  parent: StatusEffectBase
+  id: MobStatusEffectBase
+  abstract: true
+  components:
+  - type: StatusEffect
+    whitelist:
+      components:
+      - MobState
+
+# The creature sleeps so heavily that nothing can wake him up. Not even its own death.
+- type: entity
+  parent: MobStatusEffectBase
+  id: StatusEffectForcedSleeping
+  name: forced sleep
+  components:
+  - type: ForcedSleepingStatusEffect
+
+# Blurs your vision and makes you randomly fall asleep
+- type: entity
+  parent: MobStatusEffectBase
+  id: StatusEffectDrowsiness
+  name: drowsiness
+  components:
+  - type: DrowsinessStatusEffect
index 5b8e794309d760b0eab5c726d7bfa82e2cf7ab16..aaf77989cd68eda8d6f5a0f31dea98ff0ab515de 100644 (file)
@@ -27,3 +27,8 @@
   id: DoNotMap
   name: entity-category-name-donotmap
   suffix: entity-category-suffix-donotmap
+
+- type: entityCategory
+  id: StatusEffects
+  name: entity-category-name-status-effects
+  hideSpawnMenu: true
\ No newline at end of file
index 7294972e7e1aa0f19254cd4b0d992c534600a297..adb9aa28e4a2b472a4ee5a4da0d6cc74c3889058 100644 (file)
         - !type:AdjustReagent
           reagent: Theobromine
           amount: 0.05
-        - !type:GenericStatusEffect
-          key: Drowsiness
-          time: 1.0
+        - !type:ModifyStatusEffect
+          effectProto: StatusEffectDrowsiness
+          time: 1
           type: Remove
   fizziness: 0.25
 
         - !type:AdjustReagent
           reagent: Theobromine
           amount: 0.05
-        - !type:GenericStatusEffect
-          key: Drowsiness
-          time: 1.0
+        - !type:ModifyStatusEffect
+          effectProto: StatusEffectDrowsiness
+          time: 1
           type: Remove
   fizziness: 0.15
 
         - !type:AdjustReagent
           reagent: Theobromine
           amount: 0.05
-        - !type:GenericStatusEffect
-          key: Drowsiness
-          time: 1.0
+        - !type:ModifyStatusEffect
+          effectProto: StatusEffectDrowsiness
+          time: 1
           type: Remove
   fizziness: 0.15
 
         - !type:AdjustReagent
           reagent: Theobromine
           amount: 0.05
-        - !type:GenericStatusEffect
-          key: Drowsiness
-          time: 1.0
+        - !type:ModifyStatusEffect
+          effectProto: StatusEffectDrowsiness
+          time: 1
           type: Remove
   fizziness: 0.25
 
         - !type:AdjustReagent
           reagent: Theobromine
           amount: 0.05
-        - !type:GenericStatusEffect
-          key: Drowsiness
-          time: 1.0
+        - !type:ModifyStatusEffect
+          effectProto: StatusEffectDrowsiness
+          time: 1
           type: Remove
   fizziness: 0.15
 
         - !type:AdjustReagent
           reagent: Theobromine
           amount: 0.05
-        - !type:GenericStatusEffect
-          key: Drowsiness
-          time: 1.0
+        - !type:ModifyStatusEffect
+          effectProto: StatusEffectDrowsiness
+          time: 1
           type: Remove
   fizziness: 0.25
 
index 1f092032d071b495494dfa3f833c7f49f34187f8..2d8351c97f02ab27daaa50666f02a146c78ecf7f 100644 (file)
@@ -12,9 +12,9 @@
       effects:
       - !type:SatiateThirst
         factor: 2
-      - !type:GenericStatusEffect
-        key: Drowsiness
-        time: 2.0
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
+        time: 2
         type: Remove
       - !type:AdjustReagent
         reagent: Theobromine
       effects:
       - !type:SatiateThirst
         factor: 2
-      - !type:GenericStatusEffect
-        key: Drowsiness
-        time: 2.0
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
+        time: 2
         type: Remove
 
 - type: reagent
       effects:
       - !type:SatiateThirst
         factor: 2
-      - !type:GenericStatusEffect
-        key: Drowsiness
-        time: 2.0
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
+        time: 2
         type: Remove
 
 - type: reagent
       effects:
       - !type:SatiateThirst
         factor: 6
-      - !type:GenericStatusEffect
-        key: Drowsiness
-        time: 3.0
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
+        time: 3
         type: Remove
     Poison:
       effects:
       effects:
       - !type:SatiateThirst
         factor: 2
-      - !type:GenericStatusEffect
-        key: Drowsiness
-        time: 2.0
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
+        time: 2
         type: Remove
 
 - type: reagent
index d224d1573e076f3f7a926562853afc9c72809cf3..5ac667ec181580410bffd4d75eadac102e1dc900 100644 (file)
@@ -18,9 +18,9 @@
       effects:
       - !type:SatiateThirst
         factor: 2
-      - !type:GenericStatusEffect
-        key: Drowsiness
-        time: 1.0
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
+        time: 1
         type: Remove
 
 - type: reagent
@@ -80,9 +80,9 @@
       effects:
       - !type:SatiateThirst
         factor: 2
-      - !type:GenericStatusEffect
-        key: Drowsiness
-        time: 2.0
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
+        time: 2
         type: Remove
       - !type:AdjustReagent
         reagent: Theobromine
     state: icon_empty
   metamorphicMaxFillLevels: 5
   metamorphicFillBaseName: fill-
-  metamorphicChangeColor: false  
+  metamorphicChangeColor: false
   metabolisms:
     Drink:
       effects:
index 2677fc0ed0e84625bf983e76960378b349fdb8f3..1087d7bad4cd5e2499f7b7bd6de227b6f8238b79 100644 (file)
           shouldHave: false
         walkSpeedModifier: 0.65
         sprintSpeedModifier: 0.65
-      - !type:GenericStatusEffect
+      - !type:ModifyStatusEffect
         conditions:
         - !type:ReagentThreshold
           reagent: NitrousOxide
         - !type:OrganType
           type: Slime
           shouldHave: false
-        key: ForcedSleep
-        component: ForcedSleeping
+        effectProto: StatusEffectForcedSleeping
         time: 3
         type: Add
       - !type:HealthChange
index d553b8065d16c6b904f950baad42f6ec5252dd68..2873372b52d77611d9c94cc086f900792da86811 100644 (file)
@@ -83,9 +83,8 @@
         key: Jitter
         time: 3.0
         type: Remove
-      - !type:GenericStatusEffect
-        key: Drowsiness
-        component: Drowsiness
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
         time: 1.5
         type: Add
         refresh: false
       - !type:GenericStatusEffect
         key: Stutter
         component: StutteringAccent
-      - !type:GenericStatusEffect
-        key: Drowsiness
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
         time: 10
         type: Remove
       - !type:ResetNarcolepsy
   metabolisms:
     Medicine:
       effects:
-      - !type:GenericStatusEffect
-        key: Drowsiness
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
         time: 10
         type: Remove
       - !type:ResetNarcolepsy
         emote: Yawn
         showInChat: true
         probability: 0.1
-      - !type:GenericStatusEffect
-        key: Drowsiness
-        component: Drowsiness
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
         time: 4
         type: Add
         refresh: false
index 6e159278e92d5532bfe292132dffbff37df2d944..aff87479b1e1f076ad202f343365a5a98a3943b9 100644 (file)
         key: KnockedDown
         time: 3
         type: Remove
-      - !type:GenericStatusEffect
+      - !type:ModifyStatusEffect
         conditions:
         - !type:ReagentThreshold
           reagent: Haloperidol
           max: 0.01
-        key: Drowsiness
+        effectProto: StatusEffectDrowsiness
         time: 10
         type: Remove
     Medicine:
         key: KnockedDown
         time: 1
         type: Remove
-      - !type:GenericStatusEffect
+      - !type:ModifyStatusEffect
         conditions:
         - !type:ReagentThreshold
           reagent: Haloperidol
           max: 0.01
-        key: Drowsiness
+        effectProto: StatusEffectDrowsiness
         time: 10
         type: Remove
       - !type:PopupMessage
         component: StaminaModifier
         time: 3
         type: Add
-      - !type:GenericStatusEffect
-        key: ForcedSleep
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectForcedSleeping
         time: 3
         type: Remove
-      - !type:GenericStatusEffect
+      - !type:ModifyStatusEffect
         conditions:
         - !type:ReagentThreshold
           reagent: Haloperidol
           max: 0.01
-        key: Drowsiness
+        effectProto: StatusEffectDrowsiness
         time: 10
         type: Remove
     Medicine:
   metabolisms:
     Narcotic:
       effects:
-      - !type:GenericStatusEffect
+      - !type:ModifyStatusEffect
         conditions:
         - !type:ReagentThreshold
           reagent: Nocturine
           min: 8
-        key: ForcedSleep
-        component: ForcedSleeping
-        refresh: false
+        effectProto: StatusEffectForcedSleeping
+        time: 3
         type: Add
 
 - type: reagent
index 72730c7990b71cef66dad87d33fef0d6c705759b..6560f05995a3ea3980e65e7bd6c7dabef5bce4bc 100644 (file)
@@ -63,9 +63,8 @@
       - !type:MovespeedModifier
         walkSpeedModifier: 0.65
         sprintSpeedModifier: 0.65
-      - !type:GenericStatusEffect
-        key: Drowsiness
-        component: Drowsiness
+      - !type:ModifyStatusEffect
+        effectProto: StatusEffectDrowsiness
         time: 4
         type: Add
         refresh: false
index 49e5ccc57941902bcb841c6977d499fdafe1872e..e98dd4df02b17a4e0fc0efa45da2de787f4bbd55 100644 (file)
@@ -1,6 +1,9 @@
 # Status effect prototypes.
 # Holds no actual logic, just some basic data about the effect.
 
+# Note: We have a new status effect system that needs all of these status effects to be fully ported to.
+# Adding new status effects under the old system is NOT RECOMMENDED.
+
 - type: statusEffect
   id: Stun
   alert: Stun
@@ -45,9 +48,6 @@
   id: Corporeal
   alert: Corporeal
 
-- type: statusEffect
-  id: ForcedSleep #I.e., they will not wake on damage or similar
-
 - type: statusEffect
   id: TemporaryBlindness
 
@@ -66,9 +66,6 @@
 - type: statusEffect
   id: RadiationProtection
 
-- type: statusEffect
-  id: Drowsiness #blurs your vision and makes you randomly fall asleep
-
 - type: statusEffect
   id: Adrenaline
   alert: Adrenaline