From 38fc066fb9898692db0572936e17c33fb1cf93ba Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 14 Jul 2022 22:01:25 +1000 Subject: [PATCH] Re-implement recoil (#9406) * Re-implement recoil Playing around with the values atm * Update constants * final tweaks --- Content.Client/Camera/CameraRecoilSystem.cs | 32 +++++++++++ .../Weapons/Ranged/Systems/GunSystem.cs | 13 +++++ Content.Server/Camera/CameraRecoilSystem.cs | 14 +++++ .../EntitySystems/ExplosionSystem.cs | 2 +- .../EntitySystems/GravityShakeSystem.cs | 4 +- .../PneumaticCannon/PneumaticCannonSystem.cs | 4 +- .../Projectiles/SharedProjectileSystem.cs | 4 +- ...lSystem.cs => SharedCameraRecoilSystem.cs} | 55 ++++--------------- 8 files changed, 76 insertions(+), 52 deletions(-) create mode 100644 Content.Client/Camera/CameraRecoilSystem.cs create mode 100644 Content.Server/Camera/CameraRecoilSystem.cs rename Content.Shared/Camera/{CameraRecoilSystem.cs => SharedCameraRecoilSystem.cs} (57%) diff --git a/Content.Client/Camera/CameraRecoilSystem.cs b/Content.Client/Camera/CameraRecoilSystem.cs new file mode 100644 index 0000000000..cb17caa357 --- /dev/null +++ b/Content.Client/Camera/CameraRecoilSystem.cs @@ -0,0 +1,32 @@ +using Content.Shared.Camera; + +namespace Content.Client.Camera; + +public sealed class CameraRecoilSystem : SharedCameraRecoilSystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeNetworkEvent(OnCameraKick); + } + + private void OnCameraKick(CameraKickEvent ev) + { + KickCamera(ev.Euid, ev.Recoil); + } + + public override void KickCamera(EntityUid uid, Vector2 recoil, CameraRecoilComponent? component = null) + { + if (!Resolve(uid, ref component, false)) return; + + // Use really bad math to "dampen" kicks when we're already kicked. + var existing = component.CurrentKick.Length; + var dampen = existing / KickMagnitudeMax; + component.CurrentKick += recoil * (1 - dampen); + + if (component.CurrentKick.Length > KickMagnitudeMax) + component.CurrentKick = component.CurrentKick.Normalized * KickMagnitudeMax; + + component.LastKickTime = 0; + } +} diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs index ed180eac17..88967b3eb7 100644 --- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs @@ -1,5 +1,6 @@ using Content.Client.Items; using Content.Client.Weapons.Ranged.Components; +using Content.Shared.Camera; using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; @@ -27,6 +28,7 @@ public sealed partial class GunSystem : SharedGunSystem [Dependency] private readonly AnimationPlayerSystem _animPlayer = default!; [Dependency] private readonly EffectSystem _effects = default!; [Dependency] private readonly InputSystem _inputSystem = default!; + [Dependency] private readonly SharedCameraRecoilSystem _recoil = default!; public bool SpreadOverlay { @@ -164,6 +166,8 @@ public sealed partial class GunSystem : SharedGunSystem // Rather than splitting client / server for every ammo provider it's easier // to just delete the spawned entities. This is for programmer sanity despite the wasted perf. // This also means any ammo specific stuff can be grabbed as necessary. + var direction = fromCoordinates.ToMapPos(EntityManager) - toCoordinates.ToMapPos(EntityManager); + foreach (var ent in ammo) { switch (ent) @@ -174,6 +178,7 @@ public sealed partial class GunSystem : SharedGunSystem SetCartridgeSpent(cartridge, true); MuzzleFlash(gun.Owner, cartridge, user); PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user); + Recoil(user, direction); // TODO: Can't predict entity deletions. //if (cartridge.DeleteOnSpawn) // Del(cartridge.Owner); @@ -190,6 +195,7 @@ public sealed partial class GunSystem : SharedGunSystem case AmmoComponent newAmmo: MuzzleFlash(gun.Owner, newAmmo, user); PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user); + Recoil(user, direction); if (newAmmo.Owner.IsClientSide()) Del(newAmmo.Owner); else @@ -197,11 +203,18 @@ public sealed partial class GunSystem : SharedGunSystem break; case HitscanPrototype: PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user); + Recoil(user, direction); break; } } } + private void Recoil(EntityUid? user, Vector2 recoil) + { + if (!Timing.IsFirstTimePredicted || user == null || recoil == Vector2.Zero) return; + _recoil.KickCamera(user.Value, recoil.Normalized * 0.5f); + } + protected override void PlaySound(EntityUid gun, string? sound, EntityUid? user = null) { if (string.IsNullOrEmpty(sound) || user == null || !Timing.IsFirstTimePredicted) return; diff --git a/Content.Server/Camera/CameraRecoilSystem.cs b/Content.Server/Camera/CameraRecoilSystem.cs new file mode 100644 index 0000000000..ecd6993490 --- /dev/null +++ b/Content.Server/Camera/CameraRecoilSystem.cs @@ -0,0 +1,14 @@ +using Content.Shared.Camera; +using Robust.Shared.Player; + +namespace Content.Server.Camera; + +public sealed class CameraRecoilSystem : SharedCameraRecoilSystem +{ + public override void KickCamera(EntityUid euid, Vector2 kickback, CameraRecoilComponent? component = null) + { + if (!Resolve(euid, ref component, false)) return; + + RaiseNetworkEvent(new CameraKickEvent(euid, kickback), Filter.Entities(euid)); + } +} diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs index f704191521..7b420a0b45 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs @@ -31,7 +31,7 @@ public sealed partial class ExplosionSystem : EntitySystem [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly NodeGroupSystem _nodeGroupSystem = default!; - [Dependency] private readonly CameraRecoilSystem _recoilSystem = default!; + [Dependency] private readonly SharedCameraRecoilSystem _recoilSystem = default!; [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly ThrowingSystem _throwingSystem = default!; diff --git a/Content.Server/Gravity/EntitySystems/GravityShakeSystem.cs b/Content.Server/Gravity/EntitySystems/GravityShakeSystem.cs index d867ab2050..06a136316c 100644 --- a/Content.Server/Gravity/EntitySystems/GravityShakeSystem.cs +++ b/Content.Server/Gravity/EntitySystems/GravityShakeSystem.cs @@ -16,7 +16,7 @@ namespace Content.Server.Gravity.EntitySystems [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly CameraRecoilSystem _cameraRecoil = default!; + [Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!; private Dictionary _gridsToShake = new(); @@ -83,7 +83,7 @@ namespace Content.Server.Gravity.EntitySystems } var kick = new Vector2(_random.NextFloat(), _random.NextFloat()) * GravityKick; - _cameraRecoil.KickCamera(player.AttachedEntity.Value, kick); + _sharedCameraRecoil.KickCamera(player.AttachedEntity.Value, kick); } } } diff --git a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs index e826ff3cdf..3c7b5780b4 100644 --- a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs +++ b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs @@ -29,7 +29,7 @@ namespace Content.Server.PneumaticCannon [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly StunSystem _stun = default!; [Dependency] private readonly AtmosphereSystem _atmos = default!; - [Dependency] private readonly CameraRecoilSystem _cameraRecoil = default!; + [Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly ThrowingSystem _throwingSystem = default!; [Dependency] private readonly StorageSystem _storageSystem = default!; @@ -231,7 +231,7 @@ namespace Content.Server.PneumaticCannon if (EntityManager.HasComponent(data.User)) { var kick = Vector2.One * data.Strength; - _cameraRecoil.KickCamera(data.User, kick); + _sharedCameraRecoil.KickCamera(data.User, kick); } _throwingSystem.TryThrow(ent, data.Direction, data.Strength, data.User, GetPushbackRatioFromPower(comp.Power)); diff --git a/Content.Server/Projectiles/SharedProjectileSystem.cs b/Content.Server/Projectiles/SharedProjectileSystem.cs index 9cb0cb251f..9d477c05f9 100644 --- a/Content.Server/Projectiles/SharedProjectileSystem.cs +++ b/Content.Server/Projectiles/SharedProjectileSystem.cs @@ -17,7 +17,7 @@ namespace Content.Server.Projectiles { [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly CameraRecoilSystem _cameraRecoil = default!; + [Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!; [Dependency] private readonly GunSystem _guns = default!; public override void Initialize() @@ -56,7 +56,7 @@ namespace Content.Server.Projectiles if (HasComp(otherEntity)) { var direction = args.OurFixture.Body.LinearVelocity.Normalized; - _cameraRecoil.KickCamera(otherEntity, direction); + _sharedCameraRecoil.KickCamera(otherEntity, direction); } if (component.DeleteOnCollide) diff --git a/Content.Shared/Camera/CameraRecoilSystem.cs b/Content.Shared/Camera/SharedCameraRecoilSystem.cs similarity index 57% rename from Content.Shared/Camera/CameraRecoilSystem.cs rename to Content.Shared/Camera/SharedCameraRecoilSystem.cs index 97f1670843..331c2fe25b 100644 --- a/Content.Shared/Camera/CameraRecoilSystem.cs +++ b/Content.Shared/Camera/SharedCameraRecoilSystem.cs @@ -5,36 +5,34 @@ using Robust.Shared.Serialization; namespace Content.Shared.Camera; [UsedImplicitly] -public sealed class CameraRecoilSystem : EntitySystem +public abstract class SharedCameraRecoilSystem : EntitySystem { /// /// Maximum rate of magnitude restore towards 0 kick. /// - private const float RestoreRateMax = 15f; + private const float RestoreRateMax = 30f; /// /// Minimum rate of magnitude restore towards 0 kick. /// - private const float RestoreRateMin = 1f; + private const float RestoreRateMin = 0.1f; /// /// Time in seconds since the last kick that lerps RestoreRateMin and RestoreRateMax /// - private const float RestoreRateRamp = 0.1f; + private const float RestoreRateRamp = 4f; /// /// The maximum magnitude of the kick applied to the camera at any point. /// - private const float KickMagnitudeMax = 2f; + protected const float KickMagnitudeMax = 1f; - private readonly ISawmill _log; + private ISawmill _log = default!; - private CameraRecoilSystem(IEntityManager entityManager) - : base(entityManager) + public override void Initialize() { - _log = Logger.GetSawmill($"ecs.systems.{nameof(CameraRecoilSystem)}"); - - SubscribeNetworkEvent(HandleCameraKick); + base.Initialize(); + _log = Logger.GetSawmill($"ecs.systems.{nameof(SharedCameraRecoilSystem)}"); } /// @@ -44,15 +42,7 @@ public sealed class CameraRecoilSystem : EntitySystem /// If the entity is missing and/or , /// this call will have no effect. It is safe to call this function on any entity. /// - /// Entity to apply the kickback to. - /// The amount of kick to offset the view of the entity. World coordinates, in meters. - public void KickCamera(EntityUid euid, Vector2 kickback) - { - if (!EntityManager.HasComponent(euid)) - return; - - RaiseNetworkEvent(new CameraKickEvent(euid, kickback), Filter.Entities(euid)); - } + public abstract void KickCamera(EntityUid euid, Vector2 kickback, CameraRecoilComponent? component = null); public override void FrameUpdate(float frameTime) { @@ -85,31 +75,6 @@ public sealed class CameraRecoilSystem : EntitySystem } } } - - private void HandleCameraKick(CameraKickEvent args) - { - if (!EntityManager.TryGetComponent(args.Euid, out CameraRecoilComponent? recoil)) - { - _log.Warning($"Received a kick for euid {args.Euid}, but it is missing required components."); - return; - } - - if (!float.IsFinite(args.Recoil.X) || !float.IsFinite(args.Recoil.Y)) - { - _log.Error($"CameraRecoilComponent on entity {recoil.Owner} passed a NaN recoil value. Ignoring."); - return; - } - - // Use really bad math to "dampen" kicks when we're already kicked. - var existing = recoil.CurrentKick.Length; - var dampen = existing / KickMagnitudeMax; - recoil.CurrentKick += args.Recoil * (1 - dampen); - - if (recoil.CurrentKick.Length > KickMagnitudeMax) - recoil.CurrentKick = recoil.CurrentKick.Normalized * KickMagnitudeMax; - - recoil.LastKickTime = 0; - } } [Serializable]