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.Items;
using Content.Client.Weapons.Ranged.Components; using Content.Client.Weapons.Ranged.Components;
using Content.Shared.Camera;
using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged;
using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events; 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 AnimationPlayerSystem _animPlayer = default!;
[Dependency] private readonly EffectSystem _effects = default!; [Dependency] private readonly EffectSystem _effects = default!;
[Dependency] private readonly InputSystem _inputSystem = default!; [Dependency] private readonly InputSystem _inputSystem = default!;
[Dependency] private readonly SharedCameraRecoilSystem _recoil = default!;
public bool SpreadOverlay 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 // 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. // 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. // 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) foreach (var ent in ammo)
{ {
switch (ent) switch (ent)
@@ -174,6 +178,7 @@ public sealed partial class GunSystem : SharedGunSystem
SetCartridgeSpent(cartridge, true); SetCartridgeSpent(cartridge, true);
MuzzleFlash(gun.Owner, cartridge, user); MuzzleFlash(gun.Owner, cartridge, user);
PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user); PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user);
Recoil(user, direction);
// TODO: Can't predict entity deletions. // TODO: Can't predict entity deletions.
//if (cartridge.DeleteOnSpawn) //if (cartridge.DeleteOnSpawn)
// Del(cartridge.Owner); // Del(cartridge.Owner);
@@ -190,6 +195,7 @@ public sealed partial class GunSystem : SharedGunSystem
case AmmoComponent newAmmo: case AmmoComponent newAmmo:
MuzzleFlash(gun.Owner, newAmmo, user); MuzzleFlash(gun.Owner, newAmmo, user);
PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user); PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user);
Recoil(user, direction);
if (newAmmo.Owner.IsClientSide()) if (newAmmo.Owner.IsClientSide())
Del(newAmmo.Owner); Del(newAmmo.Owner);
else else
@@ -197,11 +203,18 @@ public sealed partial class GunSystem : SharedGunSystem
break; break;
case HitscanPrototype: case HitscanPrototype:
PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user); PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user);
Recoil(user, direction);
break; 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) protected override void PlaySound(EntityUid gun, string? sound, EntityUid? user = null)
{ {
if (string.IsNullOrEmpty(sound) || user == null || !Timing.IsFirstTimePredicted) return; 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 DamageableSystem _damageableSystem = default!;
[Dependency] private readonly NodeGroupSystem _nodeGroupSystem = 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 EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly ThrowingSystem _throwingSystem = 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 IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustRandom _random = 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(); private Dictionary<EntityUid, uint> _gridsToShake = new();
@@ -83,7 +83,7 @@ namespace Content.Server.Gravity.EntitySystems
} }
var kick = new Vector2(_random.NextFloat(), _random.NextFloat()) * GravityKick; 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 IRobustRandom _random = default!;
[Dependency] private readonly StunSystem _stun = default!; [Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly AtmosphereSystem _atmos = 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 SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly ThrowingSystem _throwingSystem = default!; [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
[Dependency] private readonly StorageSystem _storageSystem = default!; [Dependency] private readonly StorageSystem _storageSystem = default!;
@@ -231,7 +231,7 @@ namespace Content.Server.PneumaticCannon
if (EntityManager.HasComponent<CameraRecoilComponent>(data.User)) if (EntityManager.HasComponent<CameraRecoilComponent>(data.User))
{ {
var kick = Vector2.One * data.Strength; 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)); _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 DamageableSystem _damageableSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = 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!; [Dependency] private readonly GunSystem _guns = default!;
public override void Initialize() public override void Initialize()
@@ -56,7 +56,7 @@ namespace Content.Server.Projectiles
if (HasComp<CameraRecoilComponent>(otherEntity)) if (HasComp<CameraRecoilComponent>(otherEntity))
{ {
var direction = args.OurFixture.Body.LinearVelocity.Normalized; var direction = args.OurFixture.Body.LinearVelocity.Normalized;
_cameraRecoil.KickCamera(otherEntity, direction); _sharedCameraRecoil.KickCamera(otherEntity, direction);
} }
if (component.DeleteOnCollide) if (component.DeleteOnCollide)

View File

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