Stunnable New Status and Cleanup (#38618)

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
This commit is contained in:
Princess Cheeseballs
2025-07-21 10:22:11 -07:00
committed by GitHub
parent 2b2b9b11b8
commit e85bc1bb8c
56 changed files with 620 additions and 316 deletions

View File

@@ -1,4 +1,4 @@
using Content.Server.Stunnable; using Content.Server.Stunnable;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -17,9 +17,7 @@ namespace Content.IntegrationTests.Tests
components: components:
- type: Inventory - type: Inventory
- type: ContainerContainer - type: ContainerContainer
- type: StatusEffects - type: MobState
allowed:
- Stun
- type: entity - type: entity
name: InventoryJumpsuitJanitorDummy name: InventoryJumpsuitJanitorDummy
@@ -70,7 +68,7 @@ namespace Content.IntegrationTests.Tests
}); });
#pragma warning restore NUnit2045 #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 #pragma warning disable NUnit2045
// Since the mob is stunned, they can't equip this. // Since the mob is stunned, they can't equip this.

View File

@@ -96,7 +96,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
EntityManager.AddComponents(ent, injectedAnom.Components); 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); _jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
if (ent.Comp.StartSound is not null) 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) 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); _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)) if (_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom))
EntityManager.RemoveComponents(ent, injectedAnom.Components); 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 && if (ent.Comp.EndMessage is not null &&
_mind.TryGetMind(ent, out _, out var mindComponent) && _mind.TryGetMind(ent, out _, out var mindComponent) &&

View File

@@ -394,7 +394,7 @@ namespace Content.Server.Atmos.EntitySystems
flammable.Resisting = true; flammable.Resisting = true;
_popup.PopupEntity(Loc.GetString("flammable-component-resist-message"), uid, uid); _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... // TODO FLAMMABLE: Make this not use TimerComponent...
uid.SpawnTimer(2000, () => uid.SpawnTimer(2000, () =>

View File

@@ -101,7 +101,7 @@ public sealed class CluwneSystem : EntitySystem
else if (_robustRandom.Prob(component.KnockChance)) else if (_robustRandom.Prob(component.KnockChance))
{ {
_audio.PlayPvs(component.KnockSound, uid); _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); _chat.TrySendInGameICMessage(uid, "spasms", InGameICChatType.Emote, ChatTransmitRange.Normal);
} }
} }

View File

@@ -1,4 +1,4 @@
using Content.Shared.Bed.Sleep; using Content.Shared.Bed.Sleep;
using Content.Shared.Drowsiness; using Content.Shared.Drowsiness;
using Content.Shared.StatusEffectNew; using Content.Shared.StatusEffectNew;
using Content.Shared.StatusEffectNew.Components; using Content.Shared.StatusEffectNew.Components;

View File

@@ -397,7 +397,12 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
var shouldStun = siemensCoefficient > 0.5f; var shouldStun = siemensCoefficient > 0.5f;
if (shouldStun) if (shouldStun)
_stun.TryParalyze(uid, time * ParalyzeTimeMultiplier, refresh, statusEffects); {
_ = refresh
? _stun.TryUpdateParalyzeDuration(uid, time * ParalyzeTimeMultiplier)
: _stun.TryAddParalyzeDuration(uid, time * ParalyzeTimeMultiplier);
}
// TODO: Sparks here. // TODO: Sparks here.

View File

@@ -228,7 +228,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
continue; continue;
_npcFaction.RemoveFaction(uid, RevolutionaryNpcFaction); _npcFaction.RemoveFaction(uid, RevolutionaryNpcFaction);
_stun.TryParalyze(uid, stunTime, true); _stun.TryUpdateParalyzeDuration(uid, stunTime);
RemCompDeferred<RevolutionaryComponent>(uid); RemCompDeferred<RevolutionaryComponent>(uid);
_popup.PopupEntity(Loc.GetString("rev-break-control", ("name", Identity.Entity(uid, EntityManager))), 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."); _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(uid)} was deconverted due to all Head Revolutionaries dying.");

View File

@@ -461,7 +461,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
{ {
if (instrument.InstrumentPlayer is {Valid: true} mob) 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"), _popup.PopupEntity(Loc.GetString("instrument-component-finger-cramps-max-message"),
uid, mob, PopupType.LargeCaution); uid, mob, PopupType.LargeCaution);

View File

@@ -2,16 +2,15 @@ using Content.Server.Body.Systems;
using Content.Server.Fluids.EntitySystems; using Content.Server.Fluids.EntitySystems;
using Content.Server.Forensics; using Content.Server.Forensics;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Stunnable;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Body.Systems; using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Movement.Systems;
using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.StatusEffect;
using Robust.Server.Audio; using Robust.Server.Audio;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -27,7 +26,7 @@ namespace Content.Server.Medical
[Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PuddleSystem _puddle = default!; [Dependency] private readonly PuddleSystem _puddle = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = 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 ThirstSystem _thirst = default!;
[Dependency] private readonly ForensicsSystem _forensics = default!; [Dependency] private readonly ForensicsSystem _forensics = default!;
[Dependency] private readonly BloodstreamSystem _bloodstream = 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 // It fully empties the stomach, this amount from the chem stream is relatively small
var solutionSize = (MathF.Abs(thirstAdded) + MathF.Abs(hungerAdded)) / 6; var solutionSize = (MathF.Abs(thirstAdded) + MathF.Abs(hungerAdded)) / 6;
// Apply a bit of slowdown // Apply a bit of slowdown
if (TryComp<StatusEffectsComponent>(uid, out var status)) _movementMod.TryUpdateMovementSpeedModDuration(uid, MovementModStatusSystem.VomitingSlowdown, TimeSpan.FromSeconds(solutionSize), 0.5f);
_stun.TrySlowdown(uid, TimeSpan.FromSeconds(solutionSize), true, 0.5f, 0.5f, status);
// TODO: Need decals // TODO: Need decals
var solution = new Solution(); var solution = new Solution();

View File

@@ -63,7 +63,7 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
_audio.PlayPvs(comp.Sound, target); _audio.PlayPvs(comp.Sound, target);
_damageable.TryChangeDamage(target, comp.StunDamage, false, true, null, origin: uid); _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 // short cooldown to prevent instant stunlocking
_useDelay.SetLength((uid, useDelay), comp.Cooldown, id: comp.DelayId); _useDelay.SetLength((uid, useDelay), comp.Cooldown, id: comp.DelayId);

View File

@@ -1,4 +1,3 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Server.Storage.EntitySystems; using Content.Server.Storage.EntitySystems;
using Content.Server.Stunnable; using Content.Server.Stunnable;
@@ -7,7 +6,6 @@ using Content.Shared.Atmos.Components;
using Content.Shared.Containers.ItemSlots; using Content.Shared.Containers.ItemSlots;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.PneumaticCannon; using Content.Shared.PneumaticCannon;
using Content.Shared.StatusEffect;
using Content.Shared.Tools.Systems; using Content.Shared.Tools.Systems;
using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Events;
@@ -80,10 +78,9 @@ public sealed class PneumaticCannonSystem : SharedPneumaticCannonSystem
if (gas == null && component.GasUsage > 0f) if (gas == null && component.GasUsage > 0f)
return; return;
if (TryComp<StatusEffectsComponent>(args.User, out var status) if (component.Power == PneumaticCannonPower.High
&& 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", Popup.PopupEntity(Loc.GetString("pneumatic-cannon-component-power-stun",
("cannon", uid)), cannon, args.User); ("cannon", uid)), cannon, args.User);
} }

