diff --git a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs
index e3627ce933..68925253c4 100644
--- a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs
+++ b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs
@@ -1,29 +1,26 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.UserInterface;
using Content.Client.Utility;
using Content.Shared.GameObjects.Components.Mobs;
-using Content.Shared.Input;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Player;
-using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Interfaces.GameObjects;
-using Robust.Shared.Interfaces.Network;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
-using Robust.Shared.Log;
using Robust.Shared.Maths;
-using Robust.Shared.Players;
namespace Content.Client.GameObjects.Components.Mobs
{
///
[RegisterComponent]
+ [ComponentReference(typeof(SharedStatusEffectsComponent))]
public sealed class ClientStatusEffectsComponent : SharedStatusEffectsComponent
{
#pragma warning disable 649
@@ -65,7 +62,12 @@ namespace Content.Client.GameObjects.Components.Mobs
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
- if (!(curState is StatusEffectComponentState state) || _status == state.StatusEffects) return;
+
+ if (!(curState is StatusEffectComponentState state) || _status == state.StatusEffects)
+ {
+ return;
+ }
+
_status = state.StatusEffects;
UpdateStatusEffects();
}
@@ -154,5 +156,16 @@ namespace Content.Client.GameObjects.Components.Mobs
cooldownGraphic.Visible = ratio > -1f;
}
}
+
+ public override void ChangeStatusEffect(StatusEffect effect, string icon, (TimeSpan, TimeSpan)? cooldown)
+ {
+ _status[effect] = new StatusEffectStatus()
+ {
+ Icon = icon,
+ Cooldown = cooldown
+ };
+
+ Dirty();
+ }
}
}
diff --git a/Content.Client/GameObjects/Components/Mobs/StunnableComponent.cs b/Content.Client/GameObjects/Components/Mobs/StunnableComponent.cs
index 7c01000dd4..4c02d343cb 100644
--- a/Content.Client/GameObjects/Components/Mobs/StunnableComponent.cs
+++ b/Content.Client/GameObjects/Components/Mobs/StunnableComponent.cs
@@ -1,7 +1,10 @@
#nullable enable
+using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Movement;
+using Robust.Client.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Systems;
namespace Content.Client.GameObjects.Components.Mobs
{
@@ -9,13 +12,11 @@ namespace Content.Client.GameObjects.Components.Mobs
[ComponentReference(typeof(SharedStunnableComponent))]
public class StunnableComponent : SharedStunnableComponent
{
- private bool _stunned;
- private bool _knockedDown;
- private bool _slowedDown;
-
- public override bool Stunned => _stunned;
- public override bool KnockedDown => _knockedDown;
- public override bool SlowedDown => _slowedDown;
+ protected override void OnInteractHand()
+ {
+ EntitySystem.Get()
+ .Play("/Audio/Effects/thudswoosh.ogg", Owner, AudioHelpers.WithVariation(0.25f));
+ }
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
@@ -26,9 +27,9 @@ namespace Content.Client.GameObjects.Components.Mobs
return;
}
- _stunned = state.Stunned;
- _knockedDown = state.KnockedDown;
- _slowedDown = state.SlowedDown;
+ StunnedTimer = state.StunnedTimer;
+ KnockdownTimer = state.KnockdownTimer;
+ SlowdownTimer = state.SlowdownTimer;
WalkModifierOverride = state.WalkModifierOverride;
RunModifierOverride = state.RunModifierOverride;
diff --git a/Content.Client/GameObjects/Components/Movement/SlipperyComponent.cs b/Content.Client/GameObjects/Components/Movement/SlipperyComponent.cs
new file mode 100644
index 0000000000..071aa459f9
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Movement/SlipperyComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.GameObjects.Components.Movement;
+using Robust.Shared.GameObjects;
+
+namespace Content.Client.GameObjects.Components.Movement
+{
+ [RegisterComponent]
+ [ComponentReference(typeof(SharedSlipperyComponent))]
+ public class SlipperyComponent : SharedSlipperyComponent
+ {
+ }
+}
diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs
index eb5d480730..3b6f8782cb 100644
--- a/Content.Client/IgnoredComponents.cs
+++ b/Content.Client/IgnoredComponents.cs
@@ -128,7 +128,6 @@
"StressTestMovement",
"Toys",
"SurgeryTool",
- "Slippery",
"EmitSoundOnThrow",
"Flash",
"DamageOnToolInteract",
diff --git a/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs b/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs
index ec1df766a1..9c5792fa47 100644
--- a/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs
+++ b/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Buckle;
using Content.Shared.GameObjects.Components.Mobs;
-using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Players;
@@ -47,10 +46,11 @@ namespace Content.Server.GameObjects.Components.Mobs
Dirty();
}
- public void ChangeStatusEffect(StatusEffect effect, string icon, ValueTuple? cooldown)
+ public override void ChangeStatusEffect(StatusEffect effect, string icon, ValueTuple? cooldown)
{
_statusEffects[effect] = new StatusEffectStatus()
{Icon = icon, Cooldown = cooldown};
+
Dirty();
}
diff --git a/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs b/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs
index 9fcda39b42..16000c292b 100644
--- a/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs
+++ b/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs
@@ -1,248 +1,95 @@
-using System;
-using System.Threading;
-using Content.Server.Mobs;
-using Content.Shared.Audio;
+using Content.Server.Mobs;
using Content.Shared.GameObjects.Components.Mobs;
-using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
-using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
-using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Timer = Robust.Shared.Timers.Timer;
using Content.Shared.GameObjects.Components.Movement;
-using Content.Shared.Interfaces.GameObjects.Components;
-using Math = CannyFastMath.Math;
-using MathF = CannyFastMath.MathF;
namespace Content.Server.GameObjects.Components.Mobs
{
[RegisterComponent]
[ComponentReference(typeof(SharedStunnableComponent))]
- public class StunnableComponent : SharedStunnableComponent, IInteractHand
+ public class StunnableComponent : SharedStunnableComponent
{
#pragma warning disable 649
[Dependency] private IGameTiming _gameTiming;
#pragma warning restore 649
- private TimeSpan? _lastStun;
-
- [ViewVariables] public TimeSpan? StunStart => _lastStun;
-
- [ViewVariables]
- public TimeSpan? StunEnd => _lastStun == null
- ? (TimeSpan?) null
- : _gameTiming.CurTime +
- (TimeSpan.FromSeconds(Math.Max(_stunnedTimer, Math.Max(_knockdownTimer, _slowdownTimer))));
-
- private const int StunLevels = 8;
-
- private bool _canHelp = true;
- private float _stunCap = 20f;
- private float _knockdownCap = 20f;
- private float _slowdownCap = 20f;
- private float _helpKnockdownRemove = 1f;
- private float _helpInterval = 1f;
-
- private float _stunnedTimer = 0f;
- private float _knockdownTimer = 0f;
- private float _slowdownTimer = 0f;
-
- private string _stunTexture;
- private CancellationTokenSource _statusRemoveCancellation = new CancellationTokenSource();
-
- [ViewVariables] public override bool Stunned => _stunnedTimer > 0f;
- [ViewVariables] public override bool KnockedDown => _knockdownTimer > 0f;
- [ViewVariables] public override bool SlowedDown => _slowdownTimer > 0f;
- [ViewVariables] public float StunCap => _stunCap;
- [ViewVariables] public float KnockdownCap => _knockdownCap;
- [ViewVariables] public float SlowdownCap => _slowdownCap;
-
- public override void ExposeData(ObjectSerializer serializer)
+ protected override void OnKnockdown()
{
- base.ExposeData(serializer);
- serializer.DataField(ref _stunCap, "stunCap", 20f);
- serializer.DataField(ref _knockdownCap, "knockdownCap", 20f);
- serializer.DataField(ref _slowdownCap, "slowdownCap", 20f);
- serializer.DataField(ref _helpInterval, "helpInterval", 1f);
- serializer.DataField(ref _helpKnockdownRemove, "helpKnockdownRemove", 1f);
- serializer.DataField(ref _stunTexture, "stunTexture",
- "/Textures/Objects/Weapons/Melee/stunbaton.rsi/stunbaton_off.png");
- }
-
- ///
- /// Stuns the entity, disallowing it from doing many interactions temporarily.
- ///
- /// How many seconds the mob will stay stunned
- public void Stun(float seconds)
- {
- seconds = MathF.Min(_stunnedTimer + (seconds * StunTimeModifier), _stunCap);
-
- if (seconds <= 0f)
- return;
-
- StandingStateHelper.DropAllItemsInHands(Owner, false);
-
- _stunnedTimer = seconds;
- _lastStun = _gameTiming.CurTime;
-
- SetStatusEffect();
- Dirty();
- }
-
- ///
- /// Knocks down the mob, making it fall to the ground.
- ///
- /// How many seconds the mob will stay on the ground
- public void Knockdown(float seconds)
- {
- seconds = MathF.Min(_knockdownTimer + (seconds * KnockdownTimeModifier), _knockdownCap);
-
- if (seconds <= 0f)
- {
- return;
- }
-
StandingStateHelper.Down(Owner);
-
- _knockdownTimer = seconds;
- _lastStun = _gameTiming.CurTime;
-
- SetStatusEffect();
- Dirty();
}
- ///
- /// Applies knockdown and stun to the mob temporarily
- ///
- /// How many seconds the mob will be paralyzed
- public void Paralyze(float seconds)
- {
- Stun(seconds);
- Knockdown(seconds);
- }
-
- ///
- /// Slows down the mob's walking/running speed temporarily
- ///
- /// How many seconds the mob will be slowed down
- /// Walk speed modifier. Set to 0 or negative for default value. (0.5f)
- /// Run speed modifier. Set to 0 or negative for default value. (0.5f)
- public void Slowdown(float seconds, float walkModifierOverride = 0f, float runModifierOverride = 0f)
- {
- seconds = MathF.Min(_slowdownTimer + (seconds * SlowdownTimeModifier), _slowdownCap);
-
- if (seconds <= 0f)
- return;
-
- WalkModifierOverride = walkModifierOverride;
- RunModifierOverride = runModifierOverride;
-
- _slowdownTimer = seconds;
- _lastStun = _gameTiming.CurTime;
-
- if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
- movement.RefreshMovementSpeedModifiers();
-
- SetStatusEffect();
- Dirty();
- }
-
- ///
- /// Used when
- ///
public void CancelAll()
{
- _knockdownTimer = 0f;
- _stunnedTimer = 0f;
+ KnockdownTimer = 0f;
+ StunnedTimer = 0f;
Dirty();
}
- public bool InteractHand(InteractHandEventArgs eventArgs)
- {
- if (!_canHelp || !KnockedDown)
- return false;
-
- _canHelp = false;
- Timer.Spawn(((int) _helpInterval * 1000), () => _canHelp = true);
-
- EntitySystem.Get()
- .PlayFromEntity("/Audio/Effects/thudswoosh.ogg", Owner, AudioHelpers.WithVariation(0.25f));
-
- _knockdownTimer -= _helpKnockdownRemove;
-
- SetStatusEffect();
-
- Dirty();
- return true;
- }
-
- private void SetStatusEffect()
- {
- if (!Owner.TryGetComponent(out ServerStatusEffectsComponent status))
- return;
-
- status.ChangeStatusEffect(StatusEffect.Stun, _stunTexture,
- (StunStart == null || StunEnd == null) ? default : (StunStart.Value, StunEnd.Value));
- _statusRemoveCancellation.Cancel();
- _statusRemoveCancellation = new CancellationTokenSource();
- }
-
public void ResetStuns()
{
- _stunnedTimer = 0f;
- _slowdownTimer = 0f;
+ StunnedTimer = 0f;
+ SlowdownTimer = 0f;
if (KnockedDown)
+ {
StandingStateHelper.Standing(Owner);
+ }
- _knockdownTimer = 0f;
+ KnockdownTimer = 0f;
}
public void Update(float delta)
{
if (Stunned)
{
- _stunnedTimer -= delta;
+ StunnedTimer -= delta;
- if (_stunnedTimer <= 0)
+ if (StunnedTimer <= 0)
{
- _stunnedTimer = 0f;
+ StunnedTimer = 0f;
Dirty();
}
}
if (KnockedDown)
{
- _knockdownTimer -= delta;
+ KnockdownTimer -= delta;
- if (_knockdownTimer <= 0f)
+ if (KnockdownTimer <= 0f)
{
StandingStateHelper.Standing(Owner);
- _knockdownTimer = 0f;
+ KnockdownTimer = 0f;
Dirty();
}
}
if (SlowedDown)
{
- _slowdownTimer -= delta;
+ SlowdownTimer -= delta;
- if (_slowdownTimer <= 0f)
+ if (SlowdownTimer <= 0f)
{
- _slowdownTimer = 0f;
+ SlowdownTimer = 0f;
if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
+ {
movement.RefreshMovementSpeedModifiers();
+ }
+
Dirty();
}
}
if (!StunStart.HasValue || !StunEnd.HasValue ||
!Owner.TryGetComponent(out ServerStatusEffectsComponent status))
+ {
return;
+ }
var start = StunStart.Value;
var end = StunEnd.Value;
@@ -252,73 +99,15 @@ namespace Content.Server.GameObjects.Components.Mobs
if (progress >= length)
{
- Timer.Spawn(250, () => status.RemoveStatusEffect(StatusEffect.Stun), _statusRemoveCancellation.Token);
- _lastStun = null;
- }
- }
-
- public float StunTimeModifier
- {
- get
- {
- var modifier = 1.0f;
- var components = Owner.GetAllComponents();
-
- foreach (var component in components)
- {
- modifier *= component.StunTimeModifier;
- }
-
- return modifier;
- }
- }
-
- public float KnockdownTimeModifier
- {
- get
- {
- var modifier = 1.0f;
- var components = Owner.GetAllComponents();
-
- foreach (var component in components)
- {
- modifier *= component.KnockdownTimeModifier;
- }
-
- return modifier;
- }
- }
-
- public float SlowdownTimeModifier
- {
- get
- {
- var modifier = 1.0f;
- var components = Owner.GetAllComponents();
-
- foreach (var component in components)
- {
- modifier *= component.SlowdownTimeModifier;
- }
-
- return modifier;
+ Timer.Spawn(250, () => status.RemoveStatusEffect(StatusEffect.Stun), StatusRemoveCancellation.Token);
+ LastStun = null;
}
}
public override ComponentState GetComponentState()
{
- return new StunnableComponentState(Stunned, KnockedDown, SlowedDown, WalkModifierOverride,
+ return new StunnableComponentState(StunnedTimer, KnockdownTimer, SlowdownTimer, WalkModifierOverride,
RunModifierOverride);
}
}
-
- ///
- /// This interface allows components to multiply the time in seconds of various stuns by a number.
- ///
- public interface IStunModifier
- {
- float StunTimeModifier => 1.0f;
- float KnockdownTimeModifier => 1.0f;
- float SlowdownTimeModifier => 1.0f;
- }
}
diff --git a/Content.Server/GameObjects/Components/Movement/SlipperyComponent.cs b/Content.Server/GameObjects/Components/Movement/SlipperyComponent.cs
index bc9fc1b33b..8a1247d4d9 100644
--- a/Content.Server/GameObjects/Components/Movement/SlipperyComponent.cs
+++ b/Content.Server/GameObjects/Components/Movement/SlipperyComponent.cs
@@ -1,118 +1,36 @@
-using System.Collections.Generic;
-using System.Timers;
-using Content.Server.GameObjects.Components.Mobs;
-using Content.Server.Throw;
-using Content.Shared.Audio;
-using Content.Shared.GameObjects.EntitySystems;
-using Content.Shared.Physics;
+using Content.Shared.Audio;
+using Content.Shared.GameObjects.Components.Movement;
using Robust.Server.GameObjects.EntitySystems;
-using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
-using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
-using Robust.Shared.Interfaces.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Log;
-using Robust.Shared.Map;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
-using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameObjects.Components.Movement
{
[RegisterComponent]
- public class SlipperyComponent : Component, ICollideBehavior
+ [ComponentReference(typeof(SharedSlipperyComponent))]
+ public class SlipperyComponent : SharedSlipperyComponent
{
- [Dependency] private IEntityManager _entityManager = default!;
-
- public override string Name => "Slippery";
-
- private List _slipped = new List();
-
- ///
- /// How many seconds the mob will be paralyzed for.
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- public float ParalyzeTime { get; set; } = 3f;
-
- ///
- /// Percentage of shape intersection for a slip to occur.
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- public float IntersectPercentage { get; set; } = 0.3f;
-
- ///
- /// Entities will only be slipped if their speed exceeds this limit.
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- public float RequiredSlipSpeed { get; set; } = 0f;
-
///
/// Path to the sound to be played when a mob slips.
///
[ViewVariables]
- public string SlipSound { get; set; } = "/Audio/Effects/slip.ogg";
-
- public override void Initialize()
- {
- base.Initialize();
- var collidable = Owner.GetComponent();
-
- collidable.Hard = false;
- var shape = collidable.PhysicsShapes[0];
- shape.CollisionLayer |= (int) CollisionGroup.SmallImpassable;
- shape.CollisionMask = (int)CollisionGroup.None;
- }
+ private string SlipSound { get; set; } = "/Audio/Effects/slip.ogg";
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
- serializer.DataField(this, x => ParalyzeTime, "paralyzeTime", 3f);
- serializer.DataField(this, x => IntersectPercentage, "intersectPercentage", 0.3f);
- serializer.DataField(this, x => RequiredSlipSpeed, "requiredSlipSpeed", 0f);
+
serializer.DataField(this, x => SlipSound, "slipSound", "/Audio/Effects/slip.ogg");
}
- public void CollideWith(IEntity collidedWith)
+ protected override void OnSlip()
{
- if (ContainerHelpers.IsInContainer(Owner)
- || _slipped.Contains(collidedWith.Uid)
- || !collidedWith.TryGetComponent(out StunnableComponent stun)
- || !collidedWith.TryGetComponent(out ICollidableComponent otherBody)
- || !collidedWith.TryGetComponent(out IPhysicsComponent otherPhysics)
- || !Owner.TryGetComponent(out ICollidableComponent body))
- return;
-
- if (otherPhysics.LinearVelocity.Length < RequiredSlipSpeed || stun.KnockedDown)
- return;
-
- var percentage = otherBody.WorldAABB.IntersectPercentage(body.WorldAABB);
-
- if (percentage < IntersectPercentage)
- return;
-
- if(!EffectBlockerSystem.CanSlip(collidedWith))
- return;
-
- stun.Paralyze(5f);
- _slipped.Add(collidedWith.Uid);
-
- if(!string.IsNullOrEmpty(SlipSound))
- EntitySystem.Get().PlayFromEntity(SlipSound, Owner, AudioHelpers.WithVariation(0.2f));
- }
-
- public void Update(float frameTime)
- {
- foreach (var uid in _slipped.ToArray())
+ if (!string.IsNullOrEmpty(SlipSound))
{
- if(!uid.IsValid() || !_entityManager.EntityExists(uid)) continue;
-
- var entity = _entityManager.GetEntity(uid);
- var collidable = Owner.GetComponent();
- var otherCollidable = entity.GetComponent();
-
- if (!collidable.WorldAABB.Intersects(otherCollidable.WorldAABB))
- _slipped.Remove(uid);
+ EntitySystem.Get()
+ .PlayFromEntity(SlipSound, Owner, AudioHelpers.WithVariation(0.2f));
}
}
}
diff --git a/Content.Server/Mobs/StandingStateHelper.cs b/Content.Server/Mobs/StandingStateHelper.cs
index ac912a4546..b70c971a66 100644
--- a/Content.Server/Mobs/StandingStateHelper.cs
+++ b/Content.Server/Mobs/StandingStateHelper.cs
@@ -1,4 +1,3 @@
-using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Mobs;
diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs
index 6991ac8060..95618e04b4 100644
--- a/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs
+++ b/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs
@@ -13,6 +13,8 @@ namespace Content.Shared.GameObjects.Components.Mobs
{
public override string Name => "StatusEffectsUI";
public override uint? NetID => ContentNetIDs.STATUSEFFECTS;
+
+ public abstract void ChangeStatusEffect(StatusEffect effect, string icon, ValueTuple? cooldown);
}
[Serializable, NetSerializable]
diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs
index 121b1d0e74..0c11e7393a 100644
--- a/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs
+++ b/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs
@@ -1,23 +1,243 @@
using System;
+using System.Threading;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.GameObjects.EntitySystems;
+using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
+using Robust.Shared.Interfaces.Timing;
+using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
+using Timer = Robust.Shared.Timers.Timer;
namespace Content.Shared.GameObjects.Components.Mobs
{
- public abstract class SharedStunnableComponent : Component, IMoveSpeedModifier, IActionBlocker
+ public abstract class SharedStunnableComponent : Component, IMoveSpeedModifier, IActionBlocker, IInteractHand
{
+#pragma warning disable 649
+ [Dependency] private IGameTiming _gameTiming;
+#pragma warning restore 649
+
public sealed override string Name => "Stunnable";
public override uint? NetID => ContentNetIDs.STUNNABLE;
+ protected TimeSpan? LastStun;
+
+ [ViewVariables] protected TimeSpan? StunStart => LastStun;
+
+ [ViewVariables]
+ protected TimeSpan? StunEnd => LastStun == null
+ ? (TimeSpan?) null
+ : _gameTiming.CurTime +
+ (TimeSpan.FromSeconds(Math.Max(StunnedTimer, Math.Max(KnockdownTimer, SlowdownTimer))));
+
+ private const int StunLevels = 8;
+
+ private bool _canHelp = true;
+ protected float _stunCap = 20f;
+ protected float _knockdownCap = 20f;
+ protected float _slowdownCap = 20f;
+ private float _helpKnockdownRemove = 1f;
+ private float _helpInterval = 1f;
+
+ protected float StunnedTimer;
+ protected float KnockdownTimer;
+ protected float SlowdownTimer;
+
+ private string _stunTexture;
+
+ protected CancellationTokenSource StatusRemoveCancellation = new CancellationTokenSource();
+
[ViewVariables] protected float WalkModifierOverride = 0f;
[ViewVariables] protected float RunModifierOverride = 0f;
- [ViewVariables] public abstract bool Stunned { get; }
- [ViewVariables] public abstract bool KnockedDown { get; }
- [ViewVariables] public abstract bool SlowedDown { get; }
+ [ViewVariables] public bool Stunned => StunnedTimer > 0f;
+ [ViewVariables] public bool KnockedDown => KnockdownTimer > 0f;
+ [ViewVariables] public bool SlowedDown => SlowdownTimer > 0f;
+
+ private float StunTimeModifier
+ {
+ get
+ {
+ var modifier = 1.0f;
+ var components = Owner.GetAllComponents();
+
+ foreach (var component in components)
+ {
+ modifier *= component.StunTimeModifier;
+ }
+
+ return modifier;
+ }
+ }
+
+ private float KnockdownTimeModifier
+ {
+ get
+ {
+ var modifier = 1.0f;
+ var components = Owner.GetAllComponents();
+
+ foreach (var component in components)
+ {
+ modifier *= component.KnockdownTimeModifier;
+ }
+
+ return modifier;
+ }
+ }
+
+ private float SlowdownTimeModifier
+ {
+ get
+ {
+ var modifier = 1.0f;
+ var components = Owner.GetAllComponents();
+
+ foreach (var component in components)
+ {
+ modifier *= component.SlowdownTimeModifier;
+ }
+
+ return modifier;
+ }
+ }
+
+ ///
+ /// Stuns the entity, disallowing it from doing many interactions temporarily.
+ ///
+ /// How many seconds the mob will stay stunned.
+ /// Whether or not the owner was stunned.
+ public bool Stun(float seconds)
+ {
+ seconds = MathF.Min(StunnedTimer + (seconds * StunTimeModifier), _stunCap);
+
+ if (seconds <= 0f)
+ {
+ return false;
+ }
+
+ StunnedTimer = seconds;
+ LastStun = _gameTiming.CurTime;
+
+ SetStatusEffect();
+ OnStun();
+
+ Dirty();
+
+ return true;
+ }
+
+ protected virtual void OnStun() { }
+
+ ///
+ /// Knocks down the mob, making it fall to the ground.
+ ///
+ /// How many seconds the mob will stay on the ground.
+ /// Whether or not the owner was knocked down.
+ public bool Knockdown(float seconds)
+ {
+ seconds = MathF.Min(KnockdownTimer + (seconds * KnockdownTimeModifier), _knockdownCap);
+
+ if (seconds <= 0f)
+ {
+ return false;
+ }
+
+ KnockdownTimer = seconds;
+ LastStun = _gameTiming.CurTime;
+
+ SetStatusEffect();
+ OnKnockdown();
+
+ Dirty();
+
+ return true;
+ }
+
+ protected virtual void OnKnockdown() { }
+
+ ///
+ /// Applies knockdown and stun to the mob temporarily.
+ ///
+ /// How many seconds the mob will be paralyzed-
+ /// Whether or not the owner of this component was paralyzed-
+ public bool Paralyze(float seconds)
+ {
+ return Stun(seconds) && Knockdown(seconds);
+ }
+
+ ///
+ /// Slows down the mob's walking/running speed temporarily
+ ///
+ /// How many seconds the mob will be slowed down
+ /// Walk speed modifier. Set to 0 or negative for default value. (0.5f)
+ /// Run speed modifier. Set to 0 or negative for default value. (0.5f)
+ public void Slowdown(float seconds, float walkModifierOverride = 0f, float runModifierOverride = 0f)
+ {
+ seconds = MathF.Min(SlowdownTimer + (seconds * SlowdownTimeModifier), _slowdownCap);
+
+ if (seconds <= 0f)
+ return;
+
+ WalkModifierOverride = walkModifierOverride;
+ RunModifierOverride = runModifierOverride;
+
+ SlowdownTimer = seconds;
+ LastStun = _gameTiming.CurTime;
+
+ if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
+ movement.RefreshMovementSpeedModifiers();
+
+ SetStatusEffect();
+ Dirty();
+ }
+
+ private void SetStatusEffect()
+ {
+ if (!Owner.TryGetComponent(out SharedStatusEffectsComponent status))
+ {
+ return;
+ }
+
+ status.ChangeStatusEffect(StatusEffect.Stun, _stunTexture,
+ (StunStart == null || StunEnd == null) ? default : (StunStart.Value, StunEnd.Value));
+ StatusRemoveCancellation.Cancel();
+ StatusRemoveCancellation = new CancellationTokenSource();
+ }
+
+ public override void ExposeData(ObjectSerializer serializer)
+ {
+ base.ExposeData(serializer);
+
+ serializer.DataField(ref _stunCap, "stunCap", 20f);
+ serializer.DataField(ref _knockdownCap, "knockdownCap", 20f);
+ serializer.DataField(ref _slowdownCap, "slowdownCap", 20f);
+ serializer.DataField(ref _helpInterval, "helpInterval", 1f);
+ serializer.DataField(ref _helpKnockdownRemove, "helpKnockdownRemove", 1f);
+ serializer.DataField(ref _stunTexture, "stunTexture",
+ "/Textures/Objects/Weapons/Melee/stunbaton.rsi/stunbaton_off.png");
+ }
+
+ protected virtual void OnInteractHand() { }
+
+ bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
+ {
+ if (!_canHelp || !KnockedDown)
+ {
+ return false;
+ }
+
+ _canHelp = false;
+ Timer.Spawn((int) _helpInterval * 1000, () => _canHelp = true);
+
+ KnockdownTimer -= _helpKnockdownRemove;
+
+ SetStatusEffect();
+ Dirty();
+
+ return true;
+ }
#region ActionBlockers
public bool CanMove() => (!Stunned);
@@ -52,20 +272,30 @@ namespace Content.Shared.GameObjects.Components.Mobs
[Serializable, NetSerializable]
protected sealed class StunnableComponentState : ComponentState
{
- public bool Stunned { get; }
- public bool KnockedDown { get; }
- public bool SlowedDown { get; }
+ public float StunnedTimer { get; }
+ public float KnockdownTimer { get; }
+ public float SlowdownTimer { get; }
public float WalkModifierOverride { get; }
public float RunModifierOverride { get; }
- public StunnableComponentState(bool stunned, bool knockedDown, bool slowedDown, float walkModifierOverride, float runModifierOverride) : base(ContentNetIDs.STUNNABLE)
+ public StunnableComponentState(float stunnedTimer, float knockdownTimer, float slowdownTimer, float walkModifierOverride, float runModifierOverride) : base(ContentNetIDs.STUNNABLE)
{
- Stunned = stunned;
- KnockedDown = knockedDown;
- SlowedDown = slowedDown;
+ StunnedTimer = stunnedTimer;
+ KnockdownTimer = knockdownTimer;
+ SlowdownTimer = slowdownTimer;
WalkModifierOverride = walkModifierOverride;
RunModifierOverride = runModifierOverride;
}
}
}
+
+ ///
+ /// This interface allows components to multiply the time in seconds of various stuns by a number.
+ ///
+ public interface IStunModifier
+ {
+ float StunTimeModifier => 1.0f;
+ float KnockdownTimeModifier => 1.0f;
+ float SlowdownTimeModifier => 1.0f;
+ }
}
diff --git a/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs b/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs
new file mode 100644
index 0000000000..c0695cb160
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using Content.Shared.GameObjects.Components.Mobs;
+using Content.Shared.GameObjects.EntitySystems;
+using Content.Shared.Physics;
+using Robust.Shared.Containers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Serialization;
+using Robust.Shared.ViewVariables;
+
+namespace Content.Shared.GameObjects.Components.Movement
+{
+ public abstract class SharedSlipperyComponent : Component, ICollideBehavior
+ {
+#pragma warning disable 649
+ [Dependency] private readonly IEntityManager _entityManager;
+#pragma warning restore 649
+
+ public sealed override string Name => "Slippery";
+
+ ///
+ /// The list of entities that have been slipped by this component,
+ /// and which have not stopped colliding with its owner yet.
+ ///
+ protected readonly List _slipped = new List();
+
+ ///
+ /// How many seconds the mob will be paralyzed for.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ private float ParalyzeTime { get; set; } = 3f;
+
+ ///
+ /// Percentage of shape intersection for a slip to occur.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ private float IntersectPercentage { get; set; } = 0.3f;
+
+ ///
+ /// Entities will only be slipped if their speed exceeds this limit.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ private float RequiredSlipSpeed { get; set; } = 0f;
+
+ private bool TrySlip(IEntity entity)
+ {
+ if (ContainerHelpers.IsInContainer(Owner)
+ || _slipped.Contains(entity.Uid)
+ || !entity.TryGetComponent(out SharedStunnableComponent stun)
+ || !entity.TryGetComponent(out ICollidableComponent otherBody)
+ || !entity.TryGetComponent(out IPhysicsComponent otherPhysics)
+ || !Owner.TryGetComponent(out ICollidableComponent body))
+ {
+ return false;
+ }
+
+ if (otherPhysics.LinearVelocity.Length < RequiredSlipSpeed || stun.KnockedDown)
+ {
+ return false;
+ }
+
+ var percentage = otherBody.WorldAABB.IntersectPercentage(body.WorldAABB);
+
+ if (percentage < IntersectPercentage)
+ {
+ return false;
+ }
+
+ if (!EffectBlockerSystem.CanSlip(entity))
+ {
+ return false;
+ }
+
+ stun.Paralyze(5);
+ _slipped.Add(entity.Uid);
+
+ OnSlip();
+
+ return true;
+ }
+
+ protected virtual void OnSlip() { }
+
+ public void CollideWith(IEntity collidedWith)
+ {
+ TrySlip(collidedWith);
+ }
+
+ public void Update(float frameTime)
+ {
+ foreach (var uid in _slipped.ToArray())
+ {
+ if (!uid.IsValid() || !_entityManager.EntityExists(uid))
+ {
+ continue;
+ }
+
+ var entity = _entityManager.GetEntity(uid);
+ var collidable = Owner.GetComponent();
+ var otherCollidable = entity.GetComponent();
+
+ if (!collidable.WorldAABB.Intersects(otherCollidable.WorldAABB))
+ {
+ _slipped.Remove(uid);
+ }
+ }
+ }
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ var collidable = Owner.GetComponent();
+
+ collidable.Hard = false;
+ var shape = collidable.PhysicsShapes[0];
+ shape.CollisionLayer |= (int) CollisionGroup.SmallImpassable;
+ shape.CollisionMask = (int)CollisionGroup.None;
+ }
+
+ public override void ExposeData(ObjectSerializer serializer)
+ {
+ base.ExposeData(serializer);
+
+ serializer.DataField(this, x => ParalyzeTime, "paralyzeTime", 3f);
+ serializer.DataField(this, x => IntersectPercentage, "intersectPercentage", 0.3f);
+ serializer.DataField(this, x => RequiredSlipSpeed, "requiredSlipSpeed", 0f);
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/EntitySystems/SlipperySystem.cs b/Content.Shared/GameObjects/EntitySystems/SlipperySystem.cs
similarity index 56%
rename from Content.Server/GameObjects/EntitySystems/SlipperySystem.cs
rename to Content.Shared/GameObjects/EntitySystems/SlipperySystem.cs
index 064e34ad92..9a00451224 100644
--- a/Content.Server/GameObjects/EntitySystems/SlipperySystem.cs
+++ b/Content.Shared/GameObjects/EntitySystems/SlipperySystem.cs
@@ -1,16 +1,18 @@
-using Content.Server.GameObjects.Components.Movement;
+using Content.Shared.GameObjects.Components.Movement;
+using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
-namespace Content.Server.GameObjects.EntitySystems
+namespace Content.Shared.GameObjects.EntitySystems
{
+ [UsedImplicitly]
public class SlipperySystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
- EntityQuery = new TypeEntityQuery(typeof(SlipperyComponent));
+ EntityQuery = new TypeEntityQuery(typeof(SharedSlipperyComponent));
}
public override void Update(float frameTime)
@@ -19,7 +21,7 @@ namespace Content.Server.GameObjects.EntitySystems
foreach (var entity in RelevantEntities)
{
- entity.GetComponent().Update(frameTime);
+ entity.GetComponent().Update(frameTime);
}
}
}