diff --git a/Content.Server/Stunnable/Components/StunbatonComponent.cs b/Content.Server/Stunnable/Components/StunbatonComponent.cs index a1a62d78f3..631af4b0d8 100644 --- a/Content.Server/Stunnable/Components/StunbatonComponent.cs +++ b/Content.Server/Stunnable/Components/StunbatonComponent.cs @@ -1,27 +1,27 @@ using Content.Shared.Sound; +using Content.Shared.Timing; namespace Content.Server.Stunnable.Components { - [RegisterComponent] + [RegisterComponent, Access(typeof(StunbatonSystem))] public sealed class StunbatonComponent : Component { public bool Activated = false; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("paralyzeChanceNoSlowdown")] - public float ParalyzeChanceNoSlowdown { get; set; } = 0.35f; + /// + /// What the is when the stun baton is active. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("activeCooldown")] + public TimeSpan ActiveDelay = TimeSpan.FromSeconds(4); - [ViewVariables(VVAccess.ReadWrite)] - [DataField("paralyzeChanceWithSlowdown")] - public float ParalyzeChanceWithSlowdown { get; set; } = 0.85f; + /// + /// Store what the was before being activated. + /// + public TimeSpan? OldDelay; [ViewVariables(VVAccess.ReadWrite)] [DataField("paralyzeTime")] - public float ParalyzeTime { get; set; } = 10f; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("slowdownTime")] - public float SlowdownTime { get; set; } = 5f; + public float ParalyzeTime { get; set; } = 5f; [ViewVariables(VVAccess.ReadWrite)] [DataField("energyPerUse")] diff --git a/Content.Server/Stunnable/StunbatonSystem.cs b/Content.Server/Stunnable/StunbatonSystem.cs index 016748c99e..60f4d30630 100644 --- a/Content.Server/Stunnable/StunbatonSystem.cs +++ b/Content.Server/Stunnable/StunbatonSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Power.Events; using Content.Server.Speech.EntitySystems; using Content.Server.Stunnable.Components; using Content.Server.Weapon.Melee; +using Content.Server.Weapon.Melee.Components; using Content.Shared.Audio; using Content.Shared.Examine; using Content.Shared.Interaction.Events; @@ -13,18 +14,23 @@ using Content.Shared.Popups; using Content.Shared.StatusEffect; using Content.Shared.Stunnable; using Content.Shared.Throwing; +using Content.Shared.Timing; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Timing; namespace Content.Server.Stunnable { public sealed class StunbatonSystem : EntitySystem { + [Dependency] private readonly MeleeWeaponSystem _melee = default!; [Dependency] private readonly StunSystem _stunSystem = default!; [Dependency] private readonly StutteringSystem _stutteringSystem = default!; [Dependency] private readonly SharedJitteringSystem _jitterSystem = default!; + [Dependency] private readonly UseDelaySystem _useDelay = default!; + [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; public override void Initialize() @@ -39,18 +45,20 @@ namespace Content.Server.Stunnable private void OnMeleeHit(EntityUid uid, StunbatonComponent comp, MeleeHitEvent args) { - if (!comp.Activated || !args.HitEntities.Any() || args.Handled) + if (!comp.Activated || !args.HitEntities.Any() || args.Handled || _useDelay.ActiveDelay(uid)) return; if (!TryComp(uid, out var battery) || !battery.TryUseCharge(comp.EnergyPerUse)) return; - foreach (EntityUid entity in args.HitEntities) + foreach (var entity in args.HitEntities) { StunEntity(entity, comp); SendPowerPulse(entity, args.User, uid); } + _melee.SetAttackCooldown(uid, _timing.CurTime + comp.ActiveDelay); + _useDelay.BeginDelay(uid); // No combat should occur if we successfully stunned. args.Handled = true; } @@ -97,25 +105,10 @@ namespace Content.Server.Stunnable { if (!EntityManager.TryGetComponent(entity, out StatusEffectsComponent? status) || !comp.Activated) return; - // TODO: Make slowdown inflicted customizable. - SoundSystem.Play(comp.StunSound.GetSound(), Filter.Pvs(comp.Owner), comp.Owner, AudioHelpers.WithVariation(0.25f)); - if (!EntityManager.HasComponent(entity)) - { - if (_robustRandom.Prob(comp.ParalyzeChanceNoSlowdown)) - _stunSystem.TryParalyze(entity, TimeSpan.FromSeconds(comp.ParalyzeTime), true, status); - else - _stunSystem.TrySlowdown(entity, TimeSpan.FromSeconds(comp.SlowdownTime), true, 0.5f, 0.5f, status); - } - else - { - if (_robustRandom.Prob(comp.ParalyzeChanceWithSlowdown)) - _stunSystem.TryParalyze(entity, TimeSpan.FromSeconds(comp.ParalyzeTime), true, status); - else - _stunSystem.TrySlowdown(entity, TimeSpan.FromSeconds(comp.SlowdownTime), true, 0.5f, 0.5f, status); - } + _stunSystem.TryParalyze(entity, TimeSpan.FromSeconds(comp.ParalyzeTime), true, status); - var slowdownTime = TimeSpan.FromSeconds(comp.SlowdownTime); + var slowdownTime = TimeSpan.FromSeconds(comp.ParalyzeTime); _jitterSystem.DoJitter(entity, slowdownTime, true, status:status); _stutteringSystem.DoStutter(entity, slowdownTime, true, status); @@ -129,32 +122,39 @@ namespace Content.Server.Stunnable private void TurnOff(StunbatonComponent comp) { if (!comp.Activated) - { return; + + // TODO stunbaton visualizer + if (TryComp(comp.Owner, out var sprite) && + TryComp(comp.Owner, out var item)) + { + item.EquippedPrefix = "off"; + sprite.LayerSetState(0, "stunbaton_off"); } - if (!EntityManager.TryGetComponent(comp.Owner, out var sprite) || - !EntityManager.TryGetComponent(comp.Owner, out var item)) return; - SoundSystem.Play(comp.SparksSound.GetSound(), Filter.Pvs(comp.Owner), comp.Owner, AudioHelpers.WithVariation(0.25f)); - item.EquippedPrefix = "off"; - // TODO stunbaton visualizer - sprite.LayerSetState(0, "stunbaton_off"); + comp.Activated = false; + if (TryComp(comp.Owner, out var useDelay) && comp.OldDelay != null) + { + useDelay.Delay = comp.OldDelay.Value; + comp.OldDelay = null; + } } private void TurnOn(StunbatonComponent comp, EntityUid user) { if (comp.Activated) - { return; + + if (EntityManager.TryGetComponent(comp.Owner, out var sprite) && + EntityManager.TryGetComponent(comp.Owner, out var item)) + { + item.EquippedPrefix = "on"; + sprite.LayerSetState(0, "stunbaton_on"); } - if (!EntityManager.TryGetComponent(comp.Owner, out var sprite) || - !EntityManager.TryGetComponent(comp.Owner, out var item)) - return; - - var playerFilter = Filter.Pvs(comp.Owner); + var playerFilter = Filter.Pvs(comp.Owner, entityManager: EntityManager); if (!TryComp(comp.Owner, out var battery) || battery.CurrentCharge < comp.EnergyPerUse) { SoundSystem.Play(comp.TurnOnFailSound.GetSound(), playerFilter, comp.Owner, AudioHelpers.WithVariation(0.25f)); @@ -164,9 +164,12 @@ namespace Content.Server.Stunnable SoundSystem.Play(comp.SparksSound.GetSound(), playerFilter, comp.Owner, AudioHelpers.WithVariation(0.25f)); - item.EquippedPrefix = "on"; - sprite.LayerSetState(0, "stunbaton_on"); comp.Activated = true; + if (TryComp(comp.Owner, out var useDelay)) + { + comp.OldDelay = useDelay.Delay; + useDelay.Delay = comp.ActiveDelay; + } } private void SendPowerPulse(EntityUid target, EntityUid? user, EntityUid used) diff --git a/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs index 3a9913217d..8ee04826bf 100644 --- a/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs @@ -74,7 +74,10 @@ namespace Content.Server.Weapon.Melee args.Handled = true; var curTime = _gameTiming.CurTime; - if (curTime < comp.CooldownEnd || args.Target == null || args.Target == owner) + if (curTime < comp.CooldownEnd || + args.Target == null || + args.Target == owner || + args.User == args.Target) return; var location = Transform(args.User).Coordinates; @@ -117,9 +120,21 @@ namespace Content.Server.Weapon.Melee } comp.LastAttackTime = curTime; - comp.CooldownEnd = comp.LastAttackTime + TimeSpan.FromSeconds(comp.CooldownTime); + SetAttackCooldown(owner, comp.LastAttackTime + TimeSpan.FromSeconds(comp.CooldownTime), comp); - RaiseLocalEvent(owner, new RefreshItemCooldownEvent(comp.LastAttackTime, comp.CooldownEnd), false); + RaiseLocalEvent(owner, new RefreshItemCooldownEvent(comp.LastAttackTime, comp.CooldownEnd)); + } + + /// + /// Set the melee weapon cooldown's end to the specified value. Will use the maximum of the existing cooldown or the new one. + /// + public void SetAttackCooldown(EntityUid uid, TimeSpan endTime, MeleeWeaponComponent? component = null) + { + // Some other system may want to artificially inflate melee weapon CD. + if (!Resolve(uid, ref component) || component.CooldownEnd > endTime) return; + + component.CooldownEnd = endTime; + RaiseLocalEvent(uid, new RefreshItemCooldownEvent(component.LastAttackTime, component.CooldownEnd)); } private void OnWideAttack(EntityUid owner, MeleeWeaponComponent comp, WideAttackEvent args) diff --git a/Content.Shared/Timing/UseDelaySystem.cs b/Content.Shared/Timing/UseDelaySystem.cs index e27dcfbcd6..974c245a0c 100644 --- a/Content.Shared/Timing/UseDelaySystem.cs +++ b/Content.Shared/Timing/UseDelaySystem.cs @@ -116,6 +116,11 @@ public sealed class UseDelaySystem : EntitySystem cooldown.CooldownEnd = component.DelayEndTime; } + public bool ActiveDelay(EntityUid uid, UseDelayComponent? component = null) + { + return Resolve(uid, ref component, false) && component.ActiveDelay; + } + public void Cancel(UseDelayComponent component) { component.CancellationTokenSource?.Cancel(); diff --git a/Resources/Prototypes/Entities/Objects/Weapons/security.yml b/Resources/Prototypes/Entities/Objects/Weapons/security.yml index 3db6c2463d..6c1b7e10a6 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/security.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/security.yml @@ -16,6 +16,8 @@ range: 1.5 arcwidth: 60 arc: default + - type: UseDelay + delay: 4 - type: Battery maxCharge: 1000 startingCharge: 1000