]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Stunnable New Status and Cleanup (#38618)
authorPrincess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com>
Mon, 21 Jul 2025 17:22:11 +0000 (10:22 -0700)
committerGitHub <noreply@github.com>
Mon, 21 Jul 2025 17:22:11 +0000 (19:22 +0200)
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
56 files changed:
Content.IntegrationTests/Tests/InventoryHelpersTest.cs
Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs
Content.Server/Atmos/EntitySystems/FlammableSystem.cs
Content.Server/Cluwne/CluwneSystem.cs
Content.Server/Drowsiness/DrowsinessSystem.cs
Content.Server/Electrocution/ElectrocutionSystem.cs
Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs
Content.Server/Instruments/InstrumentSystem.cs
Content.Server/Medical/VomitSystem.cs
Content.Server/Ninja/Systems/StunProviderSystem.cs
Content.Server/PneumaticCannon/PneumaticCannonSystem.cs
Content.Server/Revenant/EntitySystems/RevenantSystem.cs
Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs
Content.Server/Stunnable/Systems/StunOnCollideSystem.cs
Content.Shared/Abilities/Goliath/GoliathTentacleSystem.cs
Content.Shared/Bed/Sleep/SleepingSystem.cs
Content.Shared/Climbing/Systems/ClimbSystem.cs
Content.Shared/Clumsy/ClumsySystem.cs
Content.Shared/Damage/Systems/DamageOnHighSpeedImpactSystem.cs
Content.Shared/Damage/Systems/DamageOnInteractSystem.cs
Content.Shared/Damage/Systems/SharedStaminaSystem.cs
Content.Shared/Doors/Systems/SharedDoorSystem.cs
Content.Shared/EntityEffects/Effects/Paralyze.cs
Content.Shared/Flash/SharedFlashSystem.cs
Content.Shared/Magic/SharedMagicSystem.cs
Content.Shared/Movement/Components/MovementModStatusEffectComponent.cs [new file with mode: 0644]
Content.Shared/Movement/Systems/MovementModStatusSystem.cs
Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs
Content.Shared/Revolutionary/SharedRevolutionarySystem.cs
Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs
Content.Shared/Slippery/SlipperySystem.cs
Content.Shared/Species/Systems/ReformSystem.cs
Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs
Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs
Content.Shared/StatusEffectNew/StatusEffectsSystem.cs
Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs [new file with mode: 0644]
Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs
Content.Shared/Stunnable/SharedStunSystem.cs
Content.Shared/Stunnable/SlowedDownComponent.cs [deleted file]
Content.Shared/Stunnable/StunnableEvents.cs
Content.Shared/Stunnable/StunnedComponent.cs
Content.Shared/Stunnable/StunnedStatusEffectComponent.cs [new file with mode: 0644]
Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs
Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml
Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml
Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml
Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml
Resources/Prototypes/Entities/Mobs/NPCs/silicon.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
Resources/Prototypes/Entities/StatusEffects/movement.yml [new file with mode: 0644]
Resources/Prototypes/tags.yml

index 4a07177f77aee3ac993ab755af8c29ad4cd4c579..39761ad0898bc1b64fc0f11a9ee18df50d6de8ef 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Server.Stunnable;
+using Content.Server.Stunnable;
 using Content.Shared.Inventory;
 using Robust.Shared.GameObjects;
 using Robust.Shared.IoC;
@@ -17,9 +17,7 @@ namespace Content.IntegrationTests.Tests
   components:
   - type: Inventory
   - type: ContainerContainer
-  - type: StatusEffects
-    allowed:
-    - Stun
+  - type: MobState
 
 - type: entity
   name: InventoryJumpsuitJanitorDummy
@@ -70,7 +68,7 @@ namespace Content.IntegrationTests.Tests
                 });
 #pragma warning restore NUnit2045
 
-                systemMan.GetEntitySystem<StunSystem>().TryStun(human, TimeSpan.FromSeconds(1f), true);
+                systemMan.GetEntitySystem<StunSystem>().TryUpdateStunDuration(human, TimeSpan.FromSeconds(1f));
 
 #pragma warning disable NUnit2045
                 // Since the mob is stunned, they can't equip this.
index ff644ad3e00a7a4eca06506ebe3547564ff0e0ff..bed249929865c185dfe8708bab99d08bb2c9af2e 100644 (file)
@@ -96,7 +96,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
 
         EntityManager.AddComponents(ent, injectedAnom.Components);
 
-        _stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
+        _stun.TryUpdateParalyzeDuration(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration));
         _jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
 
         if (ent.Comp.StartSound is not null)
@@ -125,7 +125,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
 
     private void OnAnomalyPulse(Entity<InnerBodyAnomalyComponent> ent, ref AnomalyPulseEvent args)
     {
-        _stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
+        _stun.TryUpdateParalyzeDuration(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity));
         _jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
     }
 
@@ -213,7 +213,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
         if (_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom))
             EntityManager.RemoveComponents(ent, injectedAnom.Components);
 
-        _stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
+        _stun.TryUpdateParalyzeDuration(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration));
 
         if (ent.Comp.EndMessage is not null &&
             _mind.TryGetMind(ent, out _, out var mindComponent) &&
index 13b3e480c918272c0479205efc3180cd0b17dff4..424f52ed581a9c76e49d1ab0dc74332098a58fbe 100644 (file)
@@ -394,7 +394,7 @@ namespace Content.Server.Atmos.EntitySystems
             flammable.Resisting = true;
 
             _popup.PopupEntity(Loc.GetString("flammable-component-resist-message"), uid, uid);
-            _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(2f), true);
+            _stunSystem.TryUpdateParalyzeDuration(uid, TimeSpan.FromSeconds(2f));
 
             // TODO FLAMMABLE: Make this not use TimerComponent...
             uid.SpawnTimer(2000, () =>
index 2bad4e0b963216f3b15dabdce40eab8adc86bbe6..21a15d812f9c6052583acb00df30dcd615e12178 100644 (file)
@@ -101,7 +101,7 @@ public sealed class CluwneSystem : EntitySystem
         else if (_robustRandom.Prob(component.KnockChance))
         {
             _audio.PlayPvs(component.KnockSound, uid);
-            _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(component.ParalyzeTime), true);
+            _stunSystem.TryUpdateParalyzeDuration(uid, TimeSpan.FromSeconds(component.ParalyzeTime));
             _chat.TrySendInGameICMessage(uid, "spasms", InGameICChatType.Emote, ChatTransmitRange.Normal);
         }
     }
index 13fdc42e109f684377a13f76c6358f1f780d6028..2ea0482590290d50a6f67ed637c2673c74dfd6f3 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Shared.Bed.Sleep;
+using Content.Shared.Bed.Sleep;
 using Content.Shared.Drowsiness;
 using Content.Shared.StatusEffectNew;
 using Content.Shared.StatusEffectNew.Components;
index 18a4d52ac29c2bb03884c9bd4cc4b411e093332e..bb9e71cfc333992266ea8aa184b0aae7f07bc833 100644 (file)
@@ -397,7 +397,12 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
         var shouldStun = siemensCoefficient > 0.5f;
 
         if (shouldStun)
-            _stun.TryParalyze(uid, time * ParalyzeTimeMultiplier, refresh, statusEffects);
+        {
+            _ = refresh
+                ? _stun.TryUpdateParalyzeDuration(uid, time * ParalyzeTimeMultiplier)
+                : _stun.TryAddParalyzeDuration(uid, time * ParalyzeTimeMultiplier);
+        }
+            
 
         // TODO: Sparks here.
 
index d7a548bf0fff3843e7f677b323bf15f25b15c3da..609039ae4ca623a73f6b18ca600afcc58f3ed300 100644 (file)
@@ -228,7 +228,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
                     continue;
 
                 _npcFaction.RemoveFaction(uid, RevolutionaryNpcFaction);
-                _stun.TryParalyze(uid, stunTime, true);
+                _stun.TryUpdateParalyzeDuration(uid, stunTime);
                 RemCompDeferred<RevolutionaryComponent>(uid);
                 _popup.PopupEntity(Loc.GetString("rev-break-control", ("name", Identity.Entity(uid, EntityManager))), uid);
                 _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(uid)} was deconverted due to all Head Revolutionaries dying.");
