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

View File

@@ -96,7 +96,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
EntityManager.AddComponents(ent, injectedAnom.Components);
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
_stun.TryUpdateParalyzeDuration(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration));
_jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
if (ent.Comp.StartSound is not null)
@@ -125,7 +125,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
private void OnAnomalyPulse(Entity<InnerBodyAnomalyComponent> ent, ref AnomalyPulseEvent args)
{
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
_stun.TryUpdateParalyzeDuration(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity));
_jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
}
@@ -213,7 +213,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
if (_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom))
EntityManager.RemoveComponents(ent, injectedAnom.Components);
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
_stun.TryUpdateParalyzeDuration(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration));
if (ent.Comp.EndMessage is not null &&
_mind.TryGetMind(ent, out _, out var mindComponent) &&

View File

@@ -394,7 +394,7 @@ namespace Content.Server.Atmos.EntitySystems
flammable.Resisting = true;
_popup.PopupEntity(Loc.GetString("flammable-component-resist-message"), uid, uid);
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(2f), true);
_stunSystem.TryUpdateParalyzeDuration(uid, TimeSpan.FromSeconds(2f));
// TODO FLAMMABLE: Make this not use TimerComponent...
uid.SpawnTimer(2000, () =>

View File

@@ -101,7 +101,7 @@ public sealed class CluwneSystem : EntitySystem
else if (_robustRandom.Prob(component.KnockChance))
{
_audio.PlayPvs(component.KnockSound, uid);
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(component.ParalyzeTime), true);
_stunSystem.TryUpdateParalyzeDuration(uid, TimeSpan.FromSeconds(component.ParalyzeTime));
_chat.TrySendInGameICMessage(uid, "spasms", InGameICChatType.Emote, ChatTransmitRange.Normal);
}
}

View File

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

View File

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

View File

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

View File

