From e06cabecbb9e47edb1e837257577673fbb13442a Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 4 Jun 2022 14:19:14 +1000 Subject: [PATCH] Gun spread overlay (#8588) --- .../Ranged/Commands/ShowSpreadCommand.cs | 18 +++++ .../Weapons/Ranged/GunSpreadOverlay.cs | 76 +++++++++++++++++++ .../Weapons/Ranged/Systems/GunSystem.cs | 29 +++++++ .../Weapon/Ranged/Systems/GunSystem.cs | 4 +- .../Weapons/Ranged/Components/GunComponent.cs | 4 +- .../Weapons/Ranged/Systems/SharedGunSystem.cs | 14 +++- Resources/clientCommandPerms.yml | 1 + 7 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 Content.Client/Weapons/Ranged/Commands/ShowSpreadCommand.cs create mode 100644 Content.Client/Weapons/Ranged/GunSpreadOverlay.cs diff --git a/Content.Client/Weapons/Ranged/Commands/ShowSpreadCommand.cs b/Content.Client/Weapons/Ranged/Commands/ShowSpreadCommand.cs new file mode 100644 index 0000000000..704bd3d642 --- /dev/null +++ b/Content.Client/Weapons/Ranged/Commands/ShowSpreadCommand.cs @@ -0,0 +1,18 @@ +using Content.Client.Weapons.Ranged.Systems; +using Robust.Shared.Console; + +namespace Content.Client.Weapons.Ranged; + +public sealed class ShowSpreadCommand : IConsoleCommand +{ + public string Command => "showspread"; + public string Description => $"Shows gun spread overlay for debugging"; + public string Help => $"{Command}"; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + var system = IoCManager.Resolve().GetEntitySystem(); + system.SpreadOverlay ^= true; + + shell.WriteLine($"Set spread overlay to {system.SpreadOverlay}"); + } +} diff --git a/Content.Client/Weapons/Ranged/GunSpreadOverlay.cs b/Content.Client/Weapons/Ranged/GunSpreadOverlay.cs new file mode 100644 index 0000000000..d1d4d72447 --- /dev/null +++ b/Content.Client/Weapons/Ranged/GunSpreadOverlay.cs @@ -0,0 +1,76 @@ +using Content.Client.Weapons.Ranged.Systems; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Map; +using Robust.Shared.Timing; + +namespace Content.Client.Weapons.Ranged; + +public sealed class GunSpreadOverlay : Overlay +{ + public override OverlaySpace Space => OverlaySpace.WorldSpace; + + private IEntityManager _entManager; + private IEyeManager _eye; + private IGameTiming _timing; + private IInputManager _input; + private IPlayerManager _player; + private GunSystem _guns; + + public GunSpreadOverlay(IEntityManager entManager, IEyeManager eyeManager, IGameTiming timing, IInputManager input, IPlayerManager player, GunSystem system) + { + _entManager = entManager; + _eye = eyeManager; + _input = input; + _timing = timing; + _player = player; + _guns = system; + } + + protected override void Draw(in OverlayDrawArgs args) + { + var worldHandle = args.WorldHandle; + + var player = _player.LocalPlayer?.ControlledEntity; + + if (player == null || + !_entManager.TryGetComponent(player, out var xform)) return; + + var mapPos = xform.MapPosition; + + if (mapPos.MapId == MapId.Nullspace) return; + + var gun = _guns.GetGun(player.Value); + + if (gun == null) return; + + var mouseScreenPos = _input.MouseScreenPosition; + var mousePos = _eye.ScreenToMap(mouseScreenPos); + + if (mapPos.MapId != mousePos.MapId) return; + + // (☞゚ヮ゚)☞ + var maxSpread = gun.MaxAngle; + var minSpread = gun.MinAngle; + var timeSinceLastFire = (_timing.CurTime - gun.NextFire).TotalSeconds; + var currentAngle = new Angle(MathHelper.Clamp(gun.CurrentAngle.Theta - gun.AngleDecay.Theta * timeSinceLastFire, + gun.MinAngle.Theta, gun.MaxAngle.Theta)); + var direction = (mousePos.Position - mapPos.Position); + + worldHandle.DrawLine(mapPos.Position, mousePos.Position + direction, Color.Orange); + + // Show max spread either side + worldHandle.DrawLine(mapPos.Position, mousePos.Position + maxSpread.RotateVec(direction), Color.Red); + worldHandle.DrawLine(mapPos.Position, mousePos.Position + (-maxSpread).RotateVec(direction), Color.Red); + + // Show min spread either side + worldHandle.DrawLine(mapPos.Position, mousePos.Position + minSpread.RotateVec(direction), Color.Green); + worldHandle.DrawLine(mapPos.Position, mousePos.Position + (-minSpread).RotateVec(direction), Color.Green); + + // Show current angle + worldHandle.DrawLine(mapPos.Position, mousePos.Position + currentAngle.RotateVec(direction), Color.Yellow); + worldHandle.DrawLine(mapPos.Position, mousePos.Position + (-currentAngle).RotateVec(direction), Color.Yellow); + } +} diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs index ba00414343..b8bd5871f9 100644 --- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs @@ -13,6 +13,7 @@ using Robust.Shared.Audio; using Robust.Shared.Input; using Robust.Shared.Map; using Robust.Shared.Player; +using Robust.Shared.Timing; using Robust.Shared.Utility; using SharedGunSystem = Content.Shared.Weapons.Ranged.Systems.SharedGunSystem; @@ -27,6 +28,34 @@ public sealed partial class GunSystem : SharedGunSystem [Dependency] private readonly EffectSystem _effects = default!; [Dependency] private readonly InputSystem _inputSystem = default!; + public bool SpreadOverlay + { + get => _spreadOverlay; + set + { + if (_spreadOverlay == value) return; + _spreadOverlay = value; + var overlayManager = IoCManager.Resolve(); + + if (_spreadOverlay) + { + overlayManager.AddOverlay(new GunSpreadOverlay( + EntityManager, + IoCManager.Resolve(), + IoCManager.Resolve(), + IoCManager.Resolve(), + IoCManager.Resolve(), + this)); + } + else + { + overlayManager.RemoveOverlay(); + } + } + } + + private bool _spreadOverlay; + public override void Initialize() { base.Initialize(); diff --git a/Content.Server/Weapon/Ranged/Systems/GunSystem.cs b/Content.Server/Weapon/Ranged/Systems/GunSystem.cs index c4bd95941f..74c27641b0 100644 --- a/Content.Server/Weapon/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapon/Ranged/Systems/GunSystem.cs @@ -196,8 +196,10 @@ public sealed partial class GunSystem : SharedGunSystem component.LastFire = component.NextFire; // Convert it so angle can go either side. - var random = Random.NextGaussian(0, 0.5); + var random = Random.NextFloat(-0.5f, 0.5f); + var spread = component.CurrentAngle.Theta * random; var angle = new Angle(direction.Theta + component.CurrentAngle.Theta * random); + DebugTools.Assert(spread <= component.MaxAngle.Theta); return angle; } diff --git a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs index 463ac00f1b..1cf442ed1f 100644 --- a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs @@ -56,13 +56,13 @@ public class GunComponent : Component /// /// The maximum angle allowed for /// - [ViewVariables, DataField("maxAngle")] + [ViewVariables(VVAccess.ReadWrite), DataField("maxAngle")] public Angle MaxAngle = Angle.FromDegrees(2); /// /// The minimum angle allowed for /// - [ViewVariables, DataField("minAngle")] + [ViewVariables(VVAccess.ReadWrite), DataField("minAngle")] public Angle MinAngle = Angle.FromDegrees(1); #endregion diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 9cdecb43d5..dc8008c507 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -120,6 +120,10 @@ public abstract partial class SharedGunSystem : EntitySystem { args.State = new GunComponentState { + FireRate = component.FireRate, + CurrentAngle = component.CurrentAngle, + MinAngle = component.MinAngle, + MaxAngle = component.MaxAngle, NextFire = component.NextFire, ShotCounter = component.ShotCounter, SelectiveFire = component.SelectedMode, @@ -132,13 +136,17 @@ public abstract partial class SharedGunSystem : EntitySystem if (args.Current is not GunComponentState state) return; Sawmill.Debug($"Handle state: setting shot count from {component.ShotCounter} to {state.ShotCounter}"); + component.FireRate = state.FireRate; + component.CurrentAngle = state.CurrentAngle; + component.MinAngle = state.MinAngle; + component.MaxAngle = state.MaxAngle; component.NextFire = state.NextFire; component.ShotCounter = state.ShotCounter; component.SelectedMode = state.SelectiveFire; component.AvailableModes = state.AvailableSelectiveFire; } - protected GunComponent? GetGun(EntityUid entity) + public GunComponent? GetGun(EntityUid entity) { if (!EntityManager.TryGetComponent(entity, out SharedHandsComponent? hands) || hands.ActiveHandEntity is not { } held) @@ -358,7 +366,11 @@ public abstract partial class SharedGunSystem : EntitySystem [Serializable, NetSerializable] protected sealed class GunComponentState : ComponentState { + public Angle CurrentAngle; + public Angle MinAngle; + public Angle MaxAngle; public TimeSpan NextFire; + public float FireRate; public int ShotCounter; public SelectiveFire SelectiveFire; public SelectiveFire AvailableSelectiveFire; diff --git a/Resources/clientCommandPerms.yml b/Resources/clientCommandPerms.yml index 859aed728f..8dcc26ed8a 100644 --- a/Resources/clientCommandPerms.yml +++ b/Resources/clientCommandPerms.yml @@ -14,6 +14,7 @@ - toggledecals - nodevis - nodevisfilter + - showspread - showambient - Flags: MAPPING