View File

@@ -177,7 +177,7 @@ public sealed partial class RevenantSystem : EntitySystem
ChangeEssenceAmount(uid, -abilityCost, component, false); ChangeEssenceAmount(uid, -abilityCost, component, false);
_statusEffects.TryAddStatusEffect<CorporealComponent>(uid, "Corporeal", TimeSpan.FromSeconds(debuffs.Y), 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; return true;
} }

View File

@@ -616,10 +616,7 @@ public sealed partial class ShuttleSystem
{ {
foreach (var child in toKnock) foreach (var child in toKnock)
{ {
if (!_statusQuery.TryGetComponent(child, out var status)) _stuns.TryUpdateParalyzeDuration(child, _hyperspaceKnockdownTime);
continue;
_stuns.TryParalyze(child, _hyperspaceKnockdownTime, true, status);
// If the guy we knocked down is on a spaced tile, throw them too // If the guy we knocked down is on a spaced tile, throw them too
if (grid != null) if (grid != null)

View File

@@ -249,7 +249,7 @@ public sealed partial class ShuttleSystem
if (direction.LengthSquared() > minsq) 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); _throwing.TryThrow(uid, direction, physics, Transform(uid), _projQuery, direction.Length(), playSound: false);
} }
else else

View File

@@ -1,8 +1,6 @@
using Content.Server.Stunnable.Components; using Content.Server.Stunnable.Components;
using Content.Shared.Standing; using Content.Shared.Movement.Systems;
using Content.Shared.StatusEffect;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Physics.Dynamics;
using Content.Shared.Throwing; using Content.Shared.Throwing;
using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Events;
@@ -12,6 +10,7 @@ namespace Content.Server.Stunnable
internal sealed class StunOnCollideSystem : EntitySystem internal sealed class StunOnCollideSystem : EntitySystem
{ {
[Dependency] private readonly StunSystem _stunSystem = default!; [Dependency] private readonly StunSystem _stunSystem = default!;
[Dependency] private readonly MovementModStatusSystem _movementMod = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -22,15 +21,19 @@ namespace Content.Server.Stunnable
private void TryDoCollideStun(EntityUid uid, StunOnCollideComponent component, EntityUid target) private void TryDoCollideStun(EntityUid uid, StunOnCollideComponent component, EntityUid target)
{ {
if (!TryComp<StatusEffectsComponent>(target, out var status)) _stunSystem.TryUpdateStunDuration(target, component.StunAmount);
return;
_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); _movementMod.TryUpdateMovementSpeedModDuration(
target,
_stunSystem.TrySlowdown(target, component.SlowdownAmount, component.Refresh, component.WalkSpeedModifier, component.SprintSpeedModifier, status); MovementModStatusSystem.TaserSlowdown,
component.SlowdownAmount,
component.WalkSpeedModifier,
component.SprintSpeedModifier
);
} }
private void HandleCollide(EntityUid uid, StunOnCollideComponent component, ref StartCollideEvent args) private void HandleCollide(EntityUid uid, StunOnCollideComponent component, ref StartCollideEvent args)
{ {
if (args.OurFixtureId != component.FixtureID) if (args.OurFixtureId != component.FixtureID)

View File

@@ -34,7 +34,7 @@ public sealed class GoliathTentacleSystem : EntitySystem
// TODO: animation // TODO: animation
_popup.PopupPredicted(Loc.GetString("tentacle-ability-use-popup", ("entity", args.Performer)), args.Performer, args.Performer, type: PopupType.SmallCaution); _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; var coords = args.Target;
List<EntityCoordinates> spawnPos = new(); List<EntityCoordinates> spawnPos = new();

View File

@@ -18,7 +18,6 @@ using Content.Shared.Slippery;
using Content.Shared.Sound; using Content.Shared.Sound;
using Content.Shared.Sound.Components; using Content.Shared.Sound.Components;
using Content.Shared.Speech; using Content.Shared.Speech;
using Content.Shared.StatusEffect;
using Content.Shared.StatusEffectNew; using Content.Shared.StatusEffectNew;
using Content.Shared.Stunnable; using Content.Shared.Stunnable;
using Content.Shared.Traits.Assorted; using Content.Shared.Traits.Assorted;
@@ -38,8 +37,8 @@ public sealed partial class SleepingSystem : EntitySystem
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedEmitSoundSystem _emitSound = default!; [Dependency] private readonly SharedEmitSoundSystem _emitSound = default!;
[Dependency] private readonly StatusEffect.StatusEffectsSystem _statusEffectOld = default!; [Dependency] private readonly StatusEffectsSystem _statusEffect = default!;
[Dependency] private readonly StatusEffectNew.StatusEffectsSystem _statusEffectNew = default!; [Dependency] private readonly SharedStunSystem _stun = default!;
public static readonly EntProtoId SleepActionId = "ActionSleep"; public static readonly EntProtoId SleepActionId = "ActionSleep";
public static readonly EntProtoId WakeActionId = "ActionWake"; public static readonly EntProtoId WakeActionId = "ActionWake";
@@ -67,6 +66,8 @@ public sealed partial class SleepingSystem : EntitySystem
SubscribeLocalEvent<SleepingComponent, ExaminedEvent>(OnExamined); SubscribeLocalEvent<SleepingComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb); SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb);
SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand); SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<SleepingComponent, StunEndAttemptEvent>(OnStunEndAttempt);
SubscribeLocalEvent<SleepingComponent, StandUpAttemptEvent>(OnStandUpAttempt);
SubscribeLocalEvent<ForcedSleepingStatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied); SubscribeLocalEvent<ForcedSleepingStatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt); SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
@@ -106,9 +107,7 @@ public sealed partial class SleepingSystem : EntitySystem
{ {
if (args.FellAsleep) if (args.FellAsleep)
{ {
// Expiring status effects would remove the components needed for sleeping // Just in case we're not using the sleeping status
_statusEffectOld.TryRemoveStatusEffect(ent.Owner, "Stun");
EnsureComp<StunnedComponent>(ent); EnsureComp<StunnedComponent>(ent);
EnsureComp<KnockedDownComponent>(ent); EnsureComp<KnockedDownComponent>(ent);
@@ -128,8 +127,9 @@ public sealed partial class SleepingSystem : EntitySystem
return; return;
} }
RemComp<StunnedComponent>(ent); _stun.TryUnstun(ent.Owner);
RemComp<KnockedDownComponent>(ent); _stun.TryStanding(ent.Owner, out _);
RemComp<SpamEmitSoundComponent>(ent); RemComp<SpamEmitSoundComponent>(ent);
} }
@@ -174,6 +174,17 @@ public sealed partial class SleepingSystem : EntitySystem
args.Cancelled = true; 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) private void OnExamined(Entity<SleepingComponent> ent, ref ExaminedEvent args)
{ {
if (args.IsInDetailsRange) if (args.IsInDetailsRange)
@@ -187,7 +198,6 @@ public sealed partial class SleepingSystem : EntitySystem
if (!args.CanInteract || !args.CanAccess) if (!args.CanInteract || !args.CanAccess)
return; return;
var target = args.Target;
var user = args.User; var user = args.User;
AlternativeVerb verb = new() AlternativeVerb verb = new()
{ {
@@ -309,7 +319,7 @@ public sealed partial class SleepingSystem : EntitySystem
if (!Resolve(ent, ref ent.Comp, false)) if (!Resolve(ent, ref ent.Comp, false))
return false; return false;
if (!force && _statusEffectNew.HasEffectComp<ForcedSleepingStatusEffectComponent>(ent)) if (!force && _statusEffect.HasEffectComp<ForcedSleepingStatusEffectComponent>(ent))
{ {
if (user != null) if (user != null)
{ {

View File

@@ -571,7 +571,7 @@ public sealed partial class ClimbSystem : VirtualController
_damageableSystem.TryChangeDamage(args.Climber, component.ClimberDamage, origin: args.Climber); _damageableSystem.TryChangeDamage(args.Climber, component.ClimberDamage, origin: args.Climber);
_damageableSystem.TryChangeDamage(uid, component.TableDamage, 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 // Not shown to the user, since they already get a 'you climb on the glass table' popup
_popupSystem.PopupEntity( _popupSystem.PopupEntity(

View File

@@ -129,7 +129,7 @@ public sealed class ClumsySystem : EntitySystem
if (ent.Comp.GunShootFailDamage != null) if (ent.Comp.GunShootFailDamage != null)
_damageable.TryChangeDamage(ent, ent.Comp.GunShootFailDamage, origin: ent); _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) // Apply salt to the wound ("Honk!") (No idea what this comment means)
_audio.PlayPvs(ent.Comp.GunShootFailSound, ent); _audio.PlayPvs(ent.Comp.GunShootFailSound, ent);
@@ -202,7 +202,7 @@ public sealed class ClumsySystem : EntitySystem
_damageable.TryChangeDamage(target, bonkComp.BonkDamage, true); _damageable.TryChangeDamage(target, bonkComp.BonkDamage, true);
} }
_stun.TryParalyze(target, stunTime, true); _stun.TryUpdateParalyzeDuration(target, stunTime);
} }
#endregion #endregion
} }

View File

@@ -46,7 +46,7 @@ public sealed class DamageOnHighSpeedImpactSystem : EntitySystem
component.LastHit = _gameTiming.CurTime; component.LastHit = _gameTiming.CurTime;
if (_robustRandom.Prob(component.StunChance)) 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; var damageScale = component.SpeedDamageFactor * speed / component.MinimumSpeed;

View File

@@ -92,7 +92,7 @@ public sealed class DamageOnInteractSystem : EntitySystem
// Attempt to paralyze the user after they have taken damage // Attempt to paralyze the user after they have taken damage
if (_random.Prob(entity.Comp.StunChance)) 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. // 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. // BeingPulled must be checked because the entity will be spastically thrown around without this.

View File

@@ -8,9 +8,12 @@ using Content.Shared.Damage.Events;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Effects; using Content.Shared.Effects;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Projectiles; using Content.Shared.Projectiles;
using Content.Shared.Rejuvenate; using Content.Shared.Rejuvenate;
using Content.Shared.Rounding; using Content.Shared.Rounding;
using Content.Shared.StatusEffectNew;
using Content.Shared.Stunnable; using Content.Shared.Stunnable;
using Content.Shared.Throwing; using Content.Shared.Throwing;
using Content.Shared.Weapons.Melee.Events; using Content.Shared.Weapons.Melee.Events;
@@ -20,6 +23,7 @@ using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -27,15 +31,19 @@ namespace Content.Shared.Damage.Systems;
public abstract partial class SharedStaminaSystem : EntitySystem 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] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly INetManager _net = default!; [Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!; [Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!; [Dependency] private readonly MovementModStatusSystem _movementMod = default!;
[Dependency] protected readonly SharedStunSystem StunSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = 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!;
/// <summary> /// <summary>
/// How much of a buffer is there between the stun duration and when stuns can be re-applied. /// 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; entity.Comp.StaminaDamage = 0;
AdjustSlowdown(entity.Owner); AdjustStatus(entity.Owner);
RemComp<ActiveStaminaComponent>(entity); RemComp<ActiveStaminaComponent>(entity);
_status.TryRemoveStatusEffect(entity, StaminaLow);
UpdateStaminaVisuals(entity); UpdateStaminaVisuals(entity);
Dirty(entity); Dirty(entity);
} }
@@ -284,7 +293,7 @@ public abstract partial class SharedStaminaSystem : EntitySystem
component.NextUpdate = nextUpdate; component.NextUpdate = nextUpdate;
} }
AdjustSlowdown(uid); AdjustStatus(uid);
UpdateStaminaVisuals((uid, component)); UpdateStaminaVisuals((uid, component));
@@ -292,6 +301,7 @@ public abstract partial class SharedStaminaSystem : EntitySystem
if (component.AfterCritical && oldDamage > component.StaminaDamage && component.StaminaDamage <= 0f) 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' 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) if (!component.Critical)
@@ -384,7 +394,7 @@ public abstract partial class SharedStaminaSystem : EntitySystem
component.Critical = true; component.Critical = true;
component.StaminaDamage = component.CritThreshold; component.StaminaDamage = component.CritThreshold;
if (StunSystem.TryParalyze(uid, component.StunTime, true)) if (StunSystem.TryUpdateParalyzeDuration(uid, component.StunTime))
StunSystem.TrySeeingStars(uid); StunSystem.TrySeeingStars(uid);
// Give them buffer before being able to be re-stunned // Give them buffer before being able to be re-stunned
@@ -412,18 +422,19 @@ public abstract partial class SharedStaminaSystem : EntitySystem
} }
/// <summary> /// <summary>
/// Adjusts the movement speed of an entity based on its current <see cref="StaminaComponent.StaminaDamage"/> value. /// Adjusts the modifiers of the <see cref="StaminaLow"/> status effect entity and applies relevant statuses.
/// If the entity has a <see cref="SlowOnDamageComponent"/>, its custom damage-to-speed thresholds are used, /// System iterates through the <see cref="StaminaComponent.StunModifierThresholds"/> to find correct movement modifer.
/// otherwise, a default set of thresholds is applied. /// This modifier is saved to the Stamina Low Status Effect entity's <see cref="MovementModStatusEffectComponent"/>.
/// 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.
/// </summary> /// </summary>
/// <param name="ent">Entity to update</param> /// <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)) if (!Resolve(ent, ref ent.Comp))
return; return;
if (!_status.TrySetStatusEffectDuration(ent, StaminaLow, out var status))
return;
var closest = FixedPoint2.Zero; var closest = FixedPoint2.Zero;
// Iterate through the dictionary in the similar way as in Damage.SlowOnDamageSystem.OnRefreshMovespeed // 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; closest = thres.Key;
} }
StunSystem.UpdateStunModifiers(ent, ent.Comp.StunModifierThresholds[closest]); _movementMod.TryUpdateMovementStatus(ent.Owner, status.Value, ent.Comp.StunModifierThresholds[closest]);
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]