index f6a216227155bc65f0b9bdd5660b0bbd6eb52627..56b64576cadc931435be7327a030266e215d4907 100644 (file)
@@ -461,7 +461,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
             {
                 if (instrument.InstrumentPlayer is {Valid: true} mob)
                 {
-                    _stuns.TryParalyze(mob, TimeSpan.FromSeconds(1), true);
+                    _stuns.TryUpdateParalyzeDuration(mob, TimeSpan.FromSeconds(1));
 
                     _popup.PopupEntity(Loc.GetString("instrument-component-finger-cramps-max-message"),
                         uid, mob, PopupType.LargeCaution);
index 7b8be07a15f8424a30dee6ca2e2aec98c22cfe94..9fee1dfc85cab9017cfab8ea49fd068638d70aec 100644 (file)
@@ -2,16 +2,15 @@ using Content.Server.Body.Systems;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.Forensics;
 using Content.Server.Popups;
-using Content.Server.Stunnable;
 using Content.Shared.Body.Components;
 using Content.Shared.Body.Systems;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Chemistry.Reagent;
 using Content.Shared.IdentityManagement;
+using Content.Shared.Movement.Systems;
 using Content.Shared.Nutrition.Components;
 using Content.Shared.Nutrition.EntitySystems;
-using Content.Shared.StatusEffect;
 using Robust.Server.Audio;
 using Robust.Shared.Audio;
 using Robust.Shared.Prototypes;
@@ -27,7 +26,7 @@ namespace Content.Server.Medical
         [Dependency] private readonly PopupSystem _popup = default!;
         [Dependency] private readonly PuddleSystem _puddle = default!;
         [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
-        [Dependency] private readonly StunSystem _stun = default!;
+        [Dependency] private readonly MovementModStatusSystem _movementMod = default!;
         [Dependency] private readonly ThirstSystem _thirst = default!;
         [Dependency] private readonly ForensicsSystem _forensics = default!;
         [Dependency] private readonly BloodstreamSystem _bloodstream = default!;
@@ -57,8 +56,7 @@ namespace Content.Server.Medical
             // It fully empties the stomach, this amount from the chem stream is relatively small
             var solutionSize = (MathF.Abs(thirstAdded) + MathF.Abs(hungerAdded)) / 6;
             // Apply a bit of slowdown
-            if (TryComp<StatusEffectsComponent>(uid, out var status))
-                _stun.TrySlowdown(uid, TimeSpan.FromSeconds(solutionSize), true, 0.5f, 0.5f, status);
+            _movementMod.TryUpdateMovementSpeedModDuration(uid, MovementModStatusSystem.VomitingSlowdown, TimeSpan.FromSeconds(solutionSize),  0.5f);
 
             // TODO: Need decals
             var solution = new Solution();
index 822486cff52f75ea381a2ef8e0de078aea198310..8697692e5eb2afe1199546fa2453954d9d08e744 100644 (file)
@@ -63,7 +63,7 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
         _audio.PlayPvs(comp.Sound, target);
 
         _damageable.TryChangeDamage(target, comp.StunDamage, false, true, null, origin: uid);
-        _stun.TryParalyze(target, comp.StunTime, refresh: false);
+        _stun.TryAddParalyzeDuration(target, comp.StunTime);
 
         // short cooldown to prevent instant stunlocking
         _useDelay.SetLength((uid, useDelay), comp.Cooldown, id: comp.DelayId);
index 3cf25472f0af95a9954277c0d4098b52762cc887..09f61632b645e743b6efe658b2a412a177a09922 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Server.Atmos.Components;
 using Content.Server.Atmos.EntitySystems;
 using Content.Server.Storage.EntitySystems;
 using Content.Server.Stunnable;
@@ -7,7 +6,6 @@ using Content.Shared.Atmos.Components;
 using Content.Shared.Containers.ItemSlots;
 using Content.Shared.Interaction;
 using Content.Shared.PneumaticCannon;
-using Content.Shared.StatusEffect;
 using Content.Shared.Tools.Systems;
 using Content.Shared.Weapons.Ranged.Components;
 using Content.Shared.Weapons.Ranged.Events;
@@ -80,10 +78,9 @@ public sealed class PneumaticCannonSystem : SharedPneumaticCannonSystem
         if (gas == null && component.GasUsage > 0f)
             return;
 
-        if (TryComp<StatusEffectsComponent>(args.User, out var status)
-            && component.Power == PneumaticCannonPower.High)
+        if (component.Power == PneumaticCannonPower.High
+            && _stun.TryUpdateParalyzeDuration(args.User, TimeSpan.FromSeconds(component.HighPowerStunTime)))
         {
-            _stun.TryParalyze(args.User, TimeSpan.FromSeconds(component.HighPowerStunTime), true, status);
             Popup.PopupEntity(Loc.GetString("pneumatic-cannon-component-power-stun",
                 ("cannon", uid)), cannon, args.User);
         }
index 2d38aee6ea6fc482c5ee64b90092fb1e2f1aa035..b89f10934d98f67690633d30690be9dda11503b4 100644 (file)
@@ -177,7 +177,7 @@ public sealed partial class RevenantSystem : EntitySystem
         ChangeEssenceAmount(uid, -abilityCost, component, false);
 
         _statusEffects.TryAddStatusEffect<CorporealComponent>(uid, "Corporeal", TimeSpan.FromSeconds(debuffs.Y), false);
-        _stun.TryStun(uid, TimeSpan.FromSeconds(debuffs.X), false);
+        _stun.TryAddStunDuration(uid, TimeSpan.FromSeconds(debuffs.X));
 
         return true;
     }
index 13e13bf8f371fb996d6c6442fd920f9cb77c4982..a63359077f3827a6fce97e48c482cbb6c2e7660f 100644 (file)
@@ -616,10 +616,7 @@ public sealed partial class ShuttleSystem
         {
             foreach (var child in toKnock)
             {
-                if (!_statusQuery.TryGetComponent(child, out var status))
-                    continue;
-
-                _stuns.TryParalyze(child, _hyperspaceKnockdownTime, true, status);
+                _stuns.TryUpdateParalyzeDuration(child, _hyperspaceKnockdownTime);
 
                 // If the guy we knocked down is on a spaced tile, throw them too
                 if (grid != null)
index 29657af295f18f6a0a1a0a6bf8a6fb9cb77eedc4..e78a17e180513545ac6effb7e36391e1ba679b60 100644 (file)
@@ -249,7 +249,7 @@ public sealed partial class ShuttleSystem
 
             if (direction.LengthSquared() > minsq)
             {
-                _stuns.TryKnockdown(uid, knockdownTime, true);
+                _stuns.TryUpdateKnockdownDuration(uid, knockdownTime);
                 _throwing.TryThrow(uid, direction, physics, Transform(uid), _projQuery, direction.Length(), playSound: false);
             }
             else
index b998270829bb80cde89e120ed6d25e031c2aec22..18c386d4ac05ccecf7d151e82b844e2edee0c826 100644 (file)
@@ -1,8 +1,6 @@
 using Content.Server.Stunnable.Components;
-using Content.Shared.Standing;
-using Content.Shared.StatusEffect;
+using Content.Shared.Movement.Systems;
 using JetBrains.Annotations;
-using Robust.Shared.Physics.Dynamics;
 using Content.Shared.Throwing;
 using Robust.Shared.Physics.Events;
 
@@ -12,6 +10,7 @@ namespace Content.Server.Stunnable
     internal sealed class StunOnCollideSystem : EntitySystem
     {
         [Dependency] private readonly StunSystem _stunSystem = default!;
+        [Dependency] private readonly MovementModStatusSystem _movementMod = default!;
 
         public override void Initialize()
         {
@@ -22,15 +21,19 @@ namespace Content.Server.Stunnable
 
         private void TryDoCollideStun(EntityUid uid, StunOnCollideComponent component, EntityUid target)
         {
-            if (!TryComp<StatusEffectsComponent>(target, out var status))
-                return;
-
-            _stunSystem.TryStun(target, component.StunAmount, component.Refresh, status);
+            _stunSystem.TryUpdateStunDuration(target, component.StunAmount);
 
-            _stunSystem.TryKnockdown(target, component.KnockdownAmount, component.Refresh, component.AutoStand);
+            _stunSystem.TryKnockdown(target, component.KnockdownAmount, component.Refresh, component.AutoStand, force: true);
 
-            _stunSystem.TrySlowdown(target, component.SlowdownAmount, component.Refresh, component.WalkSpeedModifier, component.SprintSpeedModifier, status);
+            _movementMod.TryUpdateMovementSpeedModDuration(
+                target,
+                MovementModStatusSystem.TaserSlowdown,
+                component.SlowdownAmount,
+                component.WalkSpeedModifier,
+                component.SprintSpeedModifier
+            );
         }
+
         private void HandleCollide(EntityUid uid, StunOnCollideComponent component, ref StartCollideEvent args)
         {
             if (args.OurFixtureId != component.FixtureID)
index a220781420abe2f292bdb88a24f03667d5c9e256..039891a3bafd40651471e80a4907c2a3dca6e5b7 100644 (file)
@@ -34,7 +34,7 @@ public sealed class GoliathTentacleSystem : EntitySystem
         // TODO: animation
 
         _popup.PopupPredicted(Loc.GetString("tentacle-ability-use-popup", ("entity", args.Performer)), args.Performer, args.Performer, type: PopupType.SmallCaution);
-        _stun.TryStun(args.Performer, TimeSpan.FromSeconds(0.8f), false);
+        _stun.TryAddStunDuration(args.Performer, TimeSpan.FromSeconds(0.8f));
 
         var coords = args.Target;
         List<EntityCoordinates> spawnPos = new();
index 3534fb88d5c6ac30d58ea618741b992c89cf4717..61803d7c7d85839c528db4838e819809314cd868 100644 (file)
@@ -18,7 +18,6 @@ using Content.Shared.Slippery;
 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;
@@ -38,8 +37,8 @@ public sealed partial class SleepingSystem : EntitySystem
     [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedEmitSoundSystem _emitSound = default!;
-    [Dependency] private readonly StatusEffect.StatusEffectsSystem _statusEffectOld = default!;
-    [Dependency] private readonly StatusEffectNew.StatusEffectsSystem _statusEffectNew = default!;
+    [Dependency] private readonly StatusEffectsSystem _statusEffect = default!;
+    [Dependency] private readonly SharedStunSystem _stun = default!;
 
     public static readonly EntProtoId SleepActionId = "ActionSleep";
     public static readonly EntProtoId WakeActionId = "ActionWake";
@@ -67,6 +66,8 @@ public sealed partial class SleepingSystem : EntitySystem
         SubscribeLocalEvent<SleepingComponent, ExaminedEvent>(OnExamined);
         SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb);
         SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
+        SubscribeLocalEvent<SleepingComponent, StunEndAttemptEvent>(OnStunEndAttempt);
+        SubscribeLocalEvent<SleepingComponent, StandUpAttemptEvent>(OnStandUpAttempt);
 
         SubscribeLocalEvent<ForcedSleepingStatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
         SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
@@ -106,9 +107,7 @@ public sealed partial class SleepingSystem : EntitySystem
     {
         if (args.FellAsleep)
         {
-            // Expiring status effects would remove the components needed for sleeping
-            _statusEffectOld.TryRemoveStatusEffect(ent.Owner, "Stun");
-
+            // Just in case we're not using the sleeping status
             EnsureComp<StunnedComponent>(ent);
             EnsureComp<KnockedDownComponent>(ent);
 
@@ -128,8 +127,9 @@ public sealed partial class SleepingSystem : EntitySystem
             return;
         }
 
-        RemComp<StunnedComponent>(ent);
-        RemComp<KnockedDownComponent>(ent);
+        _stun.TryUnstun(ent.Owner);
+        _stun.TryStanding(ent.Owner, out _);
+
         RemComp<SpamEmitSoundComponent>(ent);
     }
 
@@ -174,6 +174,17 @@ public sealed partial class SleepingSystem : EntitySystem
         args.Cancelled = true;
     }
 
+    private void OnStunEndAttempt(Entity<SleepingComponent> ent, ref StunEndAttemptEvent args)
+    {
+        args.Cancelled = true;
+    }
+
+    private void OnStandUpAttempt(Entity<SleepingComponent> ent, ref StandUpAttemptEvent args)
+    {
+        // Shh the Urist McHands is sleeping...
+        args.Cancelled = true;
+    }
+
     private void OnExamined(Entity<SleepingComponent> ent, ref ExaminedEvent args)
     {
         if (args.IsInDetailsRange)
@@ -187,7 +198,6 @@ public sealed partial class SleepingSystem : EntitySystem
         if (!args.CanInteract || !args.CanAccess)
             return;
 
-        var target = args.Target;
         var user = args.User;
         AlternativeVerb verb = new()
         {
@@ -309,7 +319,7 @@ public sealed partial class SleepingSystem : EntitySystem
         if (!Resolve(ent, ref ent.Comp, false))
             return false;
 
-        if (!force && _statusEffectNew.HasEffectComp<ForcedSleepingStatusEffectComponent>(ent))
+        if (!force && _statusEffect.HasEffectComp<ForcedSleepingStatusEffectComponent>(ent))
         {
             if (user != null)
             {
index 0067eb87b2dcdb39134ea5f738d5b91b26ff4c72..45055ebbccac172d6a4b71ff185ebf85e2a05837 100644 (file)
@@ -571,7 +571,7 @@ public sealed partial class ClimbSystem : VirtualController
 
         _damageableSystem.TryChangeDamage(args.Climber, component.ClimberDamage, origin: args.Climber);
         _damageableSystem.TryChangeDamage(uid, component.TableDamage, origin: args.Climber);
-        _stunSystem.TryParalyze(args.Climber, TimeSpan.FromSeconds(component.StunTime), true);
+        _stunSystem.TryUpdateParalyzeDuration(args.Climber, TimeSpan.FromSeconds(component.StunTime));
 
         // Not shown to the user, since they already get a 'you climb on the glass table' popup
         _popupSystem.PopupEntity(
index 9e0e82364f88598733cff6681dabd5807e182db6..2506359c25672b18cfeaa8d4d81552a95d0d0d3c 100644 (file)
@@ -129,7 +129,7 @@ public sealed class ClumsySystem : EntitySystem
         if (ent.Comp.GunShootFailDamage != null)
             _damageable.TryChangeDamage(ent, ent.Comp.GunShootFailDamage, origin: ent);
 
-        _stun.TryParalyze(ent, ent.Comp.GunShootFailStunTime, true);
+        _stun.TryUpdateParalyzeDuration(ent, ent.Comp.GunShootFailStunTime);
 
         // Apply salt to the wound ("Honk!") (No idea what this comment means)
         _audio.PlayPvs(ent.Comp.GunShootFailSound, ent);
@@ -202,7 +202,7 @@ public sealed class ClumsySystem : EntitySystem
                 _damageable.TryChangeDamage(target, bonkComp.BonkDamage, true);
         }
 
-        _stun.TryParalyze(target, stunTime, true);
+        _stun.TryUpdateParalyzeDuration(target, stunTime);
     }
     #endregion
 }
index 6ef41e87219652081972ac233ef0ff627143e4d9..33d7c65a45bd4fcfb7266e97722534810384d778 100644 (file)
@@ -46,7 +46,7 @@ public sealed class DamageOnHighSpeedImpactSystem : EntitySystem
         component.LastHit = _gameTiming.CurTime;
 
         if (_robustRandom.Prob(component.StunChance))
-            _stun.TryStun(uid, TimeSpan.FromSeconds(component.StunSeconds), true);
+            _stun.TryUpdateStunDuration(uid, TimeSpan.FromSeconds(component.StunSeconds));
 
         var damageScale = component.SpeedDamageFactor * speed / component.MinimumSpeed;
 
index 0f43e93abf3aa151b1245d9d834b0f1051756073..bd3b6979f70056de8c973be0608a95baa5f6434a 100644 (file)
@@ -92,7 +92,7 @@ public sealed class DamageOnInteractSystem : EntitySystem
 
             // Attempt to paralyze the user after they have taken damage
             if (_random.Prob(entity.Comp.StunChance))
-                _stun.TryParalyze(args.User, TimeSpan.FromSeconds(entity.Comp.StunSeconds), true);
+                _stun.TryUpdateParalyzeDuration(args.User, TimeSpan.FromSeconds(entity.Comp.StunSeconds));
         }
         // Check if the entity's Throw bool is false, or if the entity has the PullableComponent, then if the entity is currently being pulled.
         // BeingPulled must be checked because the entity will be spastically thrown around without this.
index 6687ecd7dff50054df3929b6d84a216f4386ec8e..b2d22391ebe3e4e4c5949f42789b12409e2840c9 100644 (file)
@@ -8,9 +8,12 @@ using Content.Shared.Damage.Events;
 using Content.Shared.Database;
 using Content.Shared.Effects;
 using Content.Shared.FixedPoint;
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Systems;
 using Content.Shared.Projectiles;
 using Content.Shared.Rejuvenate;
 using Content.Shared.Rounding;
+using Content.Shared.StatusEffectNew;
 using Content.Shared.Stunnable;
 using Content.Shared.Throwing;
 using Content.Shared.Weapons.Melee.Events;
@@ -20,6 +23,7 @@ using Robust.Shared.Audio.Systems;
 using Robust.Shared.Configuration;
 using Robust.Shared.Network;
 using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 using Robust.Shared.Timing;
 
@@ -27,15 +31,19 @@ namespace Content.Shared.Damage.Systems;
 
 public abstract partial class SharedStaminaSystem : EntitySystem
 {
+    public static readonly EntProtoId StaminaLow = "StatusEffectStaminaLow";
+
+    [Dependency] private readonly IConfigurationManager _config = default!;
     [Dependency] protected readonly IGameTiming Timing = default!;
     [Dependency] private readonly INetManager _net = default!;
     [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
     [Dependency] private readonly AlertsSystem _alerts = default!;
     [Dependency] private readonly MetaDataSystem _metadata = default!;
+    [Dependency] private readonly MovementModStatusSystem _movementMod = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
+    [Dependency] private readonly StatusEffectsSystem _status = default!;
     [Dependency] protected readonly SharedStunSystem StunSystem = default!;
-    [Dependency] private readonly SharedAudioSystem _audio = default!;
-    [Dependency] private readonly IConfigurationManager _config = default!;
 
     /// <summary>
     /// How much of a buffer is there between the stun duration and when stuns can be re-applied.
@@ -113,8 +121,9 @@ public abstract partial class SharedStaminaSystem : EntitySystem
         }
 
         entity.Comp.StaminaDamage = 0;
-        AdjustSlowdown(entity.Owner);
+        AdjustStatus(entity.Owner);
         RemComp<ActiveStaminaComponent>(entity);
+        _status.TryRemoveStatusEffect(entity, StaminaLow);
         UpdateStaminaVisuals(entity);
         Dirty(entity);
     }
@@ -284,7 +293,7 @@ public abstract partial class SharedStaminaSystem : EntitySystem
                 component.NextUpdate = nextUpdate;
         }
 
-        AdjustSlowdown(uid);
+        AdjustStatus(uid);
 
         UpdateStaminaVisuals((uid, component));
 
@@ -292,6 +301,7 @@ public abstract partial class SharedStaminaSystem : EntitySystem
         if (component.AfterCritical && oldDamage > component.StaminaDamage && component.StaminaDamage <= 0f)
         {
             component.AfterCritical = false; // Since the recovery from the crit has been completed, we are no longer 'after crit'
+            _status.TryRemoveStatusEffect(uid, StaminaLow);
         }
 
         if (!component.Critical)
@@ -384,7 +394,7 @@ public abstract partial class SharedStaminaSystem : EntitySystem
         component.Critical = true;
         component.StaminaDamage = component.CritThreshold;
 
-        if (StunSystem.TryParalyze(uid, component.StunTime, true))
+        if (StunSystem.TryUpdateParalyzeDuration(uid, component.StunTime))
             StunSystem.TrySeeingStars(uid);
 
         // Give them buffer before being able to be re-stunned
@@ -412,18 +422,19 @@ public abstract partial class SharedStaminaSystem : EntitySystem
     }
 
     /// <summary>
-    /// Adjusts the movement speed of an entity based on its current <see cref="StaminaComponent.StaminaDamage"/> value.
-    /// If the entity has a <see cref="SlowOnDamageComponent"/>, its custom damage-to-speed thresholds are used,
-    /// otherwise, a default set of thresholds is applied.
-    /// The method determines the closest applicable damage threshold below the crit limit and applies the corresponding
-    /// speed modifier using the stun system. If no threshold is met then the entity's speed is restored to normal.
+    /// Adjusts the modifiers of the <see cref="StaminaLow"/> status effect entity and applies relevant statuses.
+    /// System iterates through the <see cref="StaminaComponent.StunModifierThresholds"/> to find correct movement modifer.
+    /// This modifier is saved to the Stamina Low Status Effect entity's <see cref="MovementModStatusEffectComponent"/>.
     /// </summary>
     /// <param name="ent">Entity to update</param>
-    private void AdjustSlowdown(Entity<StaminaComponent?> ent)
+    private void AdjustStatus(Entity<StaminaComponent?> ent)
     {
         if (!Resolve(ent, ref ent.Comp))
             return;
 
+        if (!_status.TrySetStatusEffectDuration(ent, StaminaLow, out var status))
+            return;
+
         var closest = FixedPoint2.Zero;
 
         // Iterate through the dictionary in the similar way as in Damage.SlowOnDamageSystem.OnRefreshMovespeed
@@ -435,7 +446,7 @@ public abstract partial class SharedStaminaSystem : EntitySystem
                 closest = thres.Key;
         }
 
-        StunSystem.UpdateStunModifiers(ent, ent.Comp.StunModifierThresholds[closest]);
+        _movementMod.TryUpdateMovementStatus(ent.Owner, status.Value, ent.Comp.StunModifierThresholds[closest]);
     }
 
     [Serializable, NetSerializable]
index 7914a38334ee58696759fd9a8610ca97d713dc47..50132e42ddfaa02f730a5700fbf27fb86fc97ccc 100644 (file)
@@ -534,7 +534,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
             if (door.CrushDamage != null)
                 _damageableSystem.TryChangeDamage(entity, door.CrushDamage, origin: uid);
 
-            _stunSystem.TryParalyze(entity, stunTime, true);
+            _stunSystem.TryUpdateParalyzeDuration(entity, stunTime);
         }
 
         if (door.CurrentlyCrushing.Count == 0)
index 2da6a6a0794c1808fa906018c1db186438fea656..2a2270016b70e11fe50eb31fac626700e6aa78c9 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Shared.EntityEffects;
 using Content.Shared.Stunnable;
 using Robust.Shared.Prototypes;
 
@@ -14,9 +13,11 @@ public sealed partial class Paralyze : EntityEffect
     [DataField] public bool Refresh = true;
 
     protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-paralyze",
+        => Loc.GetString(
+            "reagent-effect-guidebook-paralyze",
             ("chance", Probability),
-            ("time", ParalyzeTime));
+            ("time", ParalyzeTime)
+        );
 
     public override void Effect(EntityEffectBaseArgs args)
     {
@@ -27,7 +28,10 @@ public sealed partial class Paralyze : EntityEffect
             paralyzeTime *= (double)reagentArgs.Scale;
         }
 
-        args.EntityManager.System<SharedStunSystem>().TryParalyze(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime), Refresh);
+        var stunSystem = args.EntityManager.System<SharedStunSystem>();
+        _ = Refresh
+            ? stunSystem.TryUpdateParalyzeDuration(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime))
+            : stunSystem.TryAddParalyzeDuration(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime));
     }
 }
 