@@ -461,7 +461,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
{
if (instrument.InstrumentPlayer is {Valid: true} mob)
{
_stuns.TryParalyze(mob, TimeSpan.FromSeconds(1), true);
_stuns.TryUpdateParalyzeDuration(mob, TimeSpan.FromSeconds(1));
_popup.PopupEntity(Loc.GetString("instrument-component-finger-cramps-max-message"),
uid, mob, PopupType.LargeCaution);

View File

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

View File

@@ -63,7 +63,7 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
_audio.PlayPvs(comp.Sound, target);
_damageable.TryChangeDamage(target, comp.StunDamage, false, true, null, origin: uid);
_stun.TryParalyze(target, comp.StunTime, refresh: false);
_stun.TryAddParalyzeDuration(target, comp.StunTime);
// short cooldown to prevent instant stunlocking
_useDelay.SetLength((uid, useDelay), comp.Cooldown, id: comp.DelayId);

View File

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

View File

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

View File

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

View File

@@ -249,7 +249,7 @@ public sealed partial class ShuttleSystem
if (direction.LengthSquared() > minsq)
{
_stuns.TryKnockdown(uid, knockdownTime, true);
_stuns.TryUpdateKnockdownDuration(uid, knockdownTime);
_throwing.TryThrow(uid, direction, physics, Transform(uid), _projQuery, direction.Length(), playSound: false);
}
else

View File

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

View File

@@ -34,7 +34,7 @@ public sealed class GoliathTentacleSystem : EntitySystem
// TODO: animation
_popup.PopupPredicted(Loc.GetString("tentacle-ability-use-popup", ("entity", args.Performer)), args.Performer, args.Performer, type: PopupType.SmallCaution);
_stun.TryStun(args.Performer, TimeSpan.FromSeconds(0.8f), false);
_stun.TryAddStunDuration(args.Performer, TimeSpan.FromSeconds(0.8f));
var coords = args.Target;
List<EntityCoordinates> spawnPos = new();

View File

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

View File

@@ -571,7 +571,7 @@ public sealed partial class ClimbSystem : VirtualController
_damageableSystem.TryChangeDamage(args.Climber, component.ClimberDamage, origin: args.Climber);
_damageableSystem.TryChangeDamage(uid, component.TableDamage, origin: args.Climber);
_stunSystem.TryParalyze(args.Climber, TimeSpan.FromSeconds(component.StunTime), true);
_stunSystem.TryUpdateParalyzeDuration(args.Climber, TimeSpan.FromSeconds(component.StunTime));
// Not shown to the user, since they already get a 'you climb on the glass table' popup
_popupSystem.PopupEntity(

View File

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

View File

@@ -46,7 +46,7 @@ public sealed class DamageOnHighSpeedImpactSystem : EntitySystem
component.LastHit = _gameTiming.CurTime;
if (_robustRandom.Prob(component.StunChance))
_stun.TryStun(uid, TimeSpan.FromSeconds(component.StunSeconds), true);
_stun.TryUpdateStunDuration(uid, TimeSpan.FromSeconds(component.StunSeconds));
var damageScale = component.SpeedDamageFactor * speed / component.MinimumSpeed;

View File

@@ -92,7 +92,7 @@ public sealed class DamageOnInteractSystem : EntitySystem
// Attempt to paralyze the user after they have taken damage
if (_random.Prob(entity.Comp.StunChance))
_stun.TryParalyze(args.User, TimeSpan.FromSeconds(entity.Comp.StunSeconds), true);
_stun.TryUpdateParalyzeDuration(args.User, TimeSpan.FromSeconds(entity.Comp.StunSeconds));
}
// Check if the entity's Throw bool is false, or if the entity has the PullableComponent, then if the entity is currently being pulled.
// BeingPulled must be checked because the entity will be spastically thrown around without this.

View File

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

View File

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

View File

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

View File

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

View File

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

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.StatusEffectNew;
using Robust.Shared.Prototypes;
@@ -6,23 +6,54 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.Movement.Systems;
/// <summary>
/// This handles the application of movement and friction modifiers to an entity as status effects.
/// This handles the slowed status effect and other movement status effects.
/// <see cref="MovementModStatusEffectComponent"/> holds a modifier for a status effect which is relayed to a mob's
/// All effects of this kinda are multiplicative.
/// Each 'source' of speed modification usually should have separate effect prototype.
/// </summary>
/// <remarks>
/// Movement modifying status effects should by default be separate effect prototypes, and their effects
/// should stack with each other (multiply). In case multiplicative effect is undesirable - such effects
/// could occupy same prototype, but be aware that this will make controlling duration of effect
/// extra 'challenging', as it will be shared too.
/// </remarks>
public sealed class MovementModStatusSystem : EntitySystem
{
public static readonly EntProtoId VomitingSlowdown = "VomitingSlowdownStatusEffect";
public static readonly EntProtoId TaserSlowdown = "TaserSlowdownStatusEffect";
public static readonly EntProtoId FlashSlowdown = "FlashSlowdownStatusEffect";
public static readonly EntProtoId StatusEffectFriction = "StatusEffectFriction";
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly StatusEffectsSystem _status = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<MovementModStatusEffectComponent, StatusEffectRemovedEvent>(OnMovementModRemoved);
SubscribeLocalEvent<MovementModStatusEffectComponent, StatusEffectRelayedEvent<RefreshMovementSpeedModifiersEvent>>(OnRefreshRelay);
SubscribeLocalEvent<FrictionStatusEffectComponent, StatusEffectRemovedEvent>(OnFrictionStatusEffectRemoved);
SubscribeLocalEvent<FrictionStatusEffectComponent, StatusEffectRelayedEvent<RefreshFrictionModifiersEvent>>(OnRefreshFrictionStatus);
SubscribeLocalEvent<FrictionStatusEffectComponent, StatusEffectRelayedEvent<TileFrictionEvent>>(OnRefreshTileFrictionStatus);
}
private void OnMovementModRemoved(Entity<MovementModStatusEffectComponent> ent, ref StatusEffectRemovedEvent args)
{
TryUpdateMovementStatus(args.Target, (ent, ent), 1f);
}
private void OnFrictionStatusEffectRemoved(Entity<FrictionStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
{
TrySetFrictionStatus(entity!, 1f, args.Target);
}
private void OnRefreshRelay(
Entity<MovementModStatusEffectComponent> entity,
ref StatusEffectRelayedEvent<RefreshMovementSpeedModifiersEvent> args
)
{
args.Args.ModifySpeed(entity.Comp.WalkSpeedModifier, entity.Comp.WalkSpeedModifier);
}
private void OnRefreshFrictionStatus(Entity<FrictionStatusEffectComponent> ent, ref StatusEffectRelayedEvent<RefreshFrictionModifiersEvent> args)
{
var ev = args.Args;
@@ -39,28 +70,169 @@ public sealed class MovementModStatusSystem : EntitySystem
}
/// <summary>
/// Applies a friction de-buff to the player.
/// Apply mob's walking/running speed modifier with provided duration, or increment duration of existing.
/// </summary>
public bool TryFriction(EntityUid uid,
TimeSpan time,
bool refresh,
float friction,
float acceleration)
/// <param name="uid">Target entity, for which speed should be modified.</param>
/// <param name="effectProtoId">Slowdown effect to be used.</param>
/// <param name="duration">Duration of speed modifying effect.</param>
/// <param name="speedModifier">Multiplier by which walking/sprinting speed should be modified.</param>
/// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
public bool TryAddMovementSpeedModDuration(
EntityUid uid,
EntProtoId effectProtoId,
TimeSpan duration,
float speedModifier
)
{
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;
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);
}
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);
}
}
/// <summary>
/// Sets the friction status modifiers for a status effect.
@@ -92,9 +264,4 @@ public sealed class MovementModStatusSystem : EntitySystem
_movementSpeedModifier.RefreshFrictionModifiers(entity);
return true;
}
private void OnFrictionStatusEffectRemoved(Entity<FrictionStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
{
TrySetFrictionStatus(entity!, 1f, args.Target);
}
}

View File

@@ -64,7 +64,7 @@ namespace Content.Shared.Nutrition.EntitySystems
CreamedEntity(uid, creamPied, args);
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(creamPie.ParalyzeTime), true);
_stunSystem.TryUpdateParalyzeDuration(uid, TimeSpan.FromSeconds(creamPie.ParalyzeTime));
}
protected virtual void CreamedEntity(EntityUid uid, CreamPiedComponent creamPied, ThrowHitByEvent args) {}