View File

@@ -534,7 +534,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
if (door.CrushDamage != null) if (door.CrushDamage != null)
_damageableSystem.TryChangeDamage(entity, door.CrushDamage, origin: uid); _damageableSystem.TryChangeDamage(entity, door.CrushDamage, origin: uid);
_stunSystem.TryParalyze(entity, stunTime, true); _stunSystem.TryUpdateParalyzeDuration(entity, stunTime);
} }
if (door.CurrentlyCrushing.Count == 0) if (door.CurrentlyCrushing.Count == 0)

View File

@@ -1,4 +1,3 @@
using Content.Shared.EntityEffects;
using Content.Shared.Stunnable; using Content.Shared.Stunnable;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -14,9 +13,11 @@ public sealed partial class Paralyze : EntityEffect
[DataField] public bool Refresh = true; [DataField] public bool Refresh = true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-paralyze", => Loc.GetString(
"reagent-effect-guidebook-paralyze",
("chance", Probability), ("chance", Probability),
("time", ParalyzeTime)); ("time", ParalyzeTime)
);
public override void Effect(EntityEffectBaseArgs args) public override void Effect(EntityEffectBaseArgs args)
{ {
@@ -27,7 +28,10 @@ public sealed partial class Paralyze : EntityEffect
paralyzeTime *= (double)reagentArgs.Scale; 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));
} }
} }