index 50652f940888debd53a6bd98b76c2577215b3d5d..dd6c9c91c10e478a18d2b3fdbca89512db5c7cdd 100644 (file)
@@ -20,6 +20,7 @@ using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
 using System.Linq;
+using Content.Shared.Movement.Systems;
 
 namespace Content.Shared.Flash;
 
@@ -33,6 +34,7 @@ public abstract class SharedFlashSystem : EntitySystem
     [Dependency] private readonly ExamineSystemShared _examine = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
     [Dependency] private readonly SharedStunSystem _stun = default!;
+    [Dependency] private readonly MovementModStatusSystem _movementMod = default!;
     [Dependency] private readonly TagSystem _tag = default!;
     [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
     [Dependency] private readonly IGameTiming _timing = default!;
@@ -163,9 +165,9 @@ public abstract class SharedFlashSystem : EntitySystem
             return;
 
         if (stunDuration != null)
-            _stun.TryParalyze(target, stunDuration.Value, true);
+            _stun.TryUpdateParalyzeDuration(target, stunDuration.Value);
         else
-            _stun.TrySlowdown(target, flashDuration, true, slowTo, slowTo);
+            _movementMod.TryUpdateMovementSpeedModDuration(target, MovementModStatusSystem.FlashSlowdown, flashDuration, slowTo);
 
         if (displayPopup && user != null && target != user && Exists(user.Value))
         {
index e5258f25183489801b8b9322c23270793f31fdc5..19d649d1f3128572c3e6cb81c1ce2eabed880c4b 100644 (file)
@@ -510,8 +510,8 @@ public abstract class SharedMagicSystem : EntitySystem
             _mind.TransferTo(tarMind, ev.Performer);
         }
 
-        _stun.TryParalyze(ev.Target, ev.TargetStunDuration, true);
-        _stun.TryParalyze(ev.Performer, ev.PerformerStunDuration, true);
+        _stun.TryUpdateParalyzeDuration(ev.Target, ev.TargetStunDuration);
+        _stun.TryUpdateParalyzeDuration(ev.Performer, ev.PerformerStunDuration);
     }
 
     #endregion
