-using Content.Server.Stunnable;
+using Content.Server.Stunnable;
using Content.Shared.Inventory;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
components:
- type: Inventory
- type: ContainerContainer
- - type: StatusEffects
- allowed:
- - Stun
+ - type: MobState
- type: entity
name: InventoryJumpsuitJanitorDummy
});
#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.
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)
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);
}
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) &&
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, () =>
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);
}
}
-using Content.Shared.Bed.Sleep;
+using Content.Shared.Bed.Sleep;
using Content.Shared.Drowsiness;
using Content.Shared.StatusEffectNew;
using Content.Shared.StatusEffectNew.Components;
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.
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.");
{
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);
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;
[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!;
// 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();
_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);
-using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Storage.EntitySystems;
using Content.Server.Stunnable;
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;
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);
}
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;
}
{
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)
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
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;
internal sealed class StunOnCollideSystem : EntitySystem
{
[Dependency] private readonly StunSystem _stunSystem = default!;
+ [Dependency] private readonly MovementModStatusSystem _movementMod = default!;
public override void Initialize()
{
private void TryDoCollideStun(EntityUid uid, StunOnCollideComponent component, EntityUid target)
{
- if (!TryComp<StatusEffectsComponent>(target, out var status))
- return;
-
- _stunSystem.TryStun(target, component.StunAmount, component.Refresh, status);
+ _stunSystem.TryUpdateStunDuration(target, component.StunAmount);
- _stunSystem.TryKnockdown(target, component.KnockdownAmount, component.Refresh, component.AutoStand);
+ _stunSystem.TryKnockdown(target, component.KnockdownAmount, component.Refresh, component.AutoStand, force: true);
- _stunSystem.TrySlowdown(target, component.SlowdownAmount, component.Refresh, component.WalkSpeedModifier, component.SprintSpeedModifier, status);
+ _movementMod.TryUpdateMovementSpeedModDuration(
+ target,
+ MovementModStatusSystem.TaserSlowdown,
+ component.SlowdownAmount,
+ component.WalkSpeedModifier,
+ component.SprintSpeedModifier
+ );
}
+
private void HandleCollide(EntityUid uid, StunOnCollideComponent component, ref StartCollideEvent args)
{
if (args.OurFixtureId != component.FixtureID)
// 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();
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;
[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";
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);
{
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);
return;
}
- RemComp<StunnedComponent>(ent);
- RemComp<KnockedDownComponent>(ent);
+ _stun.TryUnstun(ent.Owner);
+ _stun.TryStanding(ent.Owner, out _);
+
RemComp<SpamEmitSoundComponent>(ent);
}
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)
if (!args.CanInteract || !args.CanAccess)
return;
- var target = args.Target;
var user = args.User;
AlternativeVerb verb = new()
{
if (!Resolve(ent, ref ent.Comp, false))
return false;
- if (!force && _statusEffectNew.HasEffectComp<ForcedSleepingStatusEffectComponent>(ent))
+ if (!force && _statusEffect.HasEffectComp<ForcedSleepingStatusEffectComponent>(ent))
{
if (user != null)
{
_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(
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);
_damageable.TryChangeDamage(target, bonkComp.BonkDamage, true);
}
- _stun.TryParalyze(target, stunTime, true);
+ _stun.TryUpdateParalyzeDuration(target, stunTime);
}
#endregion
}
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;
// 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.
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;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
public abstract partial class SharedStaminaSystem : EntitySystem
{
+ public static readonly EntProtoId StaminaLow = "StatusEffectStaminaLow";
+
+ [Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
+ [Dependency] private readonly MovementModStatusSystem _movementMod = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
+ [Dependency] private readonly StatusEffectsSystem _status = default!;
[Dependency] protected readonly SharedStunSystem StunSystem = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly IConfigurationManager _config = default!;
/// <summary>
/// How much of a buffer is there between the stun duration and when stuns can be re-applied.
}
entity.Comp.StaminaDamage = 0;
- AdjustSlowdown(entity.Owner);
+ AdjustStatus(entity.Owner);
RemComp<ActiveStaminaComponent>(entity);
+ _status.TryRemoveStatusEffect(entity, StaminaLow);
UpdateStaminaVisuals(entity);
Dirty(entity);
}
component.NextUpdate = nextUpdate;
}
- AdjustSlowdown(uid);
+ AdjustStatus(uid);
UpdateStaminaVisuals((uid, component));
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)
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
}
/// <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
closest = thres.Key;
}
- StunSystem.UpdateStunModifiers(ent, ent.Comp.StunModifierThresholds[closest]);
+ _movementMod.TryUpdateMovementStatus(ent.Owner, status.Value, ent.Comp.StunModifierThresholds[closest]);
}
[Serializable, NetSerializable]
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)
-using Content.Shared.EntityEffects;
using Content.Shared.Stunnable;
using Robust.Shared.Prototypes;
[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)
{
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));
}
}
using Robust.Shared.Random;
using Robust.Shared.Timing;
using System.Linq;
+using Content.Shared.Movement.Systems;
namespace Content.Shared.Flash;
[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!;
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))
{
_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
--- /dev/null
+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;
+}
-using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.StatusEffectNew;
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;
}
/// <summary>
- /// Applies a friction de-buff to the player.
+ /// Apply mob's walking/running speed modifier with provided duration, or increment duration of existing.
/// </summary>
- public bool TryFriction(EntityUid uid,
- TimeSpan time,
- bool refresh,
- float friction,
- float acceleration)
+ /// <param name="uid">Target entity, for which speed should be modified.</param>
+ /// <param name="effectProtoId">Slowdown effect to be used.</param>
+ /// <param name="duration">Duration of speed modifying effect.</param>
+ /// <param name="speedModifier">Multiplier by which walking/sprinting speed should be modified.</param>
+ /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+ public bool TryAddMovementSpeedModDuration(
+ EntityUid uid,
+ EntProtoId effectProtoId,
+ TimeSpan duration,
+ float speedModifier
+ )
+ {
+ return TryAddMovementSpeedModDuration(uid, effectProtoId, duration, speedModifier, speedModifier);
+ }
+
+ /// <summary>
+ /// Apply mob's walking/running speed modifier with provided duration, or increment duration of existing.
+ /// </summary>
+ /// <param name="uid">Target entity, for which speed should be modified.</param>
+ /// <param name="effectProtoId">Slowdown effect to be used.</param>
+ /// <param name="duration">Duration of speed modifying effect.</param>
+ /// <param name="walkSpeedModifier">Multiplier by which walking speed should be modified.</param>
+ /// <param name="sprintSpeedModifier">Multiplier by which sprinting speed should be modified.</param>
+ /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+ public bool TryAddMovementSpeedModDuration(
+ EntityUid uid,
+ EntProtoId effectProtoId,
+ TimeSpan duration,
+ float walkSpeedModifier,
+ float sprintSpeedModifier
+ )
+ {
+ return _status.TryAddStatusEffectDuration(uid, effectProtoId, out var status, duration)
+ && TryUpdateMovementStatus(uid, status!.Value, walkSpeedModifier, sprintSpeedModifier);
+ }
+
+ /// <summary>
+ /// Apply mob's walking/running speed modifier with provided duration,
+ /// or update duration of existing if it is lesser than provided duration.
+ /// </summary>
+ /// <param name="uid">Target entity, for which speed should be modified.</param>
+ /// <param name="effectProtoId">Slowdown effect to be used.</param>
+ /// <param name="duration">Duration of speed modifying effect.</param>
+ /// <param name="speedModifier">Multiplier by which walking/sprinting speed should be modified.</param>
+ /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+ public bool TryUpdateMovementSpeedModDuration(
+ EntityUid uid,
+ EntProtoId effectProtoId,
+ TimeSpan duration,
+ float speedModifier
+ )
+ {
+ return TryUpdateMovementSpeedModDuration(uid, effectProtoId, duration, speedModifier, speedModifier);
+ }
+
+ /// <summary>
+ /// Apply mob's walking/running speed modifier with provided duration,
+ /// or update duration of existing if it is lesser than provided duration.
+ /// </summary>
+ /// <param name="uid">Target entity, for which speed should be modified.</param>
+ /// <param name="effectProtoId">Slowdown effect to be used.</param>
+ /// <param name="duration">Duration of speed modifying effect.</param>
+ /// <param name="walkSpeedModifier">Multiplier by which walking speed should be modified.</param>
+ /// <param name="sprintSpeedModifier">Multiplier by which sprinting speed should be modified.</param>
+ /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+ public bool TryUpdateMovementSpeedModDuration(
+ EntityUid uid,
+ EntProtoId effectProtoId,
+ TimeSpan? duration,
+ float walkSpeedModifier,
+ float sprintSpeedModifier
+ )
+ {
+ return _status.TryUpdateStatusEffectDuration(uid, effectProtoId, out var status, duration)
+ && TryUpdateMovementStatus(uid, status!.Value, walkSpeedModifier, sprintSpeedModifier);
+ }
+
+ /// <summary>
+ /// Updates entity's movement speed using <see cref="MovementModStatusEffectComponent"/> to provided values.
+ /// Then refreshes the movement speed of the entity.
+ /// </summary>
+ /// <param name="uid">Entity whose component we're updating</param>
+ /// <param name="status">Status effect entity whose modifiers we are updating</param>
+ /// <param name="walkSpeedModifier">New walkSpeedModifer we're applying</param>
+ /// <param name="sprintSpeedModifier">New sprintSpeedModifier we're applying</param>
+ public bool TryUpdateMovementStatus(
+ EntityUid uid,
+ Entity<MovementModStatusEffectComponent?> status,
+ float walkSpeedModifier,
+ float sprintSpeedModifier
+ )
{
- if (time <= TimeSpan.Zero)
+ if (!Resolve(status, ref status.Comp))
return false;
- if (refresh)
- {
- return _status.TryUpdateStatusEffectDuration(uid, StatusEffectFriction, out var status, time)
- && TrySetFrictionStatus(status.Value, friction, acceleration, uid);
- }
- else
- {
- return _status.TryAddStatusEffectDuration(uid, StatusEffectFriction, out var status, time)
+ status.Comp.SprintSpeedModifier = sprintSpeedModifier;
+ status.Comp.WalkSpeedModifier = walkSpeedModifier;
+
+ _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
+
+ return true;
+ }
+
+ /// <summary>
+ /// Updates entity's movement speed using <see cref="MovementModStatusEffectComponent"/> to provided value.
+ /// Then refreshes the movement speed of the entity.
+ /// </summary>
+ /// <param name="uid">Entity whose component we're updating</param>
+ /// <param name="status">Status effect entity whose modifiers we are updating</param>
+ /// <param name="speedModifier">
+ /// Multiplier by which speed should be modified.
+ /// Will be applied to both walking and running speed.
+ /// </param>
+ public bool TryUpdateMovementStatus(
+ EntityUid uid,
+ Entity<MovementModStatusEffectComponent?> status,
+ float speedModifier
+ )
+ {
+ return TryUpdateMovementStatus(uid, status, speedModifier, speedModifier);
+ }
+
+ /// <summary>
+ /// Apply friction modifier with provided duration,
+ /// or incrementing duration of existing.
+ /// </summary>
+ /// <param name="uid">Target entity, for which friction modifier should be applied.</param>
+ /// <param name="duration">Duration of speed modifying effect.</param>
+ /// <param name="friction">Multiplier by which walking speed should be modified.</param>
+ /// <param name="acceleration">Multiplier by which sprinting speed should be modified.</param>
+ /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+ public bool TryAddFrictionModDuration(
+ EntityUid uid,
+ TimeSpan duration,
+ float friction,
+ float acceleration
+ )
+ {
+ return _status.TryAddStatusEffectDuration(uid, StatusEffectFriction, out var status, duration)
&& TrySetFrictionStatus(status.Value, friction, acceleration, uid);
- }
+ }
+
+ /// <summary>
+ /// Apply friction modifier with provided duration,
+ /// or update duration of existing if it is lesser than provided duration.
+ /// </summary>
+ /// <param name="uid">Target entity, for which friction modifier should be applied.</param>
+ /// <param name="duration">Duration of speed modifying effect.</param>
+ /// <param name="friction">Multiplier by which walking speed should be modified.</param>
+ /// <param name="acceleration">Multiplier by which sprinting speed should be modified.</param>
+ /// <returns>True if entity have slowdown effect applied now or previously and duration was modified.</returns>
+ public bool TryUpdateFrictionModDuration(
+ EntityUid uid,
+ TimeSpan duration,
+ float friction,
+ float acceleration
+ )
+ {
+ return _status.TryUpdateStatusEffectDuration(uid, StatusEffectFriction, out var status, duration)
+ && TrySetFrictionStatus(status.Value, friction, acceleration, uid);
}
/// <summary>
_movementSpeedModifier.RefreshFrictionModifiers(entity);
return true;
}
-
- private void OnFrictionStatusEffectRemoved(Entity<FrictionStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
- {
- TrySetFrictionStatus(entity!, 1f, args.Target);
- }
}
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) {}
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);
}
}
-using Content.Shared.Emag.Systems;
+using Content.Shared.Emag.Systems;
using Content.Shared.Mind;
using Content.Shared.Popups;
using Content.Shared.Silicons.Laws.Components;
if(_mind.TryGetMind(uid, out var mindId, out _))
EnsureSubvertedSiliconRole(mindId);
- _stunSystem.TryParalyze(uid, component.StunTime, true);
+ _stunSystem.TryUpdateParalyzeDuration(uid, component.StunTime);
args.Handled = true;
}
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;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Physics.Events;
-using Robust.Shared.Utility;
namespace Content.Shared.Slippery;
[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!;
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)
// 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}");
}
{
// 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
}
- ///<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);
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);
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);
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;
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
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;
--- /dev/null
+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;
// Action blockers
SubscribeLocalEvent<KnockedDownComponent, BuckleAttemptEvent>(OnBuckleAttempt);
- SubscribeLocalEvent<KnockedDownComponent, StandAttemptEvent>(OnStandUpAttempt);
+ SubscribeLocalEvent<KnockedDownComponent, StandAttemptEvent>(OnStandAttempt);
// Updating movement a friction
SubscribeLocalEvent<KnockedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshKnockedSpeed);
#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();
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;
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);
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();
}
private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobStateChangedEvent args)
{
- if (!TryComp<StatusEffectsComponent>(uid, out var status))
- {
- return;
- }
switch (args.NewMobState)
{
case MobState.Alive:
}
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:
if (_entityWhitelist.IsBlacklistPass(ent.Comp.Blacklist, args.OtherEntity))
return;
- if (!TryComp<StatusEffectsComponent>(args.OtherEntity, out var status))
- return;
-
- TryStun(args.OtherEntity, ent.Comp.Duration, true, status);
- TryKnockdown(args.OtherEntity, ent.Comp.Duration, ent.Comp.Refresh, ent.Comp.AutoStand);
+ TryUpdateStunDuration(args.OtherEntity, ent.Comp.Duration);
+ TryKnockdown(args.OtherEntity, ent.Comp.Duration, true, force: true);
}
- private void OnSlowInit(EntityUid uid, SlowedDownComponent component, ComponentInit args)
+ // TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...)
+ public bool TryAddStunDuration(EntityUid uid, TimeSpan duration)
{
- _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
+ if (!_status.TryAddStatusEffectDuration(uid, StunId, duration))
+ return false;
+
+ OnStunnedSuccessfully(uid, duration);
+ return true;
}
- private void OnSlowRemove(EntityUid uid, SlowedDownComponent component, ComponentShutdown args)
+ public bool TryUpdateStunDuration(EntityUid uid, TimeSpan? duration)
{
- component.SprintSpeedModifier = 1f;
- component.WalkSpeedModifier = 1f;
- _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
- }
+ if (!_status.TryUpdateStatusEffectDuration(uid, StunId, duration))
+ return false;
- // TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...)
+ OnStunnedSuccessfully(uid, duration);
+ return true;
+ }
- /// <summary>
- /// Stuns the entity, disallowing it from doing many interactions temporarily.
- /// </summary>
- public bool TryStun(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null)
+ private void OnStunnedSuccessfully(EntityUid uid, TimeSpan? duration)
{
- if (time <= TimeSpan.Zero)
- return false;
+ var ev = new StunnedEvent(); // todo: rename event or change how it is raised - this event is raised each time duration of stun was externally changed
+ RaiseLocalEvent(uid, ref ev);
- if (!Resolve(uid, ref status, false))
- return false;
+ var timeForLogs = duration.HasValue
+ ? duration.Value.Seconds.ToString()
+ : "Infinite";
+ _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} stunned for {timeForLogs} seconds");
+ }
- if (!_statusEffect.TryAddStatusEffect<StunnedComponent>(uid, "Stun", time, refresh))
+ public bool TryAddKnockdownDuration(EntityUid uid, TimeSpan duration)
+ {
+ if (!_status.TryAddStatusEffectDuration(uid, KnockdownId, duration))
return false;
- var ev = new StunnedEvent();
- RaiseLocalEvent(uid, ref ev);
+ TryKnockdown(uid, duration, true, force: true);
- _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} stunned for {time.Seconds} seconds");
return true;
+
+ }
+
+ public bool TryUpdateKnockdownDuration(EntityUid uid, TimeSpan? duration)
+ {
+ if (!_status.TryUpdateStatusEffectDuration(uid, KnockdownId, duration))
+ return false;
+
+ return TryKnockdown(uid, duration, true, force: true);
}
/// <summary>
/// Knocks down the entity, making it fall to the ground.
/// </summary>
- public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh, bool autoStand = true, bool drop = true)
+ public bool TryKnockdown(Entity<StandingStateComponent?> entity, TimeSpan? time, bool refresh, bool autoStand = true, bool drop = true, bool force = false)
{
if (time <= TimeSpan.Zero)
return false;
// Can't fall down if you can't actually be downed.
- if (!HasComp<StandingStateComponent>(uid))
+ if (!Resolve(entity, ref entity.Comp, false))
return false;
- var evAttempt = new KnockDownAttemptEvent(autoStand, drop);
- RaiseLocalEvent(uid, ref evAttempt);
+ if (!force)
+ {
+ var evAttempt = new KnockDownAttemptEvent(autoStand, drop);
+ RaiseLocalEvent(entity, ref evAttempt);
- if (evAttempt.Cancelled)
- return false;
+ if (evAttempt.Cancelled)
+ return false;
+ autoStand = evAttempt.AutoStand;
+ drop = evAttempt.Drop;
+ }
+
+ Knockdown(entity!, time, autoStand, drop);
+
+ return true;
+ }
+
+ private void Knockdown(Entity<StandingStateComponent> entity, TimeSpan? time, bool refresh, bool autoStand = true, bool drop = true)
+ {
// Initialize our component with the relevant data we need if we don't have it
- if (EnsureComp<KnockedDownComponent>(uid, out var component))
+ if (EnsureComp<KnockedDownComponent>(entity, out var component))
{
- RefreshKnockedMovement((uid, component));
- CancelKnockdownDoAfter((uid, component));
+ RefreshKnockedMovement((entity, component));
+ CancelKnockdownDoAfter((entity, component));
}
else
{
if (drop)
{
var ev = new DropHandItemsEvent();
- RaiseLocalEvent(uid, ref ev);
+ RaiseLocalEvent(entity, ref ev);
}
// Only update Autostand value if it's our first time being knocked down...
- SetAutoStand((uid, component), evAttempt.AutoStand);
+ SetAutoStand((entity, component), autoStand);
}
var knockedEv = new KnockedDownEvent(time);
- RaiseLocalEvent(uid, ref knockedEv);
-
- UpdateKnockdownTime((uid, component), knockedEv.Time, refresh);
+ RaiseLocalEvent(entity, ref knockedEv);
- Alerts.ShowAlert(uid, KnockdownAlert, null, (GameTiming.CurTime, component.NextUpdate));
-
- _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} knocked down for {time.Seconds} seconds");
+ if (time != null)
+ {
+ UpdateKnockdownTime((entity, component), time.Value, refresh);
+ _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(entity):user} knocked down for {time.Value.Seconds} seconds");
+ }
+ else
+ _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(entity):user} knocked down for an indefinite amount of time");
- return true;
+ Alerts.ShowAlert(entity, KnockdownAlert, null, (GameTiming.CurTime, component.NextUpdate));
}
- /// <summary>
- /// Applies knockdown and stun to the entity temporarily.
- /// </summary>
- public bool TryParalyze(EntityUid uid, TimeSpan time, bool refresh,
- StatusEffectsComponent? status = null)
+ public bool TryAddParalyzeDuration(EntityUid uid, TimeSpan duration)
{
- if (!Resolve(uid, ref status, false))
- return false;
+ var knockdown = TryAddKnockdownDuration(uid, duration);
+ var stunned = TryAddStunDuration(uid, duration);
- return TryKnockdown(uid, time, refresh) && TryStun(uid, time, refresh, status);
+ return knockdown || stunned;
}
- /// <summary>
- /// Slows down the mob's walking/running speed temporarily
- /// </summary>
- public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh,
- float walkSpeedMod = 1f, float sprintSpeedMod = 1f,
- StatusEffectsComponent? status = null)
+ public bool TryUpdateParalyzeDuration(EntityUid uid, TimeSpan? duration)
{
- if (!Resolve(uid, ref status, false))
- return false;
-
- if (time <= TimeSpan.Zero)
- return false;
+ var knockdown = TryUpdateKnockdownDuration(uid, duration);
+ var stunned = TryUpdateStunDuration(uid, duration);
- if (_statusEffect.TryAddStatusEffect<SlowedDownComponent>(uid, "SlowedDown", time, refresh, status))
- {
- var slowed = Comp<SlowedDownComponent>(uid);
- // Doesn't make much sense to have the "TrySlowdown" method speed up entities now does it?
- walkSpeedMod = Math.Clamp(walkSpeedMod, 0f, 1f);
- sprintSpeedMod = Math.Clamp(sprintSpeedMod, 0f, 1f);
-
- slowed.WalkSpeedModifier *= walkSpeedMod;
- slowed.SprintSpeedModifier *= sprintSpeedMod;
+ return knockdown || stunned;
+ }
- _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
+ public bool TryUnstun(Entity<StunnedComponent?> entity)
+ {
+ if (!Resolve(entity, ref entity.Comp, logMissing: false))
return true;
- }
- return false;
+ var ev = new StunEndAttemptEvent();
+ RaiseLocalEvent(entity, ref ev);
+
+ return !ev.Cancelled && RemComp<StunnedComponent>(entity);
}
- /// <summary>
- /// Updates the movement speed modifiers of an entity by applying or removing the <see cref="SlowedDownComponent"/>.
- /// If both walk and run modifiers are approximately 1 (i.e. normal speed) and <see cref="StaminaComponent.StaminaDamage"/> is 0,
- /// or if the both modifiers are 0, the slowdown component is removed to restore normal movement.
- /// Otherwise, the slowdown component is created or updated with the provided modifiers,
- /// and the movement speed is refreshed accordingly.
- /// </summary>
- /// <param name="ent">Entity whose movement speed should be updated.</param>
- /// <param name="walkSpeedModifier">New walk speed modifier. Default is 1f (normal speed).</param>
- /// <param name="runSpeedModifier">New run (sprint) speed modifier. Default is 1f (normal speed).</param>
- public void UpdateStunModifiers(Entity<StaminaComponent?> ent,
- float walkSpeedModifier = 1f,
- float runSpeedModifier = 1f)
+ private void OnStunEffectApplied(Entity<StunnedStatusEffectComponent> entity, ref StatusEffectAppliedEvent args)
{
- if (!Resolve(ent, ref ent.Comp))
- return;
-
- if (
- (MathHelper.CloseTo(walkSpeedModifier, 1f) && MathHelper.CloseTo(runSpeedModifier, 1f) && ent.Comp.StaminaDamage == 0f) ||
- (walkSpeedModifier == 0f && runSpeedModifier == 0f)
- )
- {
- RemComp<SlowedDownComponent>(ent);
+ if (GameTiming.ApplyingState)
return;
- }
-
- EnsureComp<SlowedDownComponent>(ent, out var comp);
-
- comp.WalkSpeedModifier = walkSpeedModifier;
-
- comp.SprintSpeedModifier = runSpeedModifier;
-
- _movementSpeedModifier.RefreshMovementSpeedModifiers(ent);
- Dirty(ent);
+ EnsureComp<StunnedComponent>(args.Target);
}
- /// <summary>
- /// A convenience overload of <see cref="UpdateStunModifiers(EntityUid, float, float, StaminaComponent?)"/> that sets both
- /// walk and run speed modifiers to the same value.
- /// </summary>
- /// <param name="ent">Entity whose movement speed should be updated.</param>
- /// <param name="speedModifier">New walk and run speed modifier. Default is 1f (normal speed).</param>
- /// <param name="component">
- /// Optional <see cref="StaminaComponent"/> of the entity.
- /// </param>
- public void UpdateStunModifiers(Entity<StaminaComponent?> ent, float speedModifier = 1f)
+ private void OnStunStatusRemoved(Entity<StunnedStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
{
- UpdateStunModifiers(ent, speedModifier, speedModifier);
+ TryUnstun(args.Target);
}
- #region friction and movement listeners
-
- private void OnRefreshMovespeed(EntityUid ent, SlowedDownComponent comp, RefreshMovementSpeedModifiersEvent args)
+ private void OnStunEndAttempt(Entity<StunnedStatusEffectComponent> entity, ref StatusEffectRelayedEvent<StunEndAttemptEvent> args)
{
- args.ModifySpeed(comp.WalkSpeedModifier, comp.SprintSpeedModifier);
+ if (args.Args.Cancelled)
+ return;
+
+ var ev = args.Args;
+ ev.Cancelled = true;
+ args.Args = ev;
}
- #endregion
+ private void OnStandUpAttempt(Entity<KnockdownStatusEffectComponent> entity, ref StatusEffectRelayedEvent<StandUpAttemptEvent> args)
+ {
+ if (args.Args.Cancelled)
+ return;
+
+ var ev = args.Args;
+ ev.Cancelled = true;
+ args.Args = ev;
+ }
#region Attempt Event Handling
+++ /dev/null
-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;
-}
[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.
/// 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
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;
--- /dev/null
+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;
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;
- type: Body
- type: StatusEffects
allowed:
- - Stun
- - SlowedDown
- Flashed
- type: TypingIndicator
proto: robot
group: GenericNumber
- type: StatusEffects
allowed:
- - SlowedDown
- Stutter
- - Stun
- Electrocution
- TemporaryBlindness
- RadiationProtection
bodyType: KinematicController # Same for all inheritors
- type: StatusEffects
allowed:
- - Stun
- - SlowedDown
- Stutter
- Electrocution
- type: Pullable
chemicalMaxVolume: 100
- type: StatusEffects
allowed:
- - SlowedDown
- Electrocution
- type: MeleeWeapon
soundHit:
- FootstepSound
- CannotSuicide
- DoorBumpOpener
+ - StunImmune
- type: NoSlip
- type: ZombieImmune
- type: ExaminableSolution
BaseUnshaded: dead_glow
- type: StatusEffects
allowed:
- - Stun
- Corporeal
- Electrocution
- StaminaModifier
- type: Tag
tags:
- FootstepSound
+ - SlowImmune
- type: Destructible
thresholds:
- trigger:
- state: active
- type: StatusEffects
allowed:
- - Stun
- Corporeal
- type: Damageable
damageContainer: ManifestedSpirit
- type: Reactive
groups:
Acidic: [Touch]
+ - type: Tag
+ tags:
+ - SlowImmune
+ - KnockdownImmune
bodyType: KinematicController # Same for all inheritors
- type: StatusEffects
allowed:
- - Stun
- - SlowedDown
- Stutter
- Electrocution
- type: Repairable
baseSprintSpeed : 4
- type: StatusEffects
allowed:
- - SlowedDown
- Stutter
- Electrocution
- TemporaryBlindness
- type: Tag
tags:
- DoorBumpOpener
+ - StunImmune
- type: entity
abstract: true
baseDecayRate: 0.04
- type: StatusEffects
allowed:
- - Stun
- - SlowedDown
- Stutter
- Electrocution
- TemporaryBlindness
types: {}
- type: StatusEffects # Overwriting basesimplemob to remove flash, getting flashed as dragon just feelsbad
allowed:
- - SlowedDown
- Stutter
- Electrocution
- TemporaryBlindness
tags:
- CannotSuicide
- DoorBumpOpener
+ - StunImmune
- type: Puller
needsHands: false
- type: RandomMetadata
- !type:WashCreamPieReaction
- type: StatusEffects
allowed:
- - Stun
- - Friction
- - SlowedDown
- Stutter
- Electrocution
- Drunk
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
name: forced sleep
components:
- type: ForcedSleepingStatusEffect
+ - type: StunnedStatusEffect
+ - type: KnockdownStatusEffect
# This creature is asleep because it's disconnected from the game.
- type: entity
name: forced sleep
components:
- type: ForcedSleepingStatusEffect
+ - type: StunnedStatusEffect
+ - type: KnockdownStatusEffect
# Blurs your vision and makes you randomly fall asleep
- type: entity
components:
- type: DrowsinessStatusEffect
-# Makes you more slippery, or perhaps less slippery.
-- type: entity
- parent: MobStatusEffectBase
- id: StatusEffectFriction
- name: friction
- components:
- - type: FrictionStatusEffect
-
# Adds drugs overlay
- type: entity
parent: MobStatusEffectBase
--- /dev/null
+- 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
- type: Tag
id: Knife
+- type: Tag
+ id: KnockdownImmune
+
- type: Tag
id: LavaBrig
- type: Tag
id: Slice # sliced fruit, vegetables, pizza etc.
+- type: Tag
+ id: SlowImmune
+
- type: Tag
id: SmallAIChip
- type: Tag
id: StringInstrument
+- type: Tag
+ id: StunImmune
+
- type: Tag
id: SubdermalImplant