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.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;
|
||||||
|
|||||||
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 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!;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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]
|
||||||
Reference in New Issue
Block a user