diff --git a/Content.Shared/Movement/Components/MovementModStatusEffectComponent.cs b/Content.Shared/Movement/Components/MovementModStatusEffectComponent.cs
new file mode 100644 (file)
index 0000000..22b196d
--- /dev/null
@@ -0,0 +1,25 @@
+using Content.Shared.Movement.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Movement.Components;
+
+/// <summary>
+/// This is used to store a movement speed modifier attached to a status effect entity so it can be applied via statuses.
+/// To be used in conjunction with <see cref="MovementModStatusSystem"/>.
+/// See <see cref="MovementModStatusComponent"/> for the component applied to the entity.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(MovementModStatusSystem))]
+public sealed partial class MovementModStatusEffectComponent : Component
+{
+    /// <summary>
+    /// Multiplicative sprint modifier, with bounds of [0, 1)
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float SprintSpeedModifier = 0.5f;
+
+    /// <summary>
+    /// Multiplicative walk modifier, with bounds of [0, 1)
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float WalkSpeedModifier = 0.5f;
+}
index 4fd64679479e123d89e8bde2cedc118d8cbd1bd1..37039fa6d4bca0dce27685a1c595894df5b150b4 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Components;
 using Content.Shared.Movement.Events;
 using Content.Shared.StatusEffectNew;
 using Robust.Shared.Prototypes;
@@ -6,23 +6,54 @@ using Robust.Shared.Prototypes;
 namespace Content.Shared.Movement.Systems;
 
 /// <summary>
-/// This handles the application of movement and friction modifiers to an entity as status effects.
+/// This handles the slowed status effect and other movement status effects.
+/// <see cref="MovementModStatusEffectComponent"/> holds a modifier for a status effect which is relayed to a mob's
+/// All effects of this kinda are multiplicative.
+/// Each 'source' of speed modification usually should have separate effect prototype.
 /// </summary>
+/// <remarks>
+/// Movement modifying status effects should by default be separate effect prototypes, and their effects
+/// should stack with each other (multiply). In case multiplicative effect is undesirable - such effects
+/// could occupy same prototype, but be aware that this will make controlling duration of effect
+/// extra 'challenging', as it will be shared too.
+/// </remarks>
 public sealed class MovementModStatusSystem : EntitySystem
 {
+    public static readonly EntProtoId VomitingSlowdown = "VomitingSlowdownStatusEffect";
+    public static readonly EntProtoId TaserSlowdown = "TaserSlowdownStatusEffect";
+    public static readonly EntProtoId FlashSlowdown = "FlashSlowdownStatusEffect";
     public static readonly EntProtoId StatusEffectFriction = "StatusEffectFriction";
 
     [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
     [Dependency] private readonly StatusEffectsSystem _status = default!;
 
-    /// <inheritdoc/>
     public override void Initialize()
     {
+        SubscribeLocalEvent<MovementModStatusEffectComponent, StatusEffectRemovedEvent>(OnMovementModRemoved);
+        SubscribeLocalEvent<MovementModStatusEffectComponent, StatusEffectRelayedEvent<RefreshMovementSpeedModifiersEvent>>(OnRefreshRelay);
         SubscribeLocalEvent<FrictionStatusEffectComponent, StatusEffectRemovedEvent>(OnFrictionStatusEffectRemoved);
         SubscribeLocalEvent<FrictionStatusEffectComponent, StatusEffectRelayedEvent<RefreshFrictionModifiersEvent>>(OnRefreshFrictionStatus);
         SubscribeLocalEvent<FrictionStatusEffectComponent, StatusEffectRelayedEvent<TileFrictionEvent>>(OnRefreshTileFrictionStatus);
     }
 
+    private void OnMovementModRemoved(Entity<MovementModStatusEffectComponent> ent, ref StatusEffectRemovedEvent args)
+    {
+        TryUpdateMovementStatus(args.Target, (ent, ent), 1f);
+    }
+
+    private void OnFrictionStatusEffectRemoved(Entity<FrictionStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
+    {
+        TrySetFrictionStatus(entity!, 1f, args.Target);
+    }
+
+    private void OnRefreshRelay(
+        Entity<MovementModStatusEffectComponent> entity,
+        ref StatusEffectRelayedEvent<RefreshMovementSpeedModifiersEvent> args
+    )
+    {
+        args.Args.ModifySpeed(entity.Comp.WalkSpeedModifier, entity.Comp.WalkSpeedModifier);
+    }
+
     private void OnRefreshFrictionStatus(Entity<FrictionStatusEffectComponent> ent, ref StatusEffectRelayedEvent<RefreshFrictionModifiersEvent> args)
     {
         var ev = args.Args;
@@ -39,27 +70,168 @@ public sealed class MovementModStatusSystem : EntitySystem
     }
 
     /// <summary>
-    ///     Applies a friction de-buff to the player.
+    /// Apply mob's walking/running speed modifier with provided duration, or increment duration of existing.
     /// </summary>
-    public bool TryFriction(EntityUid uid,
-        TimeSpan time,
-        bool refresh,
-        float friction,
-        float acceleration)
+    /// <param name="uid">Target entity, for which speed should be modified.</param>
+    /// <param name="effectProtoId">Slowdown effect to be used.</param>
+    /// <param name="duration">Duration of speed modifying effect.</param>
+    /// <param name="speedModifier">Multiplier by which walking/sprinting speed should be modified.</param>
+    /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+    public bool TryAddMovementSpeedModDuration(
+        EntityUid uid,
+        EntProtoId effectProtoId,
+        TimeSpan duration,
+        float speedModifier
+    )
+    {
+        return TryAddMovementSpeedModDuration(uid, effectProtoId, duration, speedModifier, speedModifier);
+    }
+
+    /// <summary>
+    /// Apply mob's walking/running speed modifier with provided duration, or increment duration of existing.
+    /// </summary>
+    /// <param name="uid">Target entity, for which speed should be modified.</param>
+    /// <param name="effectProtoId">Slowdown effect to be used.</param>
+    /// <param name="duration">Duration of speed modifying effect.</param>
+    /// <param name="walkSpeedModifier">Multiplier by which walking speed should be modified.</param>
+    /// <param name="sprintSpeedModifier">Multiplier by which sprinting speed should be modified.</param>
+    /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+    public bool TryAddMovementSpeedModDuration(
+        EntityUid uid,
+        EntProtoId effectProtoId,
+        TimeSpan duration,
+        float walkSpeedModifier,
+        float sprintSpeedModifier
+    )
+    {
+        return _status.TryAddStatusEffectDuration(uid, effectProtoId, out var status, duration)
+               && TryUpdateMovementStatus(uid, status!.Value, walkSpeedModifier, sprintSpeedModifier);
+    }
+
+    /// <summary>
+    /// Apply mob's walking/running speed modifier with provided duration,
+    /// or update duration of existing if it is lesser than provided duration.
+    /// </summary>
+    /// <param name="uid">Target entity, for which speed should be modified.</param>
+    /// <param name="effectProtoId">Slowdown effect to be used.</param>
+    /// <param name="duration">Duration of speed modifying effect.</param>
+    /// <param name="speedModifier">Multiplier by which walking/sprinting speed should be modified.</param>
+    /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+    public bool TryUpdateMovementSpeedModDuration(
+        EntityUid uid,
+        EntProtoId effectProtoId,
+        TimeSpan duration,
+        float speedModifier
+    )
+    {
+        return TryUpdateMovementSpeedModDuration(uid, effectProtoId, duration, speedModifier, speedModifier);
+    }
+
+    /// <summary>
+    /// Apply mob's walking/running speed modifier with provided duration,
+    /// or update duration of existing if it is lesser than provided duration.
+    /// </summary>
+    /// <param name="uid">Target entity, for which speed should be modified.</param>
+    /// <param name="effectProtoId">Slowdown effect to be used.</param>
+    /// <param name="duration">Duration of speed modifying effect.</param>
+    /// <param name="walkSpeedModifier">Multiplier by which walking speed should be modified.</param>
+    /// <param name="sprintSpeedModifier">Multiplier by which sprinting speed should be modified.</param>
+    /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+    public bool TryUpdateMovementSpeedModDuration(
+        EntityUid uid,
+        EntProtoId effectProtoId,
+        TimeSpan? duration,
+        float walkSpeedModifier,
+        float sprintSpeedModifier
+    )
+    {
+        return _status.TryUpdateStatusEffectDuration(uid, effectProtoId, out var status, duration)
+               && TryUpdateMovementStatus(uid, status!.Value, walkSpeedModifier, sprintSpeedModifier);
+    }
+
+    /// <summary>
+    /// Updates entity's movement speed using <see cref="MovementModStatusEffectComponent"/> to provided values.
+    /// Then refreshes the movement speed of the entity.
+    /// </summary>
+    /// <param name="uid">Entity whose component we're updating</param>
+    /// <param name="status">Status effect entity whose modifiers we are updating</param>
+    /// <param name="walkSpeedModifier">New walkSpeedModifer we're applying</param>
+    /// <param name="sprintSpeedModifier">New sprintSpeedModifier we're applying</param>
+    public bool TryUpdateMovementStatus(
+        EntityUid uid,
+        Entity<MovementModStatusEffectComponent?> status,
+        float walkSpeedModifier,
+        float sprintSpeedModifier
+    )
     {
-        if (time <= TimeSpan.Zero)
+        if (!Resolve(status, ref status.Comp))
             return false;
 
-        if (refresh)
-        {
-            return _status.TryUpdateStatusEffectDuration(uid, StatusEffectFriction, out var status, time)
-                   && TrySetFrictionStatus(status.Value, friction, acceleration, uid);
-        }
-        else
-        {
-            return _status.TryAddStatusEffectDuration(uid, StatusEffectFriction, out var status, time)
+        status.Comp.SprintSpeedModifier = sprintSpeedModifier;
+        status.Comp.WalkSpeedModifier = walkSpeedModifier;
+
+        _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
+
+        return true;
+    }
+
+    /// <summary>
+    /// Updates entity's movement speed using <see cref="MovementModStatusEffectComponent"/> to provided value.
+    /// Then refreshes the movement speed of the entity.
+    /// </summary>
+    /// <param name="uid">Entity whose component we're updating</param>
+    /// <param name="status">Status effect entity whose modifiers we are updating</param>
+    /// <param name="speedModifier">
+    /// Multiplier by which speed should be modified.
+    /// Will be applied to both walking and running speed.
+    /// </param>
+    public bool TryUpdateMovementStatus(
+        EntityUid uid,
+        Entity<MovementModStatusEffectComponent?> status,
+        float speedModifier
+    )
+    {
+        return TryUpdateMovementStatus(uid, status, speedModifier, speedModifier);
+    }
+
+    /// <summary>
+    /// Apply friction modifier with provided duration,
+    /// or incrementing duration of existing.
+    /// </summary>
+    /// <param name="uid">Target entity, for which friction modifier should be applied.</param>
+    /// <param name="duration">Duration of speed modifying effect.</param>
+    /// <param name="friction">Multiplier by which walking speed should be modified.</param>
+    /// <param name="acceleration">Multiplier by which sprinting speed should be modified.</param>
+    /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+    public bool TryAddFrictionModDuration(
+        EntityUid uid,
+        TimeSpan duration,
+        float friction,
+        float acceleration
+    )
+    {
+            return _status.TryAddStatusEffectDuration(uid, StatusEffectFriction, out var status, duration)
                    && TrySetFrictionStatus(status.Value, friction, acceleration, uid);
-        }
+    }
+
+    /// <summary>
+    /// Apply friction modifier with provided duration,
+    /// or update duration of existing if it is lesser than provided duration.
+    /// </summary>
+    /// <param name="uid">Target entity, for which friction modifier should be applied.</param>
+    /// <param name="duration">Duration of speed modifying effect.</param>
+    /// <param name="friction">Multiplier by which walking speed should be modified.</param>
+    /// <param name="acceleration">Multiplier by which sprinting speed should be modified.</param>
+    /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+    public bool TryUpdateFrictionModDuration(
+        EntityUid uid,
+        TimeSpan duration,
+        float friction,
+        float acceleration
+    )
+    {
+        return _status.TryUpdateStatusEffectDuration(uid, StatusEffectFriction, out var status, duration)
+               && TrySetFrictionStatus(status.Value, friction, acceleration, uid);
     }
 
     /// <summary>
@@ -92,9 +264,4 @@ public sealed class MovementModStatusSystem : EntitySystem
         _movementSpeedModifier.RefreshFrictionModifiers(entity);
         return true;
     }
-
-    private void OnFrictionStatusEffectRemoved(Entity<FrictionStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
-    {
-        TrySetFrictionStatus(entity!, 1f, args.Target);
-    }
 }
index fa07aa20c5de874d835e414a7787beb28d51f0a9..86eec26bfd295935f0ea2a35ba91eca866a4d365 100644 (file)
@@ -64,7 +64,7 @@ namespace Content.Shared.Nutrition.EntitySystems
 
             CreamedEntity(uid, creamPied, args);
 
-            _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(creamPie.ParalyzeTime), true);
+            _stunSystem.TryUpdateParalyzeDuration(uid, TimeSpan.FromSeconds(creamPie.ParalyzeTime));
         }
 
         protected virtual void CreamedEntity(EntityUid uid, CreamPiedComponent creamPied, ThrowHitByEvent args) {}
