Re-implement recoil (#9406)
* Re-implement recoil Playing around with the values atm * Update constants * final tweaks
This commit is contained in:
32
Content.Client/Camera/CameraRecoilSystem.cs
Normal file
32
Content.Client/Camera/CameraRecoilSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
14
Content.Server/Camera/CameraRecoilSystem.cs
Normal file
14
Content.Server/Camera/CameraRecoilSystem.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
Reference in New Issue
Block a user