View File

@@ -20,6 +20,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using System.Linq; using System.Linq;
using Content.Shared.Movement.Systems;
namespace Content.Shared.Flash; namespace Content.Shared.Flash;
@@ -33,6 +34,7 @@ public abstract class SharedFlashSystem : EntitySystem
[Dependency] private readonly ExamineSystemShared _examine = default!; [Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedStunSystem _stun = default!; [Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly MovementModStatusSystem _movementMod = default!;
[Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
@@ -163,9 +165,9 @@ public abstract class SharedFlashSystem : EntitySystem
return; return;
if (stunDuration != null) if (stunDuration != null)
_stun.TryParalyze(target, stunDuration.Value, true); _stun.TryUpdateParalyzeDuration(target, stunDuration.Value);
else else
_stun.TrySlowdown(target, flashDuration, true, slowTo, slowTo); _movementMod.TryUpdateMovementSpeedModDuration(target, MovementModStatusSystem.FlashSlowdown, flashDuration, slowTo);
if (displayPopup && user != null && target != user && Exists(user.Value)) if (displayPopup && user != null && target != user && Exists(user.Value))
{ {

View File

@@ -510,8 +510,8 @@ public abstract class SharedMagicSystem : EntitySystem
_mind.TransferTo(tarMind, ev.Performer); _mind.TransferTo(tarMind, ev.Performer);
} }
_stun.TryParalyze(ev.Target, ev.TargetStunDuration, true); _stun.TryUpdateParalyzeDuration(ev.Target, ev.TargetStunDuration);
_stun.TryParalyze(ev.Performer, ev.PerformerStunDuration, true); _stun.TryUpdateParalyzeDuration(ev.Performer, ev.PerformerStunDuration);
} }
#endregion #endregion

View File

@@ -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;
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events; using Content.Shared.Movement.Events;
using Content.Shared.StatusEffectNew; using Content.Shared.StatusEffectNew;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -6,23 +6,54 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.Movement.Systems; namespace Content.Shared.Movement.Systems;
/// <summary> /// <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> /// </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 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"; public static readonly EntProtoId StatusEffectFriction = "StatusEffectFriction";
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly StatusEffectsSystem _status = default!; [Dependency] private readonly StatusEffectsSystem _status = default!;
/// <inheritdoc/>
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<MovementModStatusEffectComponent, StatusEffectRemovedEvent>(OnMovementModRemoved);
SubscribeLocalEvent<MovementModStatusEffectComponent, StatusEffectRelayedEvent<RefreshMovementSpeedModifiersEvent>>(OnRefreshRelay);
SubscribeLocalEvent<FrictionStatusEffectComponent, StatusEffectRemovedEvent>(OnFrictionStatusEffectRemoved); SubscribeLocalEvent<FrictionStatusEffectComponent, StatusEffectRemovedEvent>(OnFrictionStatusEffectRemoved);
SubscribeLocalEvent<FrictionStatusEffectComponent, StatusEffectRelayedEvent<RefreshFrictionModifiersEvent>>(OnRefreshFrictionStatus); SubscribeLocalEvent<FrictionStatusEffectComponent, StatusEffectRelayedEvent<RefreshFrictionModifiersEvent>>(OnRefreshFrictionStatus);
SubscribeLocalEvent<FrictionStatusEffectComponent, StatusEffectRelayedEvent<TileFrictionEvent>>(OnRefreshTileFrictionStatus); 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) private void OnRefreshFrictionStatus(Entity<FrictionStatusEffectComponent> ent, ref StatusEffectRelayedEvent<RefreshFrictionModifiersEvent> args)
{ {
var ev = args.Args; var ev = args.Args;
@@ -39,28 +70,169 @@ public sealed class MovementModStatusSystem : EntitySystem
} }
/// <summary> /// <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> /// </summary>
public bool TryFriction(EntityUid uid, /// <param name="uid">Target entity, for which speed should be modified.</param>
TimeSpan time, /// <param name="effectProtoId">Slowdown effect to be used.</param>
bool refresh, /// <param name="duration">Duration of speed modifying effect.</param>
float friction, /// <param name="speedModifier">Multiplier by which walking/sprinting speed should be modified.</param>
float acceleration) /// <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
)
{ {
if (time <= TimeSpan.Zero) 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 (!Resolve(status, ref status.Comp))
return false; return false;
if (refresh) 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 _status.TryUpdateStatusEffectDuration(uid, StatusEffectFriction, out var status, time) 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); && TrySetFrictionStatus(status.Value, friction, acceleration, uid);
} }
else
/// <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.TryAddStatusEffectDuration(uid, StatusEffectFriction, out var status, time) return _status.TryUpdateStatusEffectDuration(uid, StatusEffectFriction, out var status, duration)
&& TrySetFrictionStatus(status.Value, friction, acceleration, uid); && TrySetFrictionStatus(status.Value, friction, acceleration, uid);
} }
}
/// <summary> /// <summary>
/// Sets the friction status modifiers for a status effect. /// Sets the friction status modifiers for a status effect.
@@ -92,9 +264,4 @@ public sealed class MovementModStatusSystem : EntitySystem
_movementSpeedModifier.RefreshFrictionModifiers(entity); _movementSpeedModifier.RefreshFrictionModifiers(entity);
return true; return true;
} }
private void OnFrictionStatusEffectRemoved(Entity<FrictionStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
{
TrySetFrictionStatus(entity!, 1f, args.Target);
}
} }