index bbf91193cc3112f7d5b1eeab6524ca04e88213e6..36a31d88a495c33553a7ea49f7f62be45428f031 100644 (file)
@@ -42,7 +42,7 @@ public abstract class SharedRevolutionarySystem : EntitySystem
             var stunTime = TimeSpan.FromSeconds(4);
             var name = Identity.Entity(uid, EntityManager);
             RemComp<RevolutionaryComponent>(uid);
-            _sharedStun.TryParalyze(uid, stunTime, true);
+            _sharedStun.TryUpdateParalyzeDuration(uid, stunTime);
             _popupSystem.PopupEntity(Loc.GetString("rev-break-control", ("name", name)), uid);
         }
     }
index e7433de60417a39d83aa5fbcfa22f07359063361..056431c1332fa0f87cec2308eccb4956dba14d26 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Shared.Emag.Systems;
+using Content.Shared.Emag.Systems;
 using Content.Shared.Mind;
 using Content.Shared.Popups;
 using Content.Shared.Silicons.Laws.Components;
@@ -57,7 +57,7 @@ public abstract partial class SharedSiliconLawSystem : EntitySystem
         if(_mind.TryGetMind(uid, out var mindId, out _))
             EnsureSubvertedSiliconRole(mindId);
 
-        _stunSystem.TryParalyze(uid, component.StunTime, true);
+        _stunSystem.TryUpdateParalyzeDuration(uid, component.StunTime);
 
         args.Handled = true;
     }
index f7e3da8edcf740f6705b7585d802d28c71ae9506..58a0ec06d5043dafae1d39772eda266800087091 100644 (file)
@@ -2,11 +2,9 @@ using Content.Shared.Administration.Logs;
 using Content.Shared.Damage.Systems;
 using Content.Shared.Database;
 using Content.Shared.Inventory;
-using Robust.Shared.Network;
 using Content.Shared.Movement.Components;
 using Content.Shared.Movement.Systems;
-using Content.Shared.Popups;
-using Content.Shared.StatusEffect;
+using Content.Shared.StatusEffectNew;
 using Content.Shared.StepTrigger.Systems;
 using Content.Shared.Stunnable;
 using Content.Shared.Throwing;
@@ -16,7 +14,6 @@ using Robust.Shared.Containers;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Systems;
 using Robust.Shared.Physics.Events;
-using Robust.Shared.Utility;
 
 namespace Content.Shared.Slippery;
 
@@ -27,8 +24,8 @@ public sealed class SlipperySystem : EntitySystem
     [Dependency] private readonly MovementModStatusSystem _movementMod = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedStunSystem _stun = default!;
+    [Dependency] private readonly StatusEffectsSystem _status = default!;
     [Dependency] private readonly SharedStaminaSystem _stamina = default!;
-    [Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
     [Dependency] private readonly SharedContainerSystem _container = default!;
     [Dependency] private readonly SharedPhysicsSystem _physics = default!;
     [Dependency] private readonly SpeedModifierContactsSystem _speedModifier = default!;
@@ -90,7 +87,7 @@ public sealed class SlipperySystem : EntitySystem
     private bool CanSlip(EntityUid uid, EntityUid toSlip)
     {
         return !_container.IsEntityInContainer(uid)
-                && _statusEffects.CanApplyEffect(toSlip, "Stun"); //Should be KnockedDown instead?
+                && _status.CanAddStatusEffect(toSlip, SharedStunSystem.StunId); //Should be KnockedDown instead?
     }
 
     public void TrySlip(EntityUid uid, SlipperyComponent component, EntityUid other, bool requiresContact = true)
@@ -125,12 +122,18 @@ public sealed class SlipperySystem : EntitySystem
         // Preventing from playing the slip sound and stunning when you are already knocked down.
         if (!HasComp<KnockedDownComponent>(other))
         {
-            _stun.TryStun(other, component.SlipData.StunTime, true);
+            _stun.TryUpdateStunDuration(other, component.SlipData.StunTime);
             _stamina.TakeStaminaDamage(other, component.StaminaDamage); // Note that this can stamCrit
-            _movementMod.TryFriction(other, component.FrictionStatusTime, true, component.SlipData.SlipFriction, component.SlipData.SlipFriction);
+            _movementMod.TryUpdateFrictionModDuration(
+                other,
+                component.FrictionStatusTime,
+                component.SlipData.SlipFriction,
+                component.SlipData.SlipFriction
+            );
             _audio.PlayPredicted(component.SlipSound, other, other);
         }
-        _stun.TryKnockdown(other, component.SlipData.KnockdownTime, true, true);
+
+        _stun.TryKnockdown(other, component.SlipData.KnockdownTime, true, force: true);
 
         _adminLogger.Add(LogType.Slip, LogImpact.Low, $"{ToPrettyString(other):mob} slipped on collision with {ToPrettyString(uid):entity}");
     }
index d2ceecf28e8b4e8140cdf82e462ce0b12c0abdeb..943432522d80404974d11888a2b8771285b61c30 100644 (file)
@@ -63,7 +63,7 @@ public sealed partial class ReformSystem : EntitySystem
     {
         // Stun them when they use the action for the amount of reform time.
         if (comp.ShouldStun)
-            _stunSystem.TryStun(uid, TimeSpan.FromSeconds(comp.ReformTime), true);
+            _stunSystem.TryUpdateStunDuration(uid, TimeSpan.FromSeconds(comp.ReformTime));
         _popupSystem.PopupClient(Loc.GetString(comp.PopupText, ("name", uid)), uid, uid);
 
         // Create a doafter & start it
index d508ea8b739bf0ca6bbd73557804247410c6ed78..d30ab79c2b47067488a4a28f698eedc01f22d0dc 100644 (file)
@@ -31,7 +31,7 @@ public sealed partial class StatusEffectsSystem
     }
 
 
-    ///<inheritdoc cref="TryAddStatusEffectDuration(Robust.Shared.GameObjects.EntityUid,Robust.Shared.Prototypes.EntProtoId,out Robust.Shared.GameObjects.EntityUid?,System.TimeSpan)"/>
+    ///<inheritdoc cref="TryAddStatusEffectDuration(EntityUid,EntProtoId,out EntityUid?,TimeSpan)"/>
     public bool TryAddStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan duration)
     {
         return TryAddStatusEffectDuration(target, effectProto, out _, duration);
@@ -61,7 +61,7 @@ public sealed partial class StatusEffectsSystem
         return true;
     }
 
-    /// <inheritdoc cref="TrySetStatusEffectDuration(Robust.Shared.GameObjects.EntityUid,Robust.Shared.Prototypes.EntProtoId,out Robust.Shared.GameObjects.EntityUid?,System.TimeSpan?)"/>
+    /// <inheritdoc cref="TrySetStatusEffectDuration(EntityUid,EntProtoId,out EntityUid?,TimeSpan?)"/>
     public bool TrySetStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null)
     {
         return TrySetStatusEffectDuration(target, effectProto, out _, duration);
@@ -91,7 +91,7 @@ public sealed partial class StatusEffectsSystem
         return true;
     }
 
