Re-implement recoil (#9406)

* Re-implement recoil

Playing around with the values atm

* Update constants

* final tweaks
This commit is contained in:
metalgearsloth
2022-07-14 22:01:25 +10:00
committed by GitHub
parent d9e423cebb
commit 38fc066fb9
8 changed files with 76 additions and 52 deletions

View File

@@ -0,0 +1,32 @@
using Content.Shared.Camera;
namespace Content.Client.Camera;
public sealed class CameraRecoilSystem : SharedCameraRecoilSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<CameraKickEvent>(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;
}
}

View File

@@ -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;

View File

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

View File

@@ -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!;

View File

@@ -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<EntityUid, uint> _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);
}
}
}

View File

@@ -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<CameraRecoilComponent>(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));

View File

@@ -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<CameraRecoilComponent>(otherEntity))
{
var direction = args.OurFixture.Body.LinearVelocity.Normalized;
_cameraRecoil.KickCamera(otherEntity, direction);
_sharedCameraRecoil.KickCamera(otherEntity, direction);
}
if (component.DeleteOnCollide)

View File

@@ -5,36 +5,34 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Camera;
[UsedImplicitly]
public sealed class CameraRecoilSystem : EntitySystem
public abstract class SharedCameraRecoilSystem : EntitySystem
{
/// <summary>
/// Maximum rate of magnitude restore towards 0 kick.
/// </summary>
private const float RestoreRateMax = 15f;
private const float RestoreRateMax = 30f;
/// <summary>
/// Minimum rate of magnitude restore towards 0 kick.
/// </summary>
private const float RestoreRateMin = 1f;
private const float RestoreRateMin = 0.1f;
/// <summary>
/// Time in seconds since the last kick that lerps RestoreRateMin and RestoreRateMax
/// </summary>
private const float RestoreRateRamp = 0.1f;
private const float RestoreRateRamp = 4f;
/// <summary>
/// The maximum magnitude of the kick applied to the camera at any point.
/// </summary>
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<CameraKickEvent>(HandleCameraKick);
base.Initialize();
_log = Logger.GetSawmill($"ecs.systems.{nameof(SharedCameraRecoilSystem)}");
}
/// <summary>
@@ -44,15 +42,7 @@ public sealed class CameraRecoilSystem : EntitySystem
/// If the entity is missing <see cref="CameraRecoilComponent" /> and/or <see cref="SharedEyeComponent" />,
/// this call will have no effect. It is safe to call this function on any entity.
/// </remarks>
/// <param name="euid">Entity to apply the kickback to.</param>
/// <param name="kickback">The amount of kick to offset the view of the entity. World coordinates, in meters.</param>
public void KickCamera(EntityUid euid, Vector2 kickback)
{
if (!EntityManager.HasComponent<CameraRecoilComponent>(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]