View File

@@ -64,7 +64,7 @@ namespace Content.Shared.Nutrition.EntitySystems
CreamedEntity(uid, creamPied, args); 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) {} protected virtual void CreamedEntity(EntityUid uid, CreamPiedComponent creamPied, ThrowHitByEvent args) {}

View File

@@ -42,7 +42,7 @@ public abstract class SharedRevolutionarySystem : EntitySystem
var stunTime = TimeSpan.FromSeconds(4); var stunTime = TimeSpan.FromSeconds(4);
var name = Identity.Entity(uid, EntityManager); var name = Identity.Entity(uid, EntityManager);
RemComp<RevolutionaryComponent>(uid); RemComp<RevolutionaryComponent>(uid);
_sharedStun.TryParalyze(uid, stunTime, true); _sharedStun.TryUpdateParalyzeDuration(uid, stunTime);
_popupSystem.PopupEntity(Loc.GetString("rev-break-control", ("name", name)), uid); _popupSystem.PopupEntity(Loc.GetString("rev-break-control", ("name", name)), uid);
} }
} }

View File

@@ -1,4 +1,4 @@
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Silicons.Laws.Components; using Content.Shared.Silicons.Laws.Components;
@@ -57,7 +57,7 @@ public abstract partial class SharedSiliconLawSystem : EntitySystem
if(_mind.TryGetMind(uid, out var mindId, out _)) if(_mind.TryGetMind(uid, out var mindId, out _))
EnsureSubvertedSiliconRole(mindId); EnsureSubvertedSiliconRole(mindId);
_stunSystem.TryParalyze(uid, component.StunTime, true); _stunSystem.TryUpdateParalyzeDuration(uid, component.StunTime);
args.Handled = true; args.Handled = true;
} }

View File

@@ -2,11 +2,9 @@ using Content.Shared.Administration.Logs;
using Content.Shared.Damage.Systems; using Content.Shared.Damage.Systems;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Robust.Shared.Network;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Content.Shared.Popups; using Content.Shared.StatusEffectNew;
using Content.Shared.StatusEffect;
using Content.Shared.StepTrigger.Systems; using Content.Shared.StepTrigger.Systems;
using Content.Shared.Stunnable; using Content.Shared.Stunnable;
using Content.Shared.Throwing; using Content.Shared.Throwing;
@@ -16,7 +14,6 @@ using Robust.Shared.Containers;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems; using Robust.Shared.Physics.Systems;
using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Events;
using Robust.Shared.Utility;
namespace Content.Shared.Slippery; namespace Content.Shared.Slippery;
@@ -27,8 +24,8 @@ public sealed class SlipperySystem : EntitySystem
[Dependency] private readonly MovementModStatusSystem _movementMod = default!; [Dependency] private readonly MovementModStatusSystem _movementMod = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedStunSystem _stun = default!; [Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly StatusEffectsSystem _status = default!;
[Dependency] private readonly SharedStaminaSystem _stamina = default!; [Dependency] private readonly SharedStaminaSystem _stamina = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
[Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SpeedModifierContactsSystem _speedModifier = default!; [Dependency] private readonly SpeedModifierContactsSystem _speedModifier = default!;
@@ -90,7 +87,7 @@ public sealed class SlipperySystem : EntitySystem
private bool CanSlip(EntityUid uid, EntityUid toSlip) private bool CanSlip(EntityUid uid, EntityUid toSlip)
{ {
return !_container.IsEntityInContainer(uid) 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) 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. // Preventing from playing the slip sound and stunning when you are already knocked down.
if (!HasComp<KnockedDownComponent>(other)) 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 _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); _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}"); _adminLogger.Add(LogType.Slip, LogImpact.Low, $"{ToPrettyString(other):mob} slipped on collision with {ToPrettyString(uid):entity}");
} }

View File