-    /// <inheritdoc cref="TryUpdateStatusEffectDuration(Robust.Shared.GameObjects.EntityUid,Robust.Shared.Prototypes.EntProtoId,out Robust.Shared.GameObjects.EntityUid?,System.TimeSpan?)"/>
+    /// <inheritdoc cref="TryUpdateStatusEffectDuration(EntityUid,EntProtoId,out EntityUid?,TimeSpan?)"/>
     public bool TryUpdateStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null)
     {
         return TryUpdateStatusEffectDuration(target, effectProto, out _, duration);
index dddcbc660c03090ac4f6683003fbe4791ec1c1b4..96ef0121689d28b4fe46487c00959b8526150070 100644 (file)
@@ -1,6 +1,7 @@
 using Content.Shared.Movement.Events;
 using Content.Shared.Movement.Systems;
 using Content.Shared.StatusEffectNew.Components;
+using Content.Shared.Stunnable;
 using Robust.Shared.Player;
 
 namespace Content.Shared.StatusEffectNew;
@@ -12,8 +13,14 @@ public sealed partial class StatusEffectsSystem
         SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerAttachedEvent>(RelayStatusEffectEvent);
         SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerDetachedEvent>(RelayStatusEffectEvent);
 
+        SubscribeLocalEvent<StatusEffectContainerComponent, RefreshMovementSpeedModifiersEvent>(RelayStatusEffectEvent);
+        SubscribeLocalEvent<StatusEffectContainerComponent, UpdateCanMoveEvent>(RelayStatusEffectEvent);
+
         SubscribeLocalEvent<StatusEffectContainerComponent, RefreshFrictionModifiersEvent>(RefRelayStatusEffectEvent);
         SubscribeLocalEvent<StatusEffectContainerComponent, TileFrictionEvent>(RefRelayStatusEffectEvent);
+
+        SubscribeLocalEvent<StatusEffectContainerComponent, StandUpAttemptEvent>(RefRelayStatusEffectEvent);
+        SubscribeLocalEvent<StatusEffectContainerComponent, StunEndAttemptEvent>(RefRelayStatusEffectEvent);
     }
 
     private void RefRelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct
index 7f39a1f7c5431685ae634a1b05af49e544444a15..ce595109b3cb2d561f0707648e35dfdb202d6261 100644 (file)
@@ -156,7 +156,7 @@ public sealed partial class StatusEffectsSystem : EntitySystem
         Dirty(effect, effectComp);
     }
 
-    private bool CanAddStatusEffect(EntityUid uid, EntProtoId effectProto)
+    public bool CanAddStatusEffect(EntityUid uid, EntProtoId effectProto)
     {
         if (!_proto.TryIndex(effectProto, out var effectProtoData))
             return false;
diff --git a/Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs b/Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs
new file mode 100644 (file)
index 0000000..79b2fb6
--- /dev/null
@@ -0,0 +1,9 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Stunnable;
+
+/// <summary>
+/// Knockdown as a status effect.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedStunSystem))]
+public sealed partial class KnockdownStatusEffectComponent : Component;
index 2146c27a21809237c147e4e07eba579dbe8b8604..26e8e47f033e87109d71bc3cfe2a5f242454c443 100644 (file)
@@ -51,7 +51,7 @@ public abstract partial class SharedStunSystem
 
         // Action blockers
         SubscribeLocalEvent<KnockedDownComponent, BuckleAttemptEvent>(OnBuckleAttempt);
-        SubscribeLocalEvent<KnockedDownComponent, StandAttemptEvent>(OnStandUpAttempt);
+        SubscribeLocalEvent<KnockedDownComponent, StandAttemptEvent>(OnStandAttempt);
 
         // Updating movement a friction
         SubscribeLocalEvent<KnockedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshKnockedSpeed);
@@ -452,7 +452,7 @@ public abstract partial class SharedStunSystem
 
     #region Action Blockers
 
-    private void OnStandUpAttempt(Entity<KnockedDownComponent> entity, ref StandAttemptEvent args)
+    private void OnStandAttempt(Entity<KnockedDownComponent> entity, ref StandAttemptEvent args)
     {
         if (entity.Comp.LifeStage <= ComponentLifeStage.Running)
             args.Cancel();
index 2f75b71f56116d8534a35d47cd06cfec3c91c90f..4c4523849bae82b8254d8b9c4f3fb69c793bec7d 100644 (file)
@@ -4,7 +4,6 @@ using Content.Shared.Alert;
 using Content.Shared.Interaction.Events;
 using Content.Shared.Inventory.Events;
 using Content.Shared.Item;
-using Content.Shared.Damage.Components;
 using Content.Shared.Damage.Systems;
 using Content.Shared.Database;
 using Content.Shared.DoAfter;
@@ -14,35 +13,35 @@ using Content.Shared.Mobs.Components;
 using Content.Shared.Movement.Events;
 using Content.Shared.Movement.Systems;
 using Content.Shared.Standing;
-using Content.Shared.StatusEffect;
+using Content.Shared.StatusEffectNew;
 using Content.Shared.Throwing;
 using Content.Shared.Whitelist;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Physics.Events;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Timing;
 
 namespace Content.Shared.Stunnable;
 
 public abstract partial class SharedStunSystem : EntitySystem
 {
-    [Dependency] protected readonly ActionBlockerSystem Blocker = default!;
-    [Dependency] protected readonly AlertsSystem Alerts = default!;
+    public static readonly EntProtoId StunId = "StatusEffectStunned";
+    public static readonly EntProtoId KnockdownId = "StatusEffectKnockdown";
+
     [Dependency] protected readonly IGameTiming GameTiming = default!;
     [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+    [Dependency] protected readonly ActionBlockerSystem Blocker = default!;
+    [Dependency] protected readonly AlertsSystem Alerts = default!;
     [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
     [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
     [Dependency] protected readonly SharedDoAfterSystem DoAfter = default!;
     [Dependency] protected readonly SharedStaminaSystem Stamina = default!;
-    [Dependency] private readonly StatusEffectsSystem _statusEffect = default!;
+    [Dependency] private readonly StatusEffectsSystem _status = default!;
 
     public override void Initialize()
     {
-        SubscribeLocalEvent<SlowedDownComponent, ComponentInit>(OnSlowInit);
-        SubscribeLocalEvent<SlowedDownComponent, ComponentShutdown>(OnSlowRemove);
-        SubscribeLocalEvent<SlowedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
-
         SubscribeLocalEvent<StunnedComponent, ComponentStartup>(UpdateCanMove);
         SubscribeLocalEvent<StunnedComponent, ComponentShutdown>(OnStunShutdown);
 
@@ -61,6 +60,14 @@ public abstract partial class SharedStunSystem : EntitySystem
         SubscribeLocalEvent<StunnedComponent, IsUnequippingAttemptEvent>(OnUnequipAttempt);
         SubscribeLocalEvent<MobStateComponent, MobStateChangedEvent>(OnMobStateChanged);
 
+        // New Status Effect subscriptions
+        SubscribeLocalEvent<StunnedStatusEffectComponent, StatusEffectAppliedEvent>(OnStunEffectApplied);
+        SubscribeLocalEvent<StunnedStatusEffectComponent, StatusEffectRemovedEvent>(OnStunStatusRemoved);
+        SubscribeLocalEvent<StunnedStatusEffectComponent, StatusEffectRelayedEvent<StunEndAttemptEvent>>(OnStunEndAttempt);
+
+        SubscribeLocalEvent<KnockdownStatusEffectComponent, StatusEffectRelayedEvent<StandUpAttemptEvent>>(OnStandUpAttempt);
+
+        // Stun Appearance Data
         InitializeKnockdown();
         InitializeAppearance();
     }
@@ -72,10 +79,6 @@ public abstract partial class SharedStunSystem : EntitySystem
 
     private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobStateChangedEvent args)
     {
-        if (!TryComp<StatusEffectsComponent>(uid, out var status))
-        {
-            return;
-        }
         switch (args.NewMobState)
         {
             case MobState.Alive:
@@ -84,12 +87,12 @@ public abstract partial class SharedStunSystem : EntitySystem
                 }
             case MobState.Critical:
                 {
-                    _statusEffect.TryRemoveStatusEffect(uid, "Stun");
+                    _status.TryRemoveStatusEffect(uid, StunId);
                     break;
                 }
             case MobState.Dead:
                 {
-                    _statusEffect.TryRemoveStatusEffect(uid, "Stun");
+                    _status.TryRemoveStatusEffect(uid, StunId);
                     break;
                 }
             case MobState.Invalid:
@@ -119,71 +122,95 @@ public abstract partial class SharedStunSystem : EntitySystem
         if (_entityWhitelist.IsBlacklistPass(ent.Comp.Blacklist, args.OtherEntity))
             return;
 
-        if (!TryComp<StatusEffectsComponent>(args.OtherEntity, out var status))
-            return;
-
-        TryStun(args.OtherEntity, ent.Comp.Duration, true, status);
-        TryKnockdown(args.OtherEntity, ent.Comp.Duration, ent.Comp.Refresh, ent.Comp.AutoStand);
+        TryUpdateStunDuration(args.OtherEntity, ent.Comp.Duration);
+        TryKnockdown(args.OtherEntity, ent.Comp.Duration, true, force: true);
     }
 
-    private void OnSlowInit(EntityUid uid, SlowedDownComponent component, ComponentInit args)
+    // TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...)
+    public bool TryAddStunDuration(EntityUid uid, TimeSpan duration)
     {
-        _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
+        if (!_status.TryAddStatusEffectDuration(uid, StunId, duration))
+            return false;
+
+        OnStunnedSuccessfully(uid, duration);
+        return true;
     }
 
-    private void OnSlowRemove(EntityUid uid, SlowedDownComponent component, ComponentShutdown args)
+    public bool TryUpdateStunDuration(EntityUid uid, TimeSpan? duration)
     {
-        component.SprintSpeedModifier = 1f;
-        component.WalkSpeedModifier = 1f;
-        _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
-    }
+        if (!_status.TryUpdateStatusEffectDuration(uid, StunId, duration))
+            return false;
 
-    // TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...)
+        OnStunnedSuccessfully(uid, duration);
+        return true;
+    }
 
-    /// <summary>
-    ///     Stuns the entity, disallowing it from doing many interactions temporarily.
-    /// </summary>
-    public bool TryStun(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null)
+    private void OnStunnedSuccessfully(EntityUid uid, TimeSpan? duration)
     {
-        if (time <= TimeSpan.Zero)
-            return false;
+        var ev = new StunnedEvent(); // todo: rename event or change how it is raised - this event is raised each time duration of stun was externally changed
+        RaiseLocalEvent(uid, ref ev);
 
-        if (!Resolve(uid, ref status, false))
-            return false;
+        var timeForLogs = duration.HasValue
+            ? duration.Value.Seconds.ToString()
+            : "Infinite";
+        _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} stunned for {timeForLogs} seconds");
+    }
 
-        if (!_statusEffect.TryAddStatusEffect<StunnedComponent>(uid, "Stun", time, refresh))
+    public bool TryAddKnockdownDuration(EntityUid uid, TimeSpan duration)
+    {
+        if (!_status.TryAddStatusEffectDuration(uid, KnockdownId, duration))
             return false;
 
-        var ev = new StunnedEvent();
-        RaiseLocalEvent(uid, ref ev);
+        TryKnockdown(uid, duration, true, force: true);
 
-        _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} stunned for {time.Seconds} seconds");
         return true;
+
+    }
+
+    public bool TryUpdateKnockdownDuration(EntityUid uid, TimeSpan? duration)
+    {
+        if (!_status.TryUpdateStatusEffectDuration(uid, KnockdownId, duration))
+            return false;
+
+        return TryKnockdown(uid, duration, true, force: true);
     }
 
     /// <summary>
     ///     Knocks down the entity, making it fall to the ground.
     /// </summary>
-    public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh, bool autoStand = true, bool drop = true)
+    public bool TryKnockdown(Entity<StandingStateComponent?> entity, TimeSpan? time, bool refresh, bool autoStand = true, bool drop = true, bool force = false)
     {
         if (time <= TimeSpan.Zero)
             return false;
 
         // Can't fall down if you can't actually be downed.
-        if (!HasComp<StandingStateComponent>(uid))
+        if (!Resolve(entity, ref entity.Comp, false))
             return false;
 
-        var evAttempt = new KnockDownAttemptEvent(autoStand, drop);
-        RaiseLocalEvent(uid, ref evAttempt);
+        if (!force)
+        {
+            var evAttempt = new KnockDownAttemptEvent(autoStand, drop);
+            RaiseLocalEvent(entity, ref evAttempt);
 
-        if (evAttempt.Cancelled)
-            return false;
+            if (evAttempt.Cancelled)
+                return false;
 
+            autoStand = evAttempt.AutoStand;
+            drop = evAttempt.Drop;
+        }
+
+        Knockdown(entity!, time, autoStand, drop);
+
+        return true;
+    }
+
+    private void Knockdown(Entity<StandingStateComponent> entity, TimeSpan? time, bool refresh, bool autoStand = true, bool drop = true)
+    {
         // Initialize our component with the relevant data we need if we don't have it
-        if (EnsureComp<KnockedDownComponent>(uid, out var component))
+        if (EnsureComp<KnockedDownComponent>(entity, out var component))
         {
-            RefreshKnockedMovement((uid, component));
-            CancelKnockdownDoAfter((uid, component));
+            RefreshKnockedMovement((entity, component));
+            CancelKnockdownDoAfter((entity, component));
         }
         else
         {
@@ -191,126 +218,86 @@ public abstract partial class SharedStunSystem : EntitySystem
             if (drop)
             {
                 var ev = new DropHandItemsEvent();
-                RaiseLocalEvent(uid, ref ev);
+                RaiseLocalEvent(entity, ref ev);
             }
 
             // Only update Autostand value if it's our first time being knocked down...
-            SetAutoStand((uid, component), evAttempt.AutoStand);
+            SetAutoStand((entity, component), autoStand);
         }
 
         var knockedEv = new KnockedDownEvent(time);
-        RaiseLocalEvent(uid, ref knockedEv);
-
-        UpdateKnockdownTime((uid, component), knockedEv.Time, refresh);
+        RaiseLocalEvent(entity, ref knockedEv);
 
-        Alerts.ShowAlert(uid, KnockdownAlert, null, (GameTiming.CurTime, component.NextUpdate));
-
-        _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} knocked down for {time.Seconds} seconds");
+        if (time != null)
+        {
+            UpdateKnockdownTime((entity, component), time.Value, refresh);
+            _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(entity):user} knocked down for {time.Value.Seconds} seconds");
+        }
+        else
+            _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(entity):user} knocked down for an indefinite amount of time");
 