View File

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

View File

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

View File

@@ -2,11 +2,9 @@ using Content.Shared.Administration.Logs;
using Content.Shared.Damage.Systems;
using Content.Shared.Database;
using Content.Shared.Inventory;
using Robust.Shared.Network;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Popups;
using Content.Shared.StatusEffect;
using Content.Shared.StatusEffectNew;
using Content.Shared.StepTrigger.Systems;
using Content.Shared.Stunnable;
using Content.Shared.Throwing;
@@ -16,7 +14,6 @@ using Robust.Shared.Containers;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Physics.Events;
using Robust.Shared.Utility;
namespace Content.Shared.Slippery;
@@ -27,8 +24,8 @@ public sealed class SlipperySystem : EntitySystem
[Dependency] private readonly MovementModStatusSystem _movementMod = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly StatusEffectsSystem _status = default!;
[Dependency] private readonly SharedStaminaSystem _stamina = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SpeedModifierContactsSystem _speedModifier = default!;
@@ -90,7 +87,7 @@ public sealed class SlipperySystem : EntitySystem
private bool CanSlip(EntityUid uid, EntityUid toSlip)
{
return !_container.IsEntityInContainer(uid)
&& _statusEffects.CanApplyEffect(toSlip, "Stun"); //Should be KnockedDown instead?
&& _status.CanAddStatusEffect(toSlip, SharedStunSystem.StunId); //Should be KnockedDown instead?
}
public void TrySlip(EntityUid uid, SlipperyComponent component, EntityUid other, bool requiresContact = true)
@@ -125,12 +122,18 @@ public sealed class SlipperySystem : EntitySystem
// Preventing from playing the slip sound and stunning when you are already knocked down.
if (!HasComp<KnockedDownComponent>(other))
{
_stun.TryStun(other, component.SlipData.StunTime, true);
_stun.TryUpdateStunDuration(other, component.SlipData.StunTime);
_stamina.TakeStaminaDamage(other, component.StaminaDamage); // Note that this can stamCrit
_movementMod.TryFriction(other, component.FrictionStatusTime, true, component.SlipData.SlipFriction, component.SlipData.SlipFriction);
_movementMod.TryUpdateFrictionModDuration(
other,
component.FrictionStatusTime,
component.SlipData.SlipFriction,
component.SlipData.SlipFriction
);
_audio.PlayPredicted(component.SlipSound, other, other);
}
_stun.TryKnockdown(other, component.SlipData.KnockdownTime, true, true);
_stun.TryKnockdown(other, component.SlipData.KnockdownTime, true, force: true);
_adminLogger.Add(LogType.Slip, LogImpact.Low, $"{ToPrettyString(other):mob} slipped on collision with {ToPrettyString(uid):entity}");
}

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

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)
{
return TryAddStatusEffectDuration(target, effectProto, out _, duration);
@@ -61,7 +61,7 @@ public sealed partial class StatusEffectsSystem
return true;
}
/// <inheritdoc cref="TrySetStatusEffectDuration(Robust.Shared.GameObjects.EntityUid,Robust.Shared.Prototypes.EntProtoId,out Robust.Shared.GameObjects.EntityUid?,System.TimeSpan?)"/>
/// <inheritdoc cref="TrySetStatusEffectDuration(EntityUid,EntProtoId,out EntityUid?,TimeSpan?)"/>
public bool TrySetStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null)
{
return TrySetStatusEffectDuration(target, effectProto, out _, duration);
@@ -91,7 +91,7 @@ public sealed partial class StatusEffectsSystem
return true;
}
/// <inheritdoc cref="TryUpdateStatusEffectDuration(Robust.Shared.GameObjects.EntityUid,Robust.Shared.Prototypes.EntProtoId,out Robust.Shared.GameObjects.EntityUid?,System.TimeSpan?)"/>
/// <inheritdoc cref="TryUpdateStatusEffectDuration(EntityUid,EntProtoId,out EntityUid?,TimeSpan?)"/>
public bool TryUpdateStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null)
{
return TryUpdateStatusEffectDuration(target, effectProto, out _, duration);

View File

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

View File

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

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

View File

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

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]
public record struct StunnedEvent;
/// <summary>
/// Raised on a stunned entity when something wants to remove the stunned component.
/// </summary>
[ByRefEvent]
public record struct StunEndAttemptEvent(bool Cancelled);
/// <summary>
/// Raised directed on an entity before it is knocked down to see if it should be cancelled, and to determine
/// knocked down arguments.
@@ -29,7 +35,7 @@ public record struct KnockDownAttemptEvent(bool AutoStand, bool Drop)
/// Raised directed on an entity when it is knocked down.
/// </summary>
[ByRefEvent]
public record struct KnockedDownEvent(TimeSpan Time);
public record struct KnockedDownEvent(TimeSpan? Time);
/// <summary>
/// Raised on an entity that needs to refresh its knockdown modifiers

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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