@@ -63,7 +63,7 @@ public sealed partial class ReformSystem : EntitySystem
{ {
// Stun them when they use the action for the amount of reform time. // Stun them when they use the action for the amount of reform time.
if (comp.ShouldStun) 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); _popupSystem.PopupClient(Loc.GetString(comp.PopupText, ("name", uid)), uid, uid);
// Create a doafter & start it // Create a doafter & start it

View 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) public bool TryAddStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan duration)
{ {
return TryAddStatusEffectDuration(target, effectProto, out _, duration); return TryAddStatusEffectDuration(target, effectProto, out _, duration);
@@ -61,7 +61,7 @@ public sealed partial class StatusEffectsSystem
return true; 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) public bool TrySetStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null)
{ {
return TrySetStatusEffectDuration(target, effectProto, out _, duration); return TrySetStatusEffectDuration(target, effectProto, out _, duration);
@@ -91,7 +91,7 @@ public sealed partial class StatusEffectsSystem
return true; 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) public bool TryUpdateStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null)
{ {
return TryUpdateStatusEffectDuration(target, effectProto, out _, duration); return TryUpdateStatusEffectDuration(target, effectProto, out _, duration);

View File

@@ -1,6 +1,7 @@
using Content.Shared.Movement.Events; using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Content.Shared.StatusEffectNew.Components; using Content.Shared.StatusEffectNew.Components;
using Content.Shared.Stunnable;
using Robust.Shared.Player; using Robust.Shared.Player;
namespace Content.Shared.StatusEffectNew; namespace Content.Shared.StatusEffectNew;
@@ -12,8 +13,14 @@ public sealed partial class StatusEffectsSystem
SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerAttachedEvent>(RelayStatusEffectEvent); SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerAttachedEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerDetachedEvent>(RelayStatusEffectEvent); SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerDetachedEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, RefreshMovementSpeedModifiersEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, UpdateCanMoveEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, RefreshFrictionModifiersEvent>(RefRelayStatusEffectEvent); SubscribeLocalEvent<StatusEffectContainerComponent, RefreshFrictionModifiersEvent>(RefRelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, TileFrictionEvent>(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 private void RefRelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct

View File

@@ -156,7 +156,7 @@ public sealed partial class StatusEffectsSystem : EntitySystem
Dirty(effect, effectComp); Dirty(effect, effectComp);
} }
private bool CanAddStatusEffect(EntityUid uid, EntProtoId effectProto) public bool CanAddStatusEffect(EntityUid uid, EntProtoId effectProto)
{ {
if (!_proto.TryIndex(effectProto, out var effectProtoData)) if (!_proto.TryIndex(effectProto, out var effectProtoData))
return false; return false;

View File

@@ -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;

View File

@@ -51,7 +51,7 @@ public abstract partial class SharedStunSystem
// Action blockers // Action blockers
SubscribeLocalEvent<KnockedDownComponent, BuckleAttemptEvent>(OnBuckleAttempt); SubscribeLocalEvent<KnockedDownComponent, BuckleAttemptEvent>(OnBuckleAttempt);
SubscribeLocalEvent<KnockedDownComponent, StandAttemptEvent>(OnStandUpAttempt); SubscribeLocalEvent<KnockedDownComponent, StandAttemptEvent>(OnStandAttempt);
// Updating movement a friction // Updating movement a friction
SubscribeLocalEvent<KnockedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshKnockedSpeed); SubscribeLocalEvent<KnockedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshKnockedSpeed);
@@ -452,7 +452,7 @@ public abstract partial class SharedStunSystem
#region Action Blockers #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) if (entity.Comp.LifeStage <= ComponentLifeStage.Running)
args.Cancel(); args.Cancel();

View File

@@ -4,7 +4,6 @@ using Content.Shared.Alert;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
using Content.Shared.Inventory.Events; using Content.Shared.Inventory.Events;
using Content.Shared.Item; using Content.Shared.Item;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems; using Content.Shared.Damage.Systems;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
@@ -14,35 +13,35 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Movement.Events; using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Content.Shared.Standing; using Content.Shared.Standing;
using Content.Shared.StatusEffect; using Content.Shared.StatusEffectNew;
using Content.Shared.Throwing; using Content.Shared.Throwing;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Events;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Shared.Stunnable; namespace Content.Shared.Stunnable;
public abstract partial class SharedStunSystem : EntitySystem public abstract partial class SharedStunSystem : EntitySystem
{ {
[Dependency] protected readonly ActionBlockerSystem Blocker = default!; public static readonly EntProtoId StunId = "StatusEffectStunned";
[Dependency] protected readonly AlertsSystem Alerts = default!; public static readonly EntProtoId KnockdownId = "StatusEffectKnockdown";
[Dependency] protected readonly IGameTiming GameTiming = default!; [Dependency] protected readonly IGameTiming GameTiming = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = 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 EntityWhitelistSystem _entityWhitelist = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!; [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
[Dependency] protected readonly SharedDoAfterSystem DoAfter = default!; [Dependency] protected readonly SharedDoAfterSystem DoAfter = default!;
[Dependency] protected readonly SharedStaminaSystem Stamina = default!; [Dependency] protected readonly SharedStaminaSystem Stamina = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffect = default!; [Dependency] private readonly StatusEffectsSystem _status = default!;
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<SlowedDownComponent, ComponentInit>(OnSlowInit);
SubscribeLocalEvent<SlowedDownComponent, ComponentShutdown>(OnSlowRemove);
SubscribeLocalEvent<SlowedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
SubscribeLocalEvent<StunnedComponent, ComponentStartup>(UpdateCanMove); SubscribeLocalEvent<StunnedComponent, ComponentStartup>(UpdateCanMove);
SubscribeLocalEvent<StunnedComponent, ComponentShutdown>(OnStunShutdown); SubscribeLocalEvent<StunnedComponent, ComponentShutdown>(OnStunShutdown);
@@ -61,6 +60,14 @@ public abstract partial class SharedStunSystem : EntitySystem
SubscribeLocalEvent<StunnedComponent, IsUnequippingAttemptEvent>(OnUnequipAttempt); SubscribeLocalEvent<StunnedComponent, IsUnequippingAttemptEvent>(OnUnequipAttempt);
SubscribeLocalEvent<MobStateComponent, MobStateChangedEvent>(OnMobStateChanged); 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(); InitializeKnockdown();
InitializeAppearance(); InitializeAppearance();
} }
@@ -72,10 +79,6 @@ public abstract partial class SharedStunSystem : EntitySystem
private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobStateChangedEvent args) private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobStateChangedEvent args)
{ {
if (!TryComp<StatusEffectsComponent>(uid, out var status))
{
return;
}
switch (args.NewMobState) switch (args.NewMobState)
{ {
case MobState.Alive: case MobState.Alive:
@@ -84,12 +87,12 @@ public abstract partial class SharedStunSystem : EntitySystem
} }
case MobState.Critical: case MobState.Critical:
{ {
_statusEffect.TryRemoveStatusEffect(uid, "Stun"); _status.TryRemoveStatusEffect(uid, StunId);
break; break;
} }
case MobState.Dead: case MobState.Dead:
{ {
_statusEffect.TryRemoveStatusEffect(uid, "Stun"); _status.TryRemoveStatusEffect(uid, StunId);
break; break;
} }
case MobState.Invalid: case MobState.Invalid:
@@ -119,71 +122,95 @@ public abstract partial class SharedStunSystem : EntitySystem
if (_entityWhitelist.IsBlacklistPass(ent.Comp.Blacklist, args.OtherEntity)) if (_entityWhitelist.IsBlacklistPass(ent.Comp.Blacklist, args.OtherEntity))
return; return;
if (!TryComp<StatusEffectsComponent>(args.OtherEntity, out var status)) TryUpdateStunDuration(args.OtherEntity, ent.Comp.Duration);
return; TryKnockdown(args.OtherEntity, ent.Comp.Duration, true, force: true);
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);
} }
// TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...) // TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...)
public bool TryAddStunDuration(EntityUid uid, TimeSpan duration)
/// <summary>
/// Stuns the entity, disallowing it from doing many interactions temporarily.
/// </summary>
public bool TryStun(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null)
{ {
if (time <= TimeSpan.Zero) if (!_status.TryAddStatusEffectDuration(uid, StunId, duration))
return false; 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; return false;
if (!_statusEffect.TryAddStatusEffect<StunnedComponent>(uid, "Stun", time, refresh)) OnStunnedSuccessfully(uid, duration);
return false; 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); 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; 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> /// <summary>
/// Knocks down the entity, making it fall to the ground. /// Knocks down the entity, making it fall to the ground.
/// </summary> /// </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) if (time <= TimeSpan.Zero)
return false; return false;
// Can't fall down if you can't actually be downed. // Can't fall down if you can't actually be downed.
if (!HasComp<StandingStateComponent>(uid)) if (!Resolve(entity, ref entity.Comp, false))
return false; return false;
if (!force)
{
var evAttempt = new KnockDownAttemptEvent(autoStand, drop); var evAttempt = new KnockDownAttemptEvent(autoStand, drop);
RaiseLocalEvent(uid, ref evAttempt); RaiseLocalEvent(entity, ref evAttempt);
if (evAttempt.Cancelled) if (evAttempt.Cancelled)
return false; return false;
// Initialize our component with the relevant data we need if we don't have it autoStand = evAttempt.AutoStand;
if (EnsureComp<KnockedDownComponent>(uid, out var component)) 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)
{ {
RefreshKnockedMovement((uid, component)); // Initialize our component with the relevant data we need if we don't have it
CancelKnockdownDoAfter((uid, component)); if (EnsureComp<KnockedDownComponent>(entity, out var component))
{
RefreshKnockedMovement((entity, component));
CancelKnockdownDoAfter((entity, component));
} }
else else
{ {
@@ -191,127 +218,87 @@ public abstract partial class SharedStunSystem : EntitySystem
if (drop) if (drop)
{ {
var ev = new DropHandItemsEvent(); 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... // 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); var knockedEv = new KnockedDownEvent(time);
RaiseLocalEvent(uid, ref knockedEv); RaiseLocalEvent(entity, ref knockedEv);
UpdateKnockdownTime((uid, component), knockedEv.Time, refresh); 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");
Alerts.ShowAlert(uid, KnockdownAlert, null, (GameTiming.CurTime, component.NextUpdate)); Alerts.ShowAlert(entity, KnockdownAlert, null, (GameTiming.CurTime, component.NextUpdate));
}
_adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} knocked down for {time.Seconds} seconds"); 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<StunnedComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp, logMissing: false))
return true; return true;
var ev = new StunEndAttemptEvent();
RaiseLocalEvent(entity, ref ev);
return !ev.Cancelled && RemComp<StunnedComponent>(entity);
} }
/// <summary> private void OnStunEffectApplied(Entity<StunnedStatusEffectComponent> entity, ref StatusEffectAppliedEvent args)
/// Applies knockdown and stun to the entity temporarily.
/// </summary>
public bool TryParalyze(EntityUid uid, TimeSpan time, bool refresh,
StatusEffectsComponent? status = null)
{ {
if (!Resolve(uid, ref status, false)) if (GameTiming.ApplyingState)
return false;
return TryKnockdown(uid, time, refresh) && TryStun(uid, time, refresh, status);
}
/// <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)
{
if (!Resolve(uid, ref status, false))
return false;
if (time <= TimeSpan.Zero)
return false;
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;
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
return true;
}
return false;
}
/// <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)
{
if (!Resolve(ent, ref ent.Comp))
return; return;
if ( EnsureComp<StunnedComponent>(args.Target);
(MathHelper.CloseTo(walkSpeedModifier, 1f) && MathHelper.CloseTo(runSpeedModifier, 1f) && ent.Comp.StaminaDamage == 0f) || }
(walkSpeedModifier == 0f && runSpeedModifier == 0f)
) private void OnStunStatusRemoved(Entity<StunnedStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
{ {
RemComp<SlowedDownComponent>(ent); TryUnstun(args.Target);
}
private void OnStunEndAttempt(Entity<StunnedStatusEffectComponent> entity, ref StatusEffectRelayedEvent<StunEndAttemptEvent> args)
{
if (args.Args.Cancelled)
return; return;
var ev = args.Args;
ev.Cancelled = true;
args.Args = ev;
} }
EnsureComp<SlowedDownComponent>(ent, out var comp); private void OnStandUpAttempt(Entity<KnockdownStatusEffectComponent> entity, ref StatusEffectRelayedEvent<StandUpAttemptEvent> args)
comp.WalkSpeedModifier = walkSpeedModifier;
comp.SprintSpeedModifier = runSpeedModifier;
_movementSpeedModifier.RefreshMovementSpeedModifiers(ent);
Dirty(ent);
}
/// <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)
{ {
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 #region Attempt Event Handling
private void OnMoveAttempt(EntityUid uid, StunnedComponent stunned, UpdateCanMoveEvent args) private void OnMoveAttempt(EntityUid uid, StunnedComponent stunned, UpdateCanMoveEvent args)

View File

@@ -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;
}

View File

@@ -15,6 +15,12 @@ namespace Content.Shared.Stunnable;
[ByRefEvent] [ByRefEvent]
public record struct StunnedEvent; 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> /// <summary>
/// Raised directed on an entity before it is knocked down to see if it should be cancelled, and to determine /// Raised directed on an entity before it is knocked down to see if it should be cancelled, and to determine
/// knocked down arguments. /// 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. /// Raised directed on an entity when it is knocked down.
/// </summary> /// </summary>
[ByRefEvent] [ByRefEvent]
public record struct KnockedDownEvent(TimeSpan Time); public record struct KnockedDownEvent(TimeSpan? Time);
/// <summary> /// <summary>
/// Raised on an entity that needs to refresh its knockdown modifiers /// Raised on an entity that needs to refresh its knockdown modifiers

View File

@@ -2,5 +2,8 @@ using Robust.Shared.GameStates;
namespace Content.Shared.Stunnable; namespace Content.Shared.Stunnable;
/// <summary>
/// This is used to temporarily prevent an entity from moving or acting.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedStunSystem))] [RegisterComponent, NetworkedComponent, Access(typeof(SharedStunSystem))]
public sealed partial class StunnedComponent : Component; public sealed partial class StunnedComponent : Component;

View File

@@ -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;

View File

@@ -97,7 +97,7 @@ public sealed class MeleeThrowOnHitSystem : EntitySystem
RaiseLocalEvent(target, ref startEvent); RaiseLocalEvent(target, ref startEvent);
if (ent.Comp.StunTime != null) if (ent.Comp.StunTime != null)
_stun.TryParalyze(target, ent.Comp.StunTime.Value, false); _stun.TryAddParalyzeDuration(target, ent.Comp.StunTime.Value);
if (direction == Vector2.Zero) if (direction == Vector2.Zero)
return; return;

View File

@@ -143,8 +143,6 @@
- type: Body - type: Body
- type: StatusEffects - type: StatusEffects
allowed: allowed:
- Stun
- SlowedDown
- Flashed - Flashed
- type: TypingIndicator - type: TypingIndicator
proto: robot proto: robot

View File

@@ -19,9 +19,7 @@
group: GenericNumber group: GenericNumber
- type: StatusEffects - type: StatusEffects
allowed: allowed:
- SlowedDown
- Stutter - Stutter
- Stun
- Electrocution - Electrocution
- TemporaryBlindness - TemporaryBlindness
- RadiationProtection - RadiationProtection

View File

@@ -35,8 +35,6 @@
bodyType: KinematicController # Same for all inheritors bodyType: KinematicController # Same for all inheritors
- type: StatusEffects - type: StatusEffects
allowed: allowed:
- Stun
- SlowedDown
- Stutter - Stutter
- Electrocution - Electrocution
- type: Pullable - type: Pullable
@@ -385,7 +383,6 @@
chemicalMaxVolume: 100 chemicalMaxVolume: 100
- type: StatusEffects - type: StatusEffects
allowed: allowed:
- SlowedDown
- Electrocution - Electrocution
- type: MeleeWeapon - type: MeleeWeapon
soundHit: soundHit:
@@ -408,6 +405,7 @@
- FootstepSound - FootstepSound
- CannotSuicide - CannotSuicide
- DoorBumpOpener - DoorBumpOpener
- StunImmune
- type: NoSlip - type: NoSlip
- type: ZombieImmune - type: ZombieImmune
- type: ExaminableSolution - type: ExaminableSolution

View File

@@ -48,7 +48,6 @@
BaseUnshaded: dead_glow BaseUnshaded: dead_glow
- type: StatusEffects - type: StatusEffects
allowed: allowed:
- Stun
- Corporeal - Corporeal
- Electrocution - Electrocution
- StaminaModifier - StaminaModifier
@@ -91,6 +90,7 @@
- type: Tag - type: Tag
tags: tags:
- FootstepSound - FootstepSound
- SlowImmune
- type: Destructible - type: Destructible
thresholds: thresholds:
- trigger: - trigger:

View File

@@ -20,7 +20,6 @@
- state: active - state: active
- type: StatusEffects - type: StatusEffects
allowed: allowed:
- Stun
- Corporeal - Corporeal
- type: Damageable - type: Damageable
damageContainer: ManifestedSpirit damageContainer: ManifestedSpirit
@@ -83,3 +82,7 @@
- type: Reactive - type: Reactive
groups: groups:
Acidic: [Touch] Acidic: [Touch]
- type: Tag
tags:
- SlowImmune
- KnockdownImmune

View File

@@ -15,8 +15,6 @@
bodyType: KinematicController # Same for all inheritors bodyType: KinematicController # Same for all inheritors
- type: StatusEffects - type: StatusEffects
allowed: allowed:
- Stun
- SlowedDown
- Stutter - Stutter
- Electrocution - Electrocution
- type: Repairable - type: Repairable

View File

@@ -19,7 +19,6 @@
baseSprintSpeed : 4 baseSprintSpeed : 4
- type: StatusEffects - type: StatusEffects
allowed: allowed:
- SlowedDown
- Stutter - Stutter
- Electrocution - Electrocution
- TemporaryBlindness - TemporaryBlindness
@@ -32,6 +31,7 @@
- type: Tag - type: Tag
tags: tags:
- DoorBumpOpener - DoorBumpOpener
- StunImmune
- type: entity - type: entity
abstract: true abstract: true
@@ -93,8 +93,6 @@
baseDecayRate: 0.04 baseDecayRate: 0.04
- type: StatusEffects - type: StatusEffects
allowed: allowed:
- Stun
- SlowedDown
- Stutter - Stutter
- Electrocution - Electrocution
- TemporaryBlindness - TemporaryBlindness

View File

@@ -100,7 +100,6 @@
types: {} types: {}
- type: StatusEffects # Overwriting basesimplemob to remove flash, getting flashed as dragon just feelsbad - type: StatusEffects # Overwriting basesimplemob to remove flash, getting flashed as dragon just feelsbad
allowed: allowed:
- SlowedDown
- Stutter - Stutter
- Electrocution - Electrocution
- TemporaryBlindness - TemporaryBlindness
@@ -154,6 +153,7 @@
tags: tags:
- CannotSuicide - CannotSuicide
- DoorBumpOpener - DoorBumpOpener
- StunImmune
- type: Puller - type: Puller
needsHands: false needsHands: false
- type: RandomMetadata - type: RandomMetadata

View File

@@ -119,9 +119,6 @@
- !type:WashCreamPieReaction - !type:WashCreamPieReaction
- type: StatusEffects - type: StatusEffects
allowed: allowed:
- Stun
- Friction
- SlowedDown
- Stutter - Stutter
- Electrocution - Electrocution
- Drunk - Drunk

View File

@@ -19,6 +19,21 @@
components: components:
- MobState - 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. # The creature sleeps so heavily that nothing can wake him up. Not even its own death.
- type: entity - type: entity
parent: MobStatusEffectBase parent: MobStatusEffectBase
@@ -26,6 +41,8 @@
name: forced sleep name: forced sleep
components: components:
- type: ForcedSleepingStatusEffect - type: ForcedSleepingStatusEffect
- type: StunnedStatusEffect
- type: KnockdownStatusEffect
# This creature is asleep because it's disconnected from the game. # This creature is asleep because it's disconnected from the game.
- type: entity - type: entity
@@ -34,6 +51,8 @@
name: forced sleep name: forced sleep
components: components:
- type: ForcedSleepingStatusEffect - type: ForcedSleepingStatusEffect
- type: StunnedStatusEffect
- type: KnockdownStatusEffect
# Blurs your vision and makes you randomly fall asleep # Blurs your vision and makes you randomly fall asleep
- type: entity - type: entity
@@ -43,14 +62,6 @@
components: components:
- type: DrowsinessStatusEffect - type: DrowsinessStatusEffect
# Makes you more slippery, or perhaps less slippery.
- type: entity
parent: MobStatusEffectBase
id: StatusEffectFriction
name: friction
components:
- type: FrictionStatusEffect
# Adds drugs overlay # Adds drugs overlay
- type: entity - type: entity
parent: MobStatusEffectBase parent: MobStatusEffectBase

View File

@@ -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

View File

@@ -831,6 +831,9 @@
- type: Tag - type: Tag
id: Knife id: Knife
- type: Tag
id: KnockdownImmune
- type: Tag - type: Tag
id: LavaBrig id: LavaBrig
@@ -1268,6 +1271,9 @@
- type: Tag - type: Tag
id: Slice # sliced fruit, vegetables, pizza etc. id: Slice # sliced fruit, vegetables, pizza etc.
- type: Tag
id: SlowImmune
- type: Tag - type: Tag
id: SmallAIChip id: SmallAIChip
@@ -1331,6 +1337,9 @@
- type: Tag - type: Tag
id: StringInstrument id: StringInstrument
- type: Tag
id: StunImmune
- type: Tag - type: Tag
id: SubdermalImplant id: SubdermalImplant