-        return true;
+        Alerts.ShowAlert(entity, KnockdownAlert, null, (GameTiming.CurTime, component.NextUpdate));
     }
 
-    /// <summary>
-    ///     Applies knockdown and stun to the entity temporarily.
-    /// </summary>
-    public bool TryParalyze(EntityUid uid, TimeSpan time, bool refresh,
-        StatusEffectsComponent? status = null)
+    public bool TryAddParalyzeDuration(EntityUid uid, TimeSpan duration)
     {
-        if (!Resolve(uid, ref status, false))
-            return false;
+        var knockdown = TryAddKnockdownDuration(uid, duration);
+        var stunned = TryAddStunDuration(uid, duration);
 
-        return TryKnockdown(uid, time, refresh) && TryStun(uid, time, refresh, status);
+        return knockdown || stunned;
     }
 
-    /// <summary>
-    ///     Slows down the mob's walking/running speed temporarily
-    /// </summary>
-    public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh,
-        float walkSpeedMod = 1f, float sprintSpeedMod = 1f,
-        StatusEffectsComponent? status = null)
+    public bool TryUpdateParalyzeDuration(EntityUid uid, TimeSpan? duration)
     {
-        if (!Resolve(uid, ref status, false))
-            return false;
-
-        if (time <= TimeSpan.Zero)
-            return false;
+        var knockdown = TryUpdateKnockdownDuration(uid, duration);
+        var stunned = TryUpdateStunDuration(uid, duration);
 
-        if (_statusEffect.TryAddStatusEffect<SlowedDownComponent>(uid, "SlowedDown", time, refresh, status))
-        {
-            var slowed = Comp<SlowedDownComponent>(uid);
-            // Doesn't make much sense to have the "TrySlowdown" method speed up entities now does it?
-            walkSpeedMod = Math.Clamp(walkSpeedMod, 0f, 1f);
-            sprintSpeedMod = Math.Clamp(sprintSpeedMod, 0f, 1f);
-
-            slowed.WalkSpeedModifier *= walkSpeedMod;
-            slowed.SprintSpeedModifier *= sprintSpeedMod;
+        return knockdown || stunned;
+    }
 
-            _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
+    public bool TryUnstun(Entity<StunnedComponent?> entity)
+    {
+        if (!Resolve(entity, ref entity.Comp, logMissing: false))
             return true;
-        }
 
-        return false;
+        var ev = new StunEndAttemptEvent();
+        RaiseLocalEvent(entity, ref ev);
+
+        return !ev.Cancelled && RemComp<StunnedComponent>(entity);
     }
 
-    /// <summary>
-    /// Updates the movement speed modifiers of an entity by applying or removing the <see cref="SlowedDownComponent"/>.
-    /// If both walk and run modifiers are approximately 1 (i.e. normal speed) and <see cref="StaminaComponent.StaminaDamage"/> is 0,
-    /// or if the both modifiers are 0, the slowdown component is removed to restore normal movement.
-    /// Otherwise, the slowdown component is created or updated with the provided modifiers,
-    /// and the movement speed is refreshed accordingly.
-    /// </summary>
-    /// <param name="ent">Entity whose movement speed should be updated.</param>
-    /// <param name="walkSpeedModifier">New walk speed modifier. Default is 1f (normal speed).</param>
-    /// <param name="runSpeedModifier">New run (sprint) speed modifier. Default is 1f (normal speed).</param>
-    public void UpdateStunModifiers(Entity<StaminaComponent?> ent,
-        float walkSpeedModifier = 1f,
-        float runSpeedModifier = 1f)
+    private void OnStunEffectApplied(Entity<StunnedStatusEffectComponent> entity, ref StatusEffectAppliedEvent args)
     {
-        if (!Resolve(ent, ref ent.Comp))
-            return;
-
-        if (
-            (MathHelper.CloseTo(walkSpeedModifier, 1f) && MathHelper.CloseTo(runSpeedModifier, 1f) && ent.Comp.StaminaDamage == 0f) ||
-            (walkSpeedModifier == 0f && runSpeedModifier == 0f)
-        )
-        {
-            RemComp<SlowedDownComponent>(ent);
+        if (GameTiming.ApplyingState)
             return;
-        }
-
-        EnsureComp<SlowedDownComponent>(ent, out var comp);
-
-        comp.WalkSpeedModifier = walkSpeedModifier;
-
-        comp.SprintSpeedModifier = runSpeedModifier;
-
-        _movementSpeedModifier.RefreshMovementSpeedModifiers(ent);
 
-        Dirty(ent);
+        EnsureComp<StunnedComponent>(args.Target);
     }
 
-    /// <summary>
-    /// A convenience overload of <see cref="UpdateStunModifiers(EntityUid, float, float, StaminaComponent?)"/> that sets both
-    /// walk and run speed modifiers to the same value.
-    /// </summary>
-    /// <param name="ent">Entity whose movement speed should be updated.</param>
-    /// <param name="speedModifier">New walk and run speed modifier. Default is 1f (normal speed).</param>
-    /// <param name="component">
-    /// Optional <see cref="StaminaComponent"/> of the entity.
-    /// </param>
-    public void UpdateStunModifiers(Entity<StaminaComponent?> ent, float speedModifier = 1f)
+    private void OnStunStatusRemoved(Entity<StunnedStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
     {
-        UpdateStunModifiers(ent, speedModifier, speedModifier);
+        TryUnstun(args.Target);
     }
 
-    #region friction and movement listeners
-
-    private void OnRefreshMovespeed(EntityUid ent, SlowedDownComponent comp, RefreshMovementSpeedModifiersEvent args)
+    private void OnStunEndAttempt(Entity<StunnedStatusEffectComponent> entity, ref StatusEffectRelayedEvent<StunEndAttemptEvent> args)
     {
-        args.ModifySpeed(comp.WalkSpeedModifier, comp.SprintSpeedModifier);
+        if (args.Args.Cancelled)
+            return;
+
+        var ev = args.Args;
+        ev.Cancelled = true;
+        args.Args = ev;
     }
 
-    #endregion
+    private void OnStandUpAttempt(Entity<KnockdownStatusEffectComponent> entity, ref StatusEffectRelayedEvent<StandUpAttemptEvent> args)
+    {
+        if (args.Args.Cancelled)
+            return;
+
+        var ev = args.Args;
+        ev.Cancelled = true;
+        args.Args = ev;
+    }
 
     #region Attempt Event Handling
 
diff --git a/Content.Shared/Stunnable/SlowedDownComponent.cs b/Content.Shared/Stunnable/SlowedDownComponent.cs
deleted file mode 100644 (file)
index c4822b1..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Stunnable;
-
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStunSystem))]
-public sealed partial class SlowedDownComponent : Component
-{
-    [ViewVariables, DataField("sprintSpeedModifier"), AutoNetworkedField]
-    public float SprintSpeedModifier = 0.5f;
-
-    [ViewVariables, DataField("walkSpeedModifier"), AutoNetworkedField]
-    public float WalkSpeedModifier = 0.5f;
-}
index 18a757548750054415d1630ff872cee96202a803..f4a0191c92c1c2bbd6da45bd5be2db0d34cdf633 100644 (file)
@@ -15,6 +15,12 @@ namespace Content.Shared.Stunnable;
 [ByRefEvent]
 public record struct StunnedEvent;
 
+/// <summary>
+///     Raised on a stunned entity when something wants to remove the stunned component.
+/// </summary>
+[ByRefEvent]
+public record struct StunEndAttemptEvent(bool Cancelled);
+
 /// <summary>
 ///     Raised directed on an entity before it is knocked down to see if it should be cancelled, and to determine
 ///     knocked down arguments.
