diff --git a/Content.IntegrationTests/Tests/InventoryHelpersTest.cs b/Content.IntegrationTests/Tests/InventoryHelpersTest.cs index 4a07177f77..39761ad089 100644 --- a/Content.IntegrationTests/Tests/InventoryHelpersTest.cs +++ b/Content.IntegrationTests/Tests/InventoryHelpersTest.cs @@ -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().TryStun(human, TimeSpan.FromSeconds(1f), true); + systemMan.GetEntitySystem().TryUpdateStunDuration(human, TimeSpan.FromSeconds(1f)); #pragma warning disable NUnit2045 // Since the mob is stunned, they can't equip this. diff --git a/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs b/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs index ff644ad3e0..bed2499298 100644 --- a/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs @@ -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 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) && diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index 13b3e480c9..424f52ed58 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -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, () => diff --git a/Content.Server/Cluwne/CluwneSystem.cs b/Content.Server/Cluwne/CluwneSystem.cs index 2bad4e0b96..21a15d812f 100644 --- a/Content.Server/Cluwne/CluwneSystem.cs +++ b/Content.Server/Cluwne/CluwneSystem.cs @@ -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); } } diff --git a/Content.Server/Drowsiness/DrowsinessSystem.cs b/Content.Server/Drowsiness/DrowsinessSystem.cs index 13fdc42e10..2ea0482590 100644 --- a/Content.Server/Drowsiness/DrowsinessSystem.cs +++ b/Content.Server/Drowsiness/DrowsinessSystem.cs @@ -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; diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 18a4d52ac2..bb9e71cfc3 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -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. diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index d7a548bf0f..609039ae4c 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -228,7 +228,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem(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."); diff --git a/Content.Server/Instruments/InstrumentSystem.cs b/Content.Server/Instruments/InstrumentSystem.cs index f6a2162271..56b64576ca 100644 --- a/Content.Server/Instruments/InstrumentSystem.cs +++ b/Content.Server/Instruments/InstrumentSystem.cs @@ -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); diff --git a/Content.Server/Medical/VomitSystem.cs b/Content.Server/Medical/VomitSystem.cs index 7b8be07a15..9fee1dfc85 100644 --- a/Content.Server/Medical/VomitSystem.cs +++ b/Content.Server/Medical/VomitSystem.cs @@ -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(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(); diff --git a/Content.Server/Ninja/Systems/StunProviderSystem.cs b/Content.Server/Ninja/Systems/StunProviderSystem.cs index 822486cff5..8697692e5e 100644 --- a/Content.Server/Ninja/Systems/StunProviderSystem.cs +++ b/Content.Server/Ninja/Systems/StunProviderSystem.cs @@ -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); diff --git a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs index 3cf25472f0..09f61632b6 100644 --- a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs +++ b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs @@ -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(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); } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index 2d38aee6ea..b89f10934d 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs @@ -177,7 +177,7 @@ public sealed partial class RevenantSystem : EntitySystem ChangeEssenceAmount(uid, -abilityCost, component, false); _statusEffects.TryAddStatusEffect(uid, "Corporeal", TimeSpan.FromSeconds(debuffs.Y), false); - _stun.TryStun(uid, TimeSpan.FromSeconds(debuffs.X), false); + _stun.TryAddStunDuration(uid, TimeSpan.FromSeconds(debuffs.X)); return true; } diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index 13e13bf8f3..a63359077f 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -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) diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs index 29657af295..e78a17e180 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs @@ -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 diff --git a/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs b/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs index b998270829..18c386d4ac 100644 --- a/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs +++ b/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs @@ -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(target, out var status)) - return; + _stunSystem.TryUpdateStunDuration(target, component.StunAmount); - _stunSystem.TryStun(target, component.StunAmount, component.Refresh, status); + _stunSystem.TryKnockdown(target, component.KnockdownAmount, component.Refresh, component.AutoStand, force: true); - _stunSystem.TryKnockdown(target, component.KnockdownAmount, component.Refresh, component.AutoStand); - - _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) diff --git a/Content.Shared/Abilities/Goliath/GoliathTentacleSystem.cs b/Content.Shared/Abilities/Goliath/GoliathTentacleSystem.cs index a220781420..039891a3ba 100644 --- a/Content.Shared/Abilities/Goliath/GoliathTentacleSystem.cs +++ b/Content.Shared/Abilities/Goliath/GoliathTentacleSystem.cs @@ -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 spawnPos = new(); diff --git a/Content.Shared/Bed/Sleep/SleepingSystem.cs b/Content.Shared/Bed/Sleep/SleepingSystem.cs index 3534fb88d5..61803d7c7d 100644 --- a/Content.Shared/Bed/Sleep/SleepingSystem.cs +++ b/Content.Shared/Bed/Sleep/SleepingSystem.cs @@ -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(OnExamined); SubscribeLocalEvent>(AddWakeVerb); SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnStunEndAttempt); + SubscribeLocalEvent(OnStandUpAttempt); SubscribeLocalEvent(OnStatusEffectApplied); SubscribeLocalEvent(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(ent); EnsureComp(ent); @@ -128,8 +127,9 @@ public sealed partial class SleepingSystem : EntitySystem return; } - RemComp(ent); - RemComp(ent); + _stun.TryUnstun(ent.Owner); + _stun.TryStanding(ent.Owner, out _); + RemComp(ent); } @@ -174,6 +174,17 @@ public sealed partial class SleepingSystem : EntitySystem args.Cancelled = true; } + private void OnStunEndAttempt(Entity ent, ref StunEndAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnStandUpAttempt(Entity ent, ref StandUpAttemptEvent args) + { + // Shh the Urist McHands is sleeping... + args.Cancelled = true; + } + private void OnExamined(Entity 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(ent)) + if (!force && _statusEffect.HasEffectComp(ent)) { if (user != null) { diff --git a/Content.Shared/Climbing/Systems/ClimbSystem.cs b/Content.Shared/Climbing/Systems/ClimbSystem.cs index 0067eb87b2..45055ebbcc 100644 --- a/Content.Shared/Climbing/Systems/ClimbSystem.cs +++ b/Content.Shared/Climbing/Systems/ClimbSystem.cs @@ -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( diff --git a/Content.Shared/Clumsy/ClumsySystem.cs b/Content.Shared/Clumsy/ClumsySystem.cs index 9e0e82364f..2506359c25 100644 --- a/Content.Shared/Clumsy/ClumsySystem.cs +++ b/Content.Shared/Clumsy/ClumsySystem.cs @@ -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 } diff --git a/Content.Shared/Damage/Systems/DamageOnHighSpeedImpactSystem.cs b/Content.Shared/Damage/Systems/DamageOnHighSpeedImpactSystem.cs index 6ef41e8721..33d7c65a45 100644 --- a/Content.Shared/Damage/Systems/DamageOnHighSpeedImpactSystem.cs +++ b/Content.Shared/Damage/Systems/DamageOnHighSpeedImpactSystem.cs @@ -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; diff --git a/Content.Shared/Damage/Systems/DamageOnInteractSystem.cs b/Content.Shared/Damage/Systems/DamageOnInteractSystem.cs index 0f43e93abf..bd3b6979f7 100644 --- a/Content.Shared/Damage/Systems/DamageOnInteractSystem.cs +++ b/Content.Shared/Damage/Systems/DamageOnInteractSystem.cs @@ -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. diff --git a/Content.Shared/Damage/Systems/SharedStaminaSystem.cs b/Content.Shared/Damage/Systems/SharedStaminaSystem.cs index 6687ecd7df..b2d22391eb 100644 --- a/Content.Shared/Damage/Systems/SharedStaminaSystem.cs +++ b/Content.Shared/Damage/Systems/SharedStaminaSystem.cs @@ -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 SharedColorFlashEffectSystem _color = default!; - [Dependency] protected readonly SharedStunSystem StunSystem = default!; + [Dependency] private readonly MovementModStatusSystem _movementMod = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly SharedColorFlashEffectSystem _color = default!; + [Dependency] private readonly StatusEffectsSystem _status = default!; + [Dependency] protected readonly SharedStunSystem StunSystem = default!; /// /// 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(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 } /// - /// Adjusts the movement speed of an entity based on its current value. - /// If the entity has a , 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 status effect entity and applies relevant statuses. + /// System iterates through the to find correct movement modifer. + /// This modifier is saved to the Stamina Low Status Effect entity's . /// /// Entity to update - private void AdjustSlowdown(Entity ent) + private void AdjustStatus(Entity 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] diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index 7914a38334..50132e42dd 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -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) diff --git a/Content.Shared/EntityEffects/Effects/Paralyze.cs b/Content.Shared/EntityEffects/Effects/Paralyze.cs index 2da6a6a079..2a2270016b 100644 --- a/Content.Shared/EntityEffects/Effects/Paralyze.cs +++ b/Content.Shared/EntityEffects/Effects/Paralyze.cs @@ -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().TryParalyze(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime), Refresh); + var stunSystem = args.EntityManager.System(); + _ = Refresh + ? stunSystem.TryUpdateParalyzeDuration(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime)) + : stunSystem.TryAddParalyzeDuration(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime)); } } diff --git a/Content.Shared/Flash/SharedFlashSystem.cs b/Content.Shared/Flash/SharedFlashSystem.cs index 50652f9408..dd6c9c91c1 100644 --- a/Content.Shared/Flash/SharedFlashSystem.cs +++ b/Content.Shared/Flash/SharedFlashSystem.cs @@ -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)) { diff --git a/Content.Shared/Magic/SharedMagicSystem.cs b/Content.Shared/Magic/SharedMagicSystem.cs index e5258f2518..19d649d1f3 100644 --- a/Content.Shared/Magic/SharedMagicSystem.cs +++ b/Content.Shared/Magic/SharedMagicSystem.cs @@ -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 index 0000000000..22b196d127 --- /dev/null +++ b/Content.Shared/Movement/Components/MovementModStatusEffectComponent.cs @@ -0,0 +1,25 @@ +using Content.Shared.Movement.Systems; +using Robust.Shared.GameStates; + +namespace Content.Shared.Movement.Components; + +/// +/// 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 for the component applied to the entity. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(MovementModStatusSystem))] +public sealed partial class MovementModStatusEffectComponent : Component +{ + /// + /// Multiplicative sprint modifier, with bounds of [0, 1) + /// + [DataField, AutoNetworkedField] + public float SprintSpeedModifier = 0.5f; + + /// + /// Multiplicative walk modifier, with bounds of [0, 1) + /// + [DataField, AutoNetworkedField] + public float WalkSpeedModifier = 0.5f; +} diff --git a/Content.Shared/Movement/Systems/MovementModStatusSystem.cs b/Content.Shared/Movement/Systems/MovementModStatusSystem.cs index 4fd6467947..37039fa6d4 100644 --- a/Content.Shared/Movement/Systems/MovementModStatusSystem.cs +++ b/Content.Shared/Movement/Systems/MovementModStatusSystem.cs @@ -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; /// -/// 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. +/// 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. /// +/// +/// 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. +/// 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!; - /// public override void Initialize() { + SubscribeLocalEvent(OnMovementModRemoved); + SubscribeLocalEvent>(OnRefreshRelay); SubscribeLocalEvent(OnFrictionStatusEffectRemoved); SubscribeLocalEvent>(OnRefreshFrictionStatus); SubscribeLocalEvent>(OnRefreshTileFrictionStatus); } + private void OnMovementModRemoved(Entity ent, ref StatusEffectRemovedEvent args) + { + TryUpdateMovementStatus(args.Target, (ent, ent), 1f); + } + + private void OnFrictionStatusEffectRemoved(Entity entity, ref StatusEffectRemovedEvent args) + { + TrySetFrictionStatus(entity!, 1f, args.Target); + } + + private void OnRefreshRelay( + Entity entity, + ref StatusEffectRelayedEvent args + ) + { + args.Args.ModifySpeed(entity.Comp.WalkSpeedModifier, entity.Comp.WalkSpeedModifier); + } + private void OnRefreshFrictionStatus(Entity ent, ref StatusEffectRelayedEvent args) { var ev = args.Args; @@ -39,27 +70,168 @@ public sealed class MovementModStatusSystem : EntitySystem } /// - /// Applies a friction de-buff to the player. + /// Apply mob's walking/running speed modifier with provided duration, or increment duration of existing. /// - public bool TryFriction(EntityUid uid, - TimeSpan time, - bool refresh, - float friction, - float acceleration) + /// Target entity, for which speed should be modified. + /// Slowdown effect to be used. + /// Duration of speed modifying effect. + /// Multiplier by which walking/sprinting speed should be modified. + /// True if entity have slowdown effect applied now or previously and duration was modified. + public bool TryAddMovementSpeedModDuration( + EntityUid uid, + EntProtoId effectProtoId, + TimeSpan duration, + float speedModifier + ) { - if (time <= TimeSpan.Zero) + return TryAddMovementSpeedModDuration(uid, effectProtoId, duration, speedModifier, speedModifier); + } + + /// + /// Apply mob's walking/running speed modifier with provided duration, or increment duration of existing. + /// + /// Target entity, for which speed should be modified. + /// Slowdown effect to be used. + /// Duration of speed modifying effect. + /// Multiplier by which walking speed should be modified. + /// Multiplier by which sprinting speed should be modified. + /// True if entity have slowdown effect applied now or previously and duration was modified. + 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); + } + + /// + /// Apply mob's walking/running speed modifier with provided duration, + /// or update duration of existing if it is lesser than provided duration. + /// + /// Target entity, for which speed should be modified. + /// Slowdown effect to be used. + /// Duration of speed modifying effect. + /// Multiplier by which walking/sprinting speed should be modified. + /// True if entity have slowdown effect applied now or previously and duration was modified. + public bool TryUpdateMovementSpeedModDuration( + EntityUid uid, + EntProtoId effectProtoId, + TimeSpan duration, + float speedModifier + ) + { + return TryUpdateMovementSpeedModDuration(uid, effectProtoId, duration, speedModifier, speedModifier); + } + + /// + /// Apply mob's walking/running speed modifier with provided duration, + /// or update duration of existing if it is lesser than provided duration. + /// + /// Target entity, for which speed should be modified. + /// Slowdown effect to be used. + /// Duration of speed modifying effect. + /// Multiplier by which walking speed should be modified. + /// Multiplier by which sprinting speed should be modified. + /// True if entity have slowdown effect applied now or previously and duration was modified. + 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); + } + + /// + /// Updates entity's movement speed using to provided values. + /// Then refreshes the movement speed of the entity. + /// + /// Entity whose component we're updating + /// Status effect entity whose modifiers we are updating + /// New walkSpeedModifer we're applying + /// New sprintSpeedModifier we're applying + public bool TryUpdateMovementStatus( + EntityUid uid, + Entity status, + float walkSpeedModifier, + float sprintSpeedModifier + ) + { + if (!Resolve(status, ref status.Comp)) return false; - if (refresh) - { - return _status.TryUpdateStatusEffectDuration(uid, StatusEffectFriction, out var status, time) + status.Comp.SprintSpeedModifier = sprintSpeedModifier; + status.Comp.WalkSpeedModifier = walkSpeedModifier; + + _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + + return true; + } + + /// + /// Updates entity's movement speed using to provided value. + /// Then refreshes the movement speed of the entity. + /// + /// Entity whose component we're updating + /// Status effect entity whose modifiers we are updating + /// + /// Multiplier by which speed should be modified. + /// Will be applied to both walking and running speed. + /// + public bool TryUpdateMovementStatus( + EntityUid uid, + Entity status, + float speedModifier + ) + { + return TryUpdateMovementStatus(uid, status, speedModifier, speedModifier); + } + + /// + /// Apply friction modifier with provided duration, + /// or incrementing duration of existing. + /// + /// Target entity, for which friction modifier should be applied. + /// Duration of speed modifying effect. + /// Multiplier by which walking speed should be modified. + /// Multiplier by which sprinting speed should be modified. + /// True if entity have slowdown effect applied now or previously and duration was modified. + 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); - } - else - { - return _status.TryAddStatusEffectDuration(uid, StatusEffectFriction, out var status, time) - && TrySetFrictionStatus(status.Value, friction, acceleration, uid); - } + } + + /// + /// Apply friction modifier with provided duration, + /// or update duration of existing if it is lesser than provided duration. + /// + /// Target entity, for which friction modifier should be applied. + /// Duration of speed modifying effect. + /// Multiplier by which walking speed should be modified. + /// Multiplier by which sprinting speed should be modified. + /// True if entity have slowdown effect applied now or previously and duration was modified. + 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); } /// @@ -92,9 +264,4 @@ public sealed class MovementModStatusSystem : EntitySystem _movementSpeedModifier.RefreshFrictionModifiers(entity); return true; } - - private void OnFrictionStatusEffectRemoved(Entity entity, ref StatusEffectRemovedEvent args) - { - TrySetFrictionStatus(entity!, 1f, args.Target); - } } diff --git a/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs b/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs index fa07aa20c5..86eec26bfd 100644 --- a/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs @@ -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) {} diff --git a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs index bbf91193cc..36a31d88a4 100644 --- a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs +++ b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs @@ -42,7 +42,7 @@ public abstract class SharedRevolutionarySystem : EntitySystem var stunTime = TimeSpan.FromSeconds(4); var name = Identity.Entity(uid, EntityManager); RemComp(uid); - _sharedStun.TryParalyze(uid, stunTime, true); + _sharedStun.TryUpdateParalyzeDuration(uid, stunTime); _popupSystem.PopupEntity(Loc.GetString("rev-break-control", ("name", name)), uid); } } diff --git a/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs index e7433de604..056431c133 100644 --- a/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs +++ b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs @@ -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; } diff --git a/Content.Shared/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index f7e3da8edc..58a0ec06d5 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -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(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}"); } diff --git a/Content.Shared/Species/Systems/ReformSystem.cs b/Content.Shared/Species/Systems/ReformSystem.cs index d2ceecf28e..943432522d 100644 --- a/Content.Shared/Species/Systems/ReformSystem.cs +++ b/Content.Shared/Species/Systems/ReformSystem.cs @@ -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 diff --git a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs index d508ea8b73..d30ab79c2b 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs @@ -31,7 +31,7 @@ public sealed partial class StatusEffectsSystem } - /// + /// 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; } - /// + /// 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; } - /// + /// public bool TryUpdateStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null) { return TryUpdateStatusEffectDuration(target, effectProto, out _, duration); diff --git a/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs b/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs index dddcbc660c..96ef012168 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs @@ -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(RelayStatusEffectEvent); SubscribeLocalEvent(RelayStatusEffectEvent); + SubscribeLocalEvent(RelayStatusEffectEvent); + SubscribeLocalEvent(RelayStatusEffectEvent); + SubscribeLocalEvent(RefRelayStatusEffectEvent); SubscribeLocalEvent(RefRelayStatusEffectEvent); + + SubscribeLocalEvent(RefRelayStatusEffectEvent); + SubscribeLocalEvent(RefRelayStatusEffectEvent); } private void RefRelayStatusEffectEvent(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct diff --git a/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs b/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs index 7f39a1f7c5..ce595109b3 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs @@ -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 index 0000000000..79b2fb695b --- /dev/null +++ b/Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Stunnable; + +/// +/// Knockdown as a status effect. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedStunSystem))] +public sealed partial class KnockdownStatusEffectComponent : Component; diff --git a/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs b/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs index 2146c27a21..26e8e47f03 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs @@ -51,7 +51,7 @@ public abstract partial class SharedStunSystem // Action blockers SubscribeLocalEvent(OnBuckleAttempt); - SubscribeLocalEvent(OnStandUpAttempt); + SubscribeLocalEvent(OnStandAttempt); // Updating movement a friction SubscribeLocalEvent(OnRefreshKnockedSpeed); @@ -452,7 +452,7 @@ public abstract partial class SharedStunSystem #region Action Blockers - private void OnStandUpAttempt(Entity entity, ref StandAttemptEvent args) + private void OnStandAttempt(Entity entity, ref StandAttemptEvent args) { if (entity.Comp.LifeStage <= ComponentLifeStage.Running) args.Cancel(); diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index 2f75b71f56..4c4523849b 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -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(OnSlowInit); - SubscribeLocalEvent(OnSlowRemove); - SubscribeLocalEvent(OnRefreshMovespeed); - SubscribeLocalEvent(UpdateCanMove); SubscribeLocalEvent(OnStunShutdown); @@ -61,6 +60,14 @@ public abstract partial class SharedStunSystem : EntitySystem SubscribeLocalEvent(OnUnequipAttempt); SubscribeLocalEvent(OnMobStateChanged); + // New Status Effect subscriptions + SubscribeLocalEvent(OnStunEffectApplied); + SubscribeLocalEvent(OnStunStatusRemoved); + SubscribeLocalEvent>(OnStunEndAttempt); + + SubscribeLocalEvent>(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(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(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); - } - - private void OnSlowInit(EntityUid uid, SlowedDownComponent component, ComponentInit args) - { - _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); - } - - private void OnSlowRemove(EntityUid uid, SlowedDownComponent component, ComponentShutdown args) - { - component.SprintSpeedModifier = 1f; - component.WalkSpeedModifier = 1f; - _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + TryUpdateStunDuration(args.OtherEntity, ent.Comp.Duration); + TryKnockdown(args.OtherEntity, ent.Comp.Duration, true, force: true); } // TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...) - - /// - /// Stuns the entity, disallowing it from doing many interactions temporarily. - /// - public bool TryStun(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null) + public bool TryAddStunDuration(EntityUid uid, TimeSpan duration) { - if (time <= TimeSpan.Zero) + if (!_status.TryAddStatusEffectDuration(uid, StunId, duration)) return false; - if (!Resolve(uid, ref status, false)) + OnStunnedSuccessfully(uid, duration); + return true; + } + + public bool TryUpdateStunDuration(EntityUid uid, TimeSpan? duration) + { + if (!_status.TryUpdateStatusEffectDuration(uid, StunId, duration)) return false; - if (!_statusEffect.TryAddStatusEffect(uid, "Stun", time, refresh)) - return false; + OnStunnedSuccessfully(uid, duration); + return true; + } - var ev = new StunnedEvent(); + private void OnStunnedSuccessfully(EntityUid uid, TimeSpan? duration) + { + 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); - _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} stunned for {time.Seconds} seconds"); + var timeForLogs = duration.HasValue + ? duration.Value.Seconds.ToString() + : "Infinite"; + _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} stunned for {timeForLogs} seconds"); + } + + public bool TryAddKnockdownDuration(EntityUid uid, TimeSpan duration) + { + if (!_status.TryAddStatusEffectDuration(uid, KnockdownId, duration)) + return false; + + TryKnockdown(uid, duration, true, force: true); + return true; + + } + + public bool TryUpdateKnockdownDuration(EntityUid uid, TimeSpan? duration) + { + if (!_status.TryUpdateStatusEffectDuration(uid, KnockdownId, duration)) + return false; + + return TryKnockdown(uid, duration, true, force: true); } /// /// Knocks down the entity, making it fall to the ground. /// - public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh, bool autoStand = true, bool drop = true) + public bool TryKnockdown(Entity 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(uid)) + if (!Resolve(entity, ref entity.Comp, false)) return false; - var evAttempt = new KnockDownAttemptEvent(autoStand, drop); - RaiseLocalEvent(uid, ref evAttempt); - - if (evAttempt.Cancelled) - return false; - - // Initialize our component with the relevant data we need if we don't have it - if (EnsureComp(uid, out var component)) + if (!force) { - RefreshKnockedMovement((uid, component)); - CancelKnockdownDoAfter((uid, component)); + var evAttempt = new KnockDownAttemptEvent(autoStand, drop); + RaiseLocalEvent(entity, ref evAttempt); + + if (evAttempt.Cancelled) + return false; + + autoStand = evAttempt.AutoStand; + drop = evAttempt.Drop; + } + + Knockdown(entity!, time, autoStand, drop); + + return true; + } + + private void Knockdown(Entity 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(entity, out var component)) + { + RefreshKnockedMovement((entity, component)); + CancelKnockdownDoAfter((entity, component)); } else { @@ -191,127 +218,87 @@ 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); + RaiseLocalEvent(entity, ref knockedEv); - UpdateKnockdownTime((uid, component), knockedEv.Time, refresh); - - Alerts.ShowAlert(uid, KnockdownAlert, null, (GameTiming.CurTime, component.NextUpdate)); - - _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} knocked down for {time.Seconds} seconds"); - - return true; - } - - /// - /// Applies knockdown and stun to the entity temporarily. - /// - public bool TryParalyze(EntityUid uid, TimeSpan time, bool refresh, - StatusEffectsComponent? status = null) - { - if (!Resolve(uid, ref status, false)) - return false; - - return TryKnockdown(uid, time, refresh) && TryStun(uid, time, refresh, status); - } - - /// - /// Slows down the mob's walking/running speed temporarily - /// - public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh, - float walkSpeedMod = 1f, float sprintSpeedMod = 1f, - StatusEffectsComponent? status = null) - { - if (!Resolve(uid, ref status, false)) - return false; - - if (time <= TimeSpan.Zero) - return false; - - if (_statusEffect.TryAddStatusEffect(uid, "SlowedDown", time, refresh, status)) + if (time != null) { - var slowed = Comp(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); + 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"); - slowed.WalkSpeedModifier *= walkSpeedMod; - slowed.SprintSpeedModifier *= sprintSpeedMod; + Alerts.ShowAlert(entity, KnockdownAlert, null, (GameTiming.CurTime, component.NextUpdate)); + } - _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + public bool TryAddParalyzeDuration(EntityUid uid, TimeSpan duration) + { + var knockdown = TryAddKnockdownDuration(uid, duration); + var stunned = TryAddStunDuration(uid, duration); + + return knockdown || stunned; + } + + public bool TryUpdateParalyzeDuration(EntityUid uid, TimeSpan? duration) + { + var knockdown = TryUpdateKnockdownDuration(uid, duration); + var stunned = TryUpdateStunDuration(uid, duration); + + return knockdown || stunned; + } + + public bool TryUnstun(Entity 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(entity); } - /// - /// Updates the movement speed modifiers of an entity by applying or removing the . - /// If both walk and run modifiers are approximately 1 (i.e. normal speed) and 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. - /// - /// Entity whose movement speed should be updated. - /// New walk speed modifier. Default is 1f (normal speed). - /// New run (sprint) speed modifier. Default is 1f (normal speed). - public void UpdateStunModifiers(Entity ent, - float walkSpeedModifier = 1f, - float runSpeedModifier = 1f) + private void OnStunEffectApplied(Entity entity, ref StatusEffectAppliedEvent args) { - if (!Resolve(ent, ref ent.Comp)) + if (GameTiming.ApplyingState) return; - if ( - (MathHelper.CloseTo(walkSpeedModifier, 1f) && MathHelper.CloseTo(runSpeedModifier, 1f) && ent.Comp.StaminaDamage == 0f) || - (walkSpeedModifier == 0f && runSpeedModifier == 0f) - ) - { - RemComp(ent); + EnsureComp(args.Target); + } + + private void OnStunStatusRemoved(Entity entity, ref StatusEffectRemovedEvent args) + { + TryUnstun(args.Target); + } + + private void OnStunEndAttempt(Entity entity, ref StatusEffectRelayedEvent args) + { + if (args.Args.Cancelled) return; - } - EnsureComp(ent, out var comp); - - comp.WalkSpeedModifier = walkSpeedModifier; - - comp.SprintSpeedModifier = runSpeedModifier; - - _movementSpeedModifier.RefreshMovementSpeedModifiers(ent); - - Dirty(ent); + var ev = args.Args; + ev.Cancelled = true; + args.Args = ev; } - /// - /// A convenience overload of that sets both - /// walk and run speed modifiers to the same value. - /// - /// Entity whose movement speed should be updated. - /// New walk and run speed modifier. Default is 1f (normal speed). - /// - /// Optional of the entity. - /// - public void UpdateStunModifiers(Entity ent, float speedModifier = 1f) + private void OnStandUpAttempt(Entity entity, ref StatusEffectRelayedEvent args) { - UpdateStunModifiers(ent, speedModifier, speedModifier); + if (args.Args.Cancelled) + return; + + var ev = args.Args; + ev.Cancelled = true; + args.Args = ev; } - #region friction and movement listeners - - private void OnRefreshMovespeed(EntityUid ent, SlowedDownComponent comp, RefreshMovementSpeedModifiersEvent args) - { - args.ModifySpeed(comp.WalkSpeedModifier, comp.SprintSpeedModifier); - } - - #endregion - #region Attempt Event Handling private void OnMoveAttempt(EntityUid uid, StunnedComponent stunned, UpdateCanMoveEvent args) diff --git a/Content.Shared/Stunnable/SlowedDownComponent.cs b/Content.Shared/Stunnable/SlowedDownComponent.cs deleted file mode 100644 index c4822b1eae..0000000000 --- a/Content.Shared/Stunnable/SlowedDownComponent.cs +++ /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; -} diff --git a/Content.Shared/Stunnable/StunnableEvents.cs b/Content.Shared/Stunnable/StunnableEvents.cs index 18a7575487..f4a0191c92 100644 --- a/Content.Shared/Stunnable/StunnableEvents.cs +++ b/Content.Shared/Stunnable/StunnableEvents.cs @@ -15,6 +15,12 @@ namespace Content.Shared.Stunnable; [ByRefEvent] public record struct StunnedEvent; +/// +/// Raised on a stunned entity when something wants to remove the stunned component. +/// +[ByRefEvent] +public record struct StunEndAttemptEvent(bool Cancelled); + /// /// 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. /// [ByRefEvent] -public record struct KnockedDownEvent(TimeSpan Time); +public record struct KnockedDownEvent(TimeSpan? Time); /// /// Raised on an entity that needs to refresh its knockdown modifiers diff --git a/Content.Shared/Stunnable/StunnedComponent.cs b/Content.Shared/Stunnable/StunnedComponent.cs index 037a3cd3ac..8fb53e1a68 100644 --- a/Content.Shared/Stunnable/StunnedComponent.cs +++ b/Content.Shared/Stunnable/StunnedComponent.cs @@ -2,5 +2,8 @@ using Robust.Shared.GameStates; namespace Content.Shared.Stunnable; +/// +/// This is used to temporarily prevent an entity from moving or acting. +/// [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 index 0000000000..62b34967e5 --- /dev/null +++ b/Content.Shared/Stunnable/StunnedStatusEffectComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Stunnable; + +/// +/// Stun as a status effect. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedStunSystem))] +public sealed partial class StunnedStatusEffectComponent : Component; diff --git a/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs b/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs index eaaf336e96..f1f9a19a75 100644 --- a/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs +++ b/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs @@ -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; diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index cb3e5be478..714c740cf8 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -143,8 +143,6 @@ - type: Body - type: StatusEffects allowed: - - Stun - - SlowedDown - Flashed - type: TypingIndicator proto: robot diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml b/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml index 941091335a..d2dd761468 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml @@ -19,9 +19,7 @@ group: GenericNumber - type: StatusEffects allowed: - - SlowedDown - Stutter - - Stun - Electrocution - TemporaryBlindness - RadiationProtection diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml index 95dcd84ae6..77bb8e1a7f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml @@ -35,8 +35,6 @@ bodyType: KinematicController # Same for all inheritors - type: StatusEffects allowed: - - Stun - - SlowedDown - Stutter - Electrocution - type: Pullable @@ -385,7 +383,6 @@ chemicalMaxVolume: 100 - type: StatusEffects allowed: - - SlowedDown - Electrocution - type: MeleeWeapon soundHit: @@ -408,6 +405,7 @@ - FootstepSound - CannotSuicide - DoorBumpOpener + - StunImmune - type: NoSlip - type: ZombieImmune - type: ExaminableSolution diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml b/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml index 9500345e4f..4717e200dd 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml @@ -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: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index 13bce86b06..9b0bfb05ca 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 2251742436..279dbe9f54 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -15,8 +15,6 @@ bodyType: KinematicController # Same for all inheritors - type: StatusEffects allowed: - - Stun - - SlowedDown - Stutter - Electrocution - type: Repairable diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index eb0ea81617..11c0f0d21d 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index 0d6489744f..b6b344d6b5 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -100,7 +100,6 @@ types: {} - type: StatusEffects # Overwriting basesimplemob to remove flash, getting flashed as dragon just feelsbad allowed: - - SlowedDown - Stutter - Electrocution - TemporaryBlindness @@ -154,6 +153,7 @@ tags: - CannotSuicide - DoorBumpOpener + - StunImmune - type: Puller needsHands: false - type: RandomMetadata diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 9ea12f2ae3..849e65f3b2 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -119,9 +119,6 @@ - !type:WashCreamPieReaction - type: StatusEffects allowed: - - Stun - - Friction - - SlowedDown - Stutter - Electrocution - Drunk diff --git a/Resources/Prototypes/Entities/StatusEffects/misc.yml b/Resources/Prototypes/Entities/StatusEffects/misc.yml index 444600023a..273a52c0a8 100644 --- a/Resources/Prototypes/Entities/StatusEffects/misc.yml +++ b/Resources/Prototypes/Entities/StatusEffects/misc.yml @@ -19,6 +19,21 @@ 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 @@ -43,14 +62,6 @@ 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 index 0000000000..b6fa426f72 --- /dev/null +++ b/Resources/Prototypes/Entities/StatusEffects/movement.yml @@ -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 diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index aa3be06694..24e21742c3 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -831,6 +831,9 @@ - type: Tag id: Knife +- type: Tag + id: KnockdownImmune + - type: Tag id: LavaBrig @@ -1268,6 +1271,9 @@ - type: Tag id: Slice # sliced fruit, vegetables, pizza etc. +- type: Tag + id: SlowImmune + - type: Tag id: SmallAIChip @@ -1331,6 +1337,9 @@ - type: Tag id: StringInstrument +- type: Tag + id: StunImmune + - type: Tag id: SubdermalImplant