diff --git a/Content.Client/Camera/CameraRecoilComponent.cs b/Content.Client/Camera/CameraRecoilComponent.cs deleted file mode 100644 index d037fbd602..0000000000 --- a/Content.Client/Camera/CameraRecoilComponent.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using Content.Shared.Camera; -using Robust.Client.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.Log; -using Robust.Shared.Maths; -using Robust.Shared.Network; -using Robust.Shared.Players; - -namespace Content.Client.Camera -{ - [RegisterComponent] - [ComponentReference(typeof(SharedCameraRecoilComponent))] - public sealed class CameraRecoilComponent : SharedCameraRecoilComponent - { - // Maximum rate of magnitude restore towards 0 kick. - private const float RestoreRateMax = 15f; - - // Minimum rate of magnitude restore towards 0 kick. - private const float RestoreRateMin = 1f; - - // Time in seconds since the last kick that lerps RestoreRateMin and RestoreRateMax - private const float RestoreRateRamp = 0.1f; - - // The maximum magnitude of the kick applied to the camera at any point. - private const float KickMagnitudeMax = 2f; - - private Vector2 _currentKick; - private float _lastKickTime; - - [ComponentDependency] - private readonly EyeComponent? _eye = default; - - // Basically I needed a way to chain this effect for the attack lunge animation. - // Sorry! - public Vector2 BaseOffset { get; set; } - - public override void Kick(Vector2 recoil) - { - if (float.IsNaN(recoil.X) || float.IsNaN(recoil.Y)) - { - Logger.Error($"CameraRecoilComponent on entity {Owner} passed a NaN recoil value. Ignoring."); - return; - } - - // Use really bad math to "dampen" kicks when we're already kicked. - var existing = _currentKick.Length; - var dampen = existing/KickMagnitudeMax; - _currentKick += recoil * (1-dampen); - if (_currentKick.Length > KickMagnitudeMax) - { - _currentKick = _currentKick.Normalized * KickMagnitudeMax; - } - - _lastKickTime = 0; - _updateEye(); - } - - [Obsolete("Component Messages are deprecated, use Entity Events instead.")] - public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null) - { - base.HandleNetworkMessage(message, channel, session); - - switch (message) - { - case RecoilKickMessage msg: - Kick(msg.Recoil); - break; - } - } - - public void FrameUpdate(float frameTime) - { - var magnitude = _currentKick.Length; - if (magnitude <= 0.005f) - { - _currentKick = Vector2.Zero; - _updateEye(); - return; - } - - // Continually restore camera to 0. - var normalized = _currentKick.Normalized; - _lastKickTime += frameTime; - var restoreRate = MathHelper.Lerp(RestoreRateMin, RestoreRateMax, Math.Min(1, _lastKickTime/RestoreRateRamp)); - var restore = normalized * restoreRate * frameTime; - var (x, y) = _currentKick - restore; - if (Math.Sign(x) != Math.Sign(_currentKick.X)) - { - x = 0; - } - - if (Math.Sign(y) != Math.Sign(_currentKick.Y)) - { - y = 0; - } - - _currentKick = (x, y); - - _updateEye(); - } - - private void _updateEye() - { - if (_eye != null) _eye.Offset = BaseOffset + _currentKick; - } - } -} diff --git a/Content.Client/Camera/CameraRecoilSystem.cs b/Content.Client/Camera/CameraRecoilSystem.cs deleted file mode 100644 index 8a5c701db7..0000000000 --- a/Content.Client/Camera/CameraRecoilSystem.cs +++ /dev/null @@ -1,19 +0,0 @@ -using JetBrains.Annotations; -using Robust.Shared.GameObjects; - -namespace Content.Client.Camera -{ - [UsedImplicitly] - public sealed class CameraRecoilSystem : EntitySystem - { - public override void FrameUpdate(float frameTime) - { - base.FrameUpdate(frameTime); - - foreach (var recoil in EntityManager.EntityQuery(true)) - { - recoil.FrameUpdate(frameTime); - } - } - } -} diff --git a/Content.Server/Camera/CameraRecoilComponent.cs b/Content.Server/Camera/CameraRecoilComponent.cs deleted file mode 100644 index 0d7c19dad9..0000000000 --- a/Content.Server/Camera/CameraRecoilComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Shared.Camera; -using Robust.Shared.GameObjects; -using Robust.Shared.Maths; - -namespace Content.Server.Camera -{ - [RegisterComponent] - [ComponentReference(typeof(SharedCameraRecoilComponent))] - public sealed class CameraRecoilComponent : SharedCameraRecoilComponent - { - public override void Kick(Vector2 recoil) - { - var msg = new RecoilKickMessage(recoil); -#pragma warning disable 618 - SendNetworkMessage(msg); -#pragma warning restore 618 - } - } -} diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs index 0e5c630efa..367a2bb88f 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs @@ -2,9 +2,9 @@ using System; using System.Collections.Generic; using System.Linq; using Content.Server.Administration.Logs; -using Content.Server.Camera; using Content.Server.Explosion.Components; using Content.Shared.Acts; +using Content.Shared.Camera; using Content.Shared.Database; using Content.Shared.Interaction.Helpers; using Content.Shared.Maps; @@ -51,6 +51,7 @@ namespace Content.Server.Explosion.EntitySystems [Dependency] private readonly EffectSystem _effects = default!; [Dependency] private readonly TriggerSystem _triggers = default!; [Dependency] private readonly AdminLogSystem _logSystem = default!; + [Dependency] private readonly CameraRecoilSystem _cameraRecoil = default!; private bool IgnoreExplosivePassable(EntityUid e) { @@ -82,7 +83,7 @@ namespace Content.Server.Explosion.EntitySystems foreach (var player in players) { if (player.AttachedEntity is not {Valid: true} playerEntity || - !EntityManager.TryGetComponent(playerEntity, out CameraRecoilComponent? recoil)) + !EntityManager.HasComponent(playerEntity)) { continue; } @@ -99,7 +100,7 @@ namespace Content.Server.Explosion.EntitySystems if (effect > 0.01f) { var kick = -delta.Normalized * effect; - recoil.Kick(kick); + _cameraRecoil.KickCamera(player.AttachedEntity.Value, kick); } } } diff --git a/Content.Server/Gravity/EntitySystems/GravityShakeSystem.cs b/Content.Server/Gravity/EntitySystems/GravityShakeSystem.cs index 04e2902968..fcc30daae7 100644 --- a/Content.Server/Gravity/EntitySystems/GravityShakeSystem.cs +++ b/Content.Server/Gravity/EntitySystems/GravityShakeSystem.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using Content.Server.Camera; +using Content.Shared.Camera; using Content.Shared.Gravity; using Robust.Server.Player; using Robust.Shared.Audio; @@ -20,6 +20,8 @@ namespace Content.Server.Gravity.EntitySystems [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly CameraRecoilSystem _cameraRecoil = default!; + private Dictionary _gridsToShake = new(); private const float GravityKick = 100.0f; @@ -81,12 +83,13 @@ namespace Content.Server.Gravity.EntitySystems { if (player.AttachedEntity is not {Valid: true} attached || EntityManager.GetComponent(attached).GridID != gridId - || !EntityManager.TryGetComponent(attached, out CameraRecoilComponent? recoil)) + || !EntityManager.HasComponent(attached)) { continue; } - recoil.Kick(new Vector2(_random.NextFloat(), _random.NextFloat()) * GravityKick); + var kick = new Vector2(_random.NextFloat(), _random.NextFloat()) * GravityKick; + _cameraRecoil.KickCamera(player.AttachedEntity.Value, kick); } } } diff --git a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs index 6784c555eb..ae8382154f 100644 --- a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs +++ b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs @@ -1,9 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; -using Content.Server.Camera; using Content.Server.Hands.Components; using Content.Server.Items; using Content.Server.Nutrition.Components; @@ -11,6 +10,7 @@ using Content.Server.Storage.Components; using Content.Server.Stunnable; using Content.Server.Throwing; using Content.Server.Tools.Components; +using Content.Shared.Camera; using Content.Shared.CombatMode; using Content.Shared.Interaction; using Content.Shared.PneumaticCannon; @@ -34,6 +34,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!; private HashSet _currentlyFiring = new(); @@ -229,9 +230,10 @@ namespace Content.Server.PneumaticCannon storage.Remove(ent); SoundSystem.Play(Filter.Pvs(data.User), comp.FireSound.GetSound(), ((IComponent) comp).Owner, AudioParams.Default); - if (EntityManager.TryGetComponent(data.User, out var recoil)) + if (EntityManager.HasComponent(data.User)) { - recoil.Kick(Vector2.One * data.Strength); + var kick = Vector2.One * data.Strength; + _cameraRecoil.KickCamera(data.User, kick); } ent.TryThrow(data.Direction, data.Strength, data.User, GetPushbackRatioFromPower(comp.Power)); diff --git a/Content.Server/Projectiles/ProjectileSystem.cs b/Content.Server/Projectiles/ProjectileSystem.cs index 2d00d30938..721d1687aa 100644 --- a/Content.Server/Projectiles/ProjectileSystem.cs +++ b/Content.Server/Projectiles/ProjectileSystem.cs @@ -1,7 +1,7 @@ using Content.Server.Administration.Logs; -using Content.Server.Camera; using Content.Server.Projectiles.Components; using Content.Shared.Body.Components; +using Content.Shared.Camera; using Content.Shared.Damage; using Content.Shared.Database; using JetBrains.Annotations; @@ -9,6 +9,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Maths; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Player; @@ -19,6 +20,7 @@ namespace Content.Server.Projectiles { [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly AdminLogSystem _adminLogSystem = default!; + [Dependency] private readonly CameraRecoilSystem _cameraRecoil = default!; public override void Initialize() { @@ -65,10 +67,10 @@ namespace Content.Server.Projectiles // Damaging it can delete it if (!EntityManager.GetComponent(otherEntity).EntityDeleted && - EntityManager.TryGetComponent(otherEntity, out CameraRecoilComponent? recoilComponent)) + EntityManager.HasComponent(otherEntity)) { var direction = args.OurFixture.Body.LinearVelocity.Normalized; - recoilComponent.Kick(direction); + _cameraRecoil.KickCamera(otherEntity, direction); } if (component.DeleteOnCollide) diff --git a/Content.Server/Weapon/Ranged/Barrels/Components/ServerRangedBarrelComponent.cs b/Content.Server/Weapon/Ranged/Barrels/Components/ServerRangedBarrelComponent.cs index 0f84af3763..2391bcd43f 100644 --- a/Content.Server/Weapon/Ranged/Barrels/Components/ServerRangedBarrelComponent.cs +++ b/Content.Server/Weapon/Ranged/Barrels/Components/ServerRangedBarrelComponent.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Content.Server.Administration.Logs; -using Content.Server.Camera; using Content.Server.Projectiles.Components; using Content.Server.Weapon.Ranged.Ammunition.Components; +using Content.Shared.Camera; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Examine; @@ -208,9 +208,10 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components var direction = (targetPos - Entities.GetComponent(shooter).WorldPosition).ToAngle(); var angle = GetRecoilAngle(direction); // This should really be client-side but for now we'll just leave it here - if (Entities.TryGetComponent(shooter, out CameraRecoilComponent? recoilComponent)) + if (Entities.HasComponent(shooter)) { - recoilComponent.Kick(-angle.ToVec() * 0.15f); + var kick = -angle.ToVec() * 0.15f; + EntitySystem.Get().KickCamera(shooter, kick); } // This section probably needs tweaking so there can be caseless hitscan etc. diff --git a/Content.Shared/Camera/CameraRecoilComponent.cs b/Content.Shared/Camera/CameraRecoilComponent.cs new file mode 100644 index 0000000000..924591680f --- /dev/null +++ b/Content.Shared/Camera/CameraRecoilComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Maths; + +namespace Content.Shared.Camera; + +[RegisterComponent] +[NetworkedComponent] +[ComponentProtoName("CameraRecoil")] +public class CameraRecoilComponent : Component +{ + public Vector2 CurrentKick { get; set; } + public float LastKickTime { get; set; } + + /// + /// Basically I needed a way to chain this effect for the attack lunge animation. Sorry! + /// + public Vector2 BaseOffset { get; set; } +} diff --git a/Content.Shared/Camera/CameraRecoilSystem.cs b/Content.Shared/Camera/CameraRecoilSystem.cs new file mode 100644 index 0000000000..0c1e88bff1 --- /dev/null +++ b/Content.Shared/Camera/CameraRecoilSystem.cs @@ -0,0 +1,132 @@ +using System; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.Player; +using Robust.Shared.Serialization; + +namespace Content.Shared.Camera; + +[UsedImplicitly] +public class CameraRecoilSystem : EntitySystem +{ + /// + /// Maximum rate of magnitude restore towards 0 kick. + /// + private const float RestoreRateMax = 15f; + + /// + /// Minimum rate of magnitude restore towards 0 kick. + /// + private const float RestoreRateMin = 1f; + + /// + /// Time in seconds since the last kick that lerps RestoreRateMin and RestoreRateMax + /// + private const float RestoreRateRamp = 0.1f; + + /// + /// The maximum magnitude of the kick applied to the camera at any point. + /// + private const float KickMagnitudeMax = 2f; + + private readonly ISawmill _log; + + protected CameraRecoilSystem(IEntityManager entityManager) + : base(entityManager) + { + _log = Logger.GetSawmill($"ecs.systems.{nameof(CameraRecoilSystem)}"); + + SubscribeNetworkEvent(HandleCameraKick); + } + + /// + /// Applies explosion/recoil/etc kickback to the view of the entity. + /// + /// + /// 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; + + //TODO: This should only be sent to clients registered as viewers to the entity. + RaiseNetworkEvent(new CameraKickEvent(euid, kickback), Filter.Broadcast()); + } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + + foreach (var entity in EntityManager.EntityQuery(true)) + { + var recoil = entity.Item2; + var eye = entity.Item1; + var magnitude = recoil.CurrentKick.Length; + if (magnitude <= 0.005f) + { + recoil.CurrentKick = Vector2.Zero; + eye.Offset = recoil.BaseOffset + recoil.CurrentKick; + } + else // Continually restore camera to 0. + { + var normalized = recoil.CurrentKick.Normalized; + recoil.LastKickTime += frameTime; + var restoreRate = MathHelper.Lerp(RestoreRateMin, RestoreRateMax, Math.Min(1, recoil.LastKickTime / RestoreRateRamp)); + var restore = normalized * restoreRate * frameTime; + var (x, y) = recoil.CurrentKick - restore; + if (Math.Sign(x) != Math.Sign(recoil.CurrentKick.X)) x = 0; + + if (Math.Sign(y) != Math.Sign(recoil.CurrentKick.Y)) y = 0; + + recoil.CurrentKick = (x, y); + + eye.Offset = recoil.BaseOffset + recoil.CurrentKick; + } + } + } + + 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] +[NetSerializable] +public class CameraKickEvent : EntityEventArgs +{ + public readonly EntityUid Euid; + public readonly Vector2 Recoil; + + public CameraKickEvent(EntityUid euid, Vector2 recoil) + { + Recoil = recoil; + Euid = euid; + } +} diff --git a/Content.Shared/Camera/SharedCameraRecoilComponent.cs b/Content.Shared/Camera/SharedCameraRecoilComponent.cs deleted file mode 100644 index a8b20990d4..0000000000 --- a/Content.Shared/Camera/SharedCameraRecoilComponent.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using Robust.Shared.GameObjects; -using Robust.Shared.GameStates; -using Robust.Shared.Maths; -using Robust.Shared.Serialization; - -namespace Content.Shared.Camera -{ - [NetworkedComponent()] - public abstract class SharedCameraRecoilComponent : Component - { - public sealed override string Name => "CameraRecoil"; - - public abstract void Kick(Vector2 recoil); - - [Serializable, NetSerializable] -#pragma warning disable 618 - protected class RecoilKickMessage : ComponentMessage -#pragma warning restore 618 - { - public readonly Vector2 Recoil; - - public RecoilKickMessage(Vector2 recoil) - { - Directed = true; - Recoil = recoil; - } - } - } -}