@@ -29,7 +35,7 @@ public record struct KnockDownAttemptEvent(bool AutoStand, bool Drop)
 ///     Raised directed on an entity when it is knocked down.
 /// </summary>
 [ByRefEvent]
-public record struct KnockedDownEvent(TimeSpan Time);
+public record struct KnockedDownEvent(TimeSpan? Time);
 
 /// <summary>
 ///     Raised on an entity that needs to refresh its knockdown modifiers
index 037a3cd3ac07776b3c70fb3e9c31598407748bfa..8fb53e1a6842c93f15a32ff2c28d626c263da28c 100644 (file)
@@ -2,5 +2,8 @@ using Robust.Shared.GameStates;
 
 namespace Content.Shared.Stunnable;
 
+/// <summary>
+/// This is used to temporarily prevent an entity from moving or acting.
+/// </summary>
 [RegisterComponent, NetworkedComponent, Access(typeof(SharedStunSystem))]
 public sealed partial class StunnedComponent : Component;
diff --git a/Content.Shared/Stunnable/StunnedStatusEffectComponent.cs b/Content.Shared/Stunnable/StunnedStatusEffectComponent.cs
new file mode 100644 (file)
index 0000000..62b3496
--- /dev/null
@@ -0,0 +1,9 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Stunnable;
+
+/// <summary>
+/// Stun as a status effect.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedStunSystem))]
+public sealed partial class StunnedStatusEffectComponent : Component;
index eaaf336e96bf3bfef4edc70ba162aa463073c590..f1f9a19a75e8eb9636215161367a9af362dbf019 100644 (file)
@@ -97,7 +97,7 @@ public sealed class MeleeThrowOnHitSystem : EntitySystem
         RaiseLocalEvent(target, ref startEvent);
 
         if (ent.Comp.StunTime != null)
-            _stun.TryParalyze(target, ent.Comp.StunTime.Value, false);
+            _stun.TryAddParalyzeDuration(target, ent.Comp.StunTime.Value);
 
         if (direction == Vector2.Zero)
             return;
index cb3e5be478a243593247f894d49cc7dbd0e23adb..714c740cf8c2224266b7e87dbb76e076b2d04dfc 100644 (file)
   - type: Body
   - type: StatusEffects
     allowed:
-    - Stun
-    - SlowedDown
     - Flashed
   - type: TypingIndicator
     proto: robot
index 941091335a80a9a5e0e803326f575f45190a8365..d2dd76146871dffe4cc59c4b9eee3d68a5b6a6ff 100644 (file)
@@ -19,9 +19,7 @@
     group: GenericNumber
   - type: StatusEffects
     allowed:
-    - SlowedDown
     - Stutter
-    - Stun
     - Electrocution
     - TemporaryBlindness
     - RadiationProtection
index 95dcd84ae65a2b3b4766e216f20abbdfa6ce393e..77bb8e1a7fbd00a6b94b18b6d3c278861084c99b 100644 (file)
@@ -35,8 +35,6 @@
     bodyType: KinematicController # Same for all inheritors
   - type: StatusEffects
     allowed:
-    - Stun
-    - SlowedDown
     - Stutter
     - Electrocution
   - type: Pullable
     chemicalMaxVolume: 100
   - type: StatusEffects
     allowed:
-    - SlowedDown
     - Electrocution
   - type: MeleeWeapon
     soundHit:
       - FootstepSound
       - CannotSuicide
       - DoorBumpOpener
+      - StunImmune
   - type: NoSlip
   - type: ZombieImmune
   - type: ExaminableSolution
index 9500345e4fc70839ef44eb30350d7563180bc564..4717e200ddb8d62fde86426d1d47234fc7055fa1 100644 (file)
@@ -48,7 +48,6 @@
         BaseUnshaded: dead_glow
   - type: StatusEffects
     allowed:
-    - Stun
     - Corporeal
     - Electrocution
     - StaminaModifier
@@ -91,6 +90,7 @@
   - type: Tag
     tags:
       - FootstepSound
+      - SlowImmune
   - type: Destructible
     thresholds:
       - trigger:
index 13bce86b06ab5bcf46a5f235d9a09b8c76e87aaf..9b0bfb05ca9ca4efe6765abc6234bca37854ce93 100644 (file)
@@ -20,7 +20,6 @@
     - state: active
   - type: StatusEffects
     allowed:
-    - Stun
     - Corporeal
   - type: Damageable
     damageContainer: ManifestedSpirit
@@ -83,3 +82,7 @@
   - type: Reactive
     groups:
       Acidic: [Touch]
+  - type: Tag
+    tags:
+    - SlowImmune
+    - KnockdownImmune
index 225174243605d5e83b57f6ef3e9cd34c9349ae3e..279dbe9f5430498b3fe672fd2cf7c26a1d447917 100644 (file)
@@ -15,8 +15,6 @@
     bodyType: KinematicController # Same for all inheritors
   - type: StatusEffects
     allowed:
-    - Stun
-    - SlowedDown
     - Stutter
     - Electrocution
   - type: Repairable
index eb0ea8161758901a595155f14ad4291b3105e4f7..11c0f0d21d5d0a24ecd11ae0a72e7e9fe608f108 100644 (file)
@@ -19,7 +19,6 @@
     baseSprintSpeed : 4
   - type: StatusEffects
     allowed:
-    - SlowedDown
     - Stutter
     - Electrocution
     - TemporaryBlindness
@@ -32,6 +31,7 @@
   - type: Tag
     tags:
     - DoorBumpOpener
+    - StunImmune
 
 - type: entity
   abstract: true
@@ -93,8 +93,6 @@
     baseDecayRate: 0.04
   - type: StatusEffects
     allowed:
-    - Stun
-    - SlowedDown
     - Stutter
     - Electrocution
     - TemporaryBlindness
index 0d6489744fa7310cdcd9f3127573defe5458e4be..b6b344d6b5508ace6ed29c62f685aeca5f32f188 100644 (file)
       types: {}
   - type: StatusEffects # Overwriting basesimplemob to remove flash, getting flashed as dragon just feelsbad
     allowed:
-    - SlowedDown
     - Stutter
     - Electrocution
     - TemporaryBlindness
     tags:
     - CannotSuicide
     - DoorBumpOpener
+    - StunImmune
   - type: Puller
     needsHands: false
   - type: RandomMetadata
index 9ea12f2ae39a61cac52e30577c4b781e1cd90409..849e65f3b22cd5e8791b40d1bf09ea6c961c4e6c 100644 (file)
       - !type:WashCreamPieReaction
   - type: StatusEffects
     allowed:
-    - Stun
-    - Friction
-    - SlowedDown
     - Stutter
     - Electrocution
     - Drunk
index 444600023a865396487a6f170242c718754bbfd1..273a52c0a8ece9f72675cfd527e47625af1030f3 100644 (file)
       components:
       - MobState
 
+- type: entity
+  parent: StatusEffectBase
+  id: MobStandStatusEffectBase
+  abstract: true
+  components:
+  - type: StatusEffect
+    whitelist:
+      components:
+      - MobState
+      - StandingState
+      requireAll: true
+    blacklist: # This blacklist exists because mob prototypes are smelly and everything needs a standing state component.
+      tags:
+      - KnockdownImmune
+
 # The creature sleeps so heavily that nothing can wake him up. Not even its own death.
 - type: entity
   parent: MobStatusEffectBase
@@ -26,6 +41,8 @@
   name: forced sleep
   components:
   - type: ForcedSleepingStatusEffect
+  - type: StunnedStatusEffect
+  - type: KnockdownStatusEffect
 
 # This creature is asleep because it's disconnected from the game.
 - type: entity
@@ -34,6 +51,8 @@
   name: forced sleep
   components:
   - type: ForcedSleepingStatusEffect
+  - type: StunnedStatusEffect
+  - type: KnockdownStatusEffect
 
 # Blurs your vision and makes you randomly fall asleep
 - type: entity
   components:
   - type: DrowsinessStatusEffect
 
-# Makes you more slippery, or perhaps less slippery.
-- type: entity
-  parent: MobStatusEffectBase
-  id: StatusEffectFriction
-  name: friction
-  components:
-  - type: FrictionStatusEffect
-
 # Adds drugs overlay
 - type: entity
   parent: MobStatusEffectBase
diff --git a/Resources/Prototypes/Entities/StatusEffects/movement.yml b/Resources/Prototypes/Entities/StatusEffects/movement.yml
new file mode 100644 (file)
index 0000000..b6fa426
--- /dev/null
@@ -0,0 +1,67 @@
+- type: entity
+  parent: MobStatusEffectBase
+  id: StatusEffectSlowdown
+  abstract: true
+  name: slowdown
+  components:
+  - type: StatusEffect
+    whitelist:
+      components:
+      - MobState
+    blacklist:
+      tags:
+      - SlowImmune
+  - type: MovementModStatusEffect
+
+- type: entity
+  parent: StatusEffectSlowdown
+  id: VomitingSlowdownStatusEffect
+  name: vomiting slowdown
+
+- type: entity
+  parent: StatusEffectSlowdown
+  id: TaserSlowdownStatusEffect
+  name: shot by taser slowdown
+
+- type: entity
+  parent: StatusEffectSlowdown
+  id: FlashSlowdownStatusEffect
+  name: affected by flash slowdown
+
+- type: entity
+  parent: StatusEffectSlowdown
+  id: StatusEffectStaminaLow
+  name: stamina low
+
+# Makes you more slippery, or perhaps less slippery.
+- type: entity
+  parent: MobStatusEffectBase
+  id: StatusEffectFriction
+  name: friction
+  components:
+  - type: FrictionStatusEffect
+
+# Stunnable Status Effect
+
+- type: entity
+  parent: MobStatusEffectBase
+  id: StatusEffectStunned
+  name: stunned
+  components:
+  - type: StatusEffect
+    whitelist:
+      components:
+      - MobState
+    blacklist:
+      tags:
+      - StunImmune
+  - type: StatusEffectAlert
+    alert: Stun
+  - type: StunnedStatusEffect
+
+- type: entity
+  parent: MobStandStatusEffectBase
+  id: StatusEffectKnockdown
+  name: knocked down
+  components:
+  - type: KnockdownStatusEffect
index aa3be06694392f7082418a1ebfdb00c3e75348fb..24e21742c302dce9cf6977a9102f909cef94a275 100644 (file)
 - type: Tag
   id: Knife
 
+- type: Tag
+  id: KnockdownImmune
+
 - type: Tag
   id: LavaBrig
 
 - type: Tag
   id: Slice # sliced fruit, vegetables, pizza etc.
 
+- type: Tag
+  id: SlowImmune
+
 - type: Tag
   id: SmallAIChip
 
 - type: Tag
   id: StringInstrument
 
+- type: Tag
+  id: StunImmune
+
 - type: Tag
   id: SubdermalImplant