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