diff --git a/Content.Client/Eye/EyeLerpingSystem.cs b/Content.Client/Eye/EyeLerpingSystem.cs index d1af9a211b..be069ca300 100644 --- a/Content.Client/Eye/EyeLerpingSystem.cs +++ b/Content.Client/Eye/EyeLerpingSystem.cs @@ -5,21 +5,19 @@ using Robust.Client.Graphics; using Robust.Client.Physics; using Robust.Client.Player; using Robust.Shared.Collections; -using Robust.Shared.Map; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Client.Eye; public sealed class EyeLerpingSystem : EntitySystem { - [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedMoverController _mover = default!; - // Eyes other than the primary eye that are currently active. - private readonly Dictionary _activeEyes = new(); + // Convenience variable for for VV. + [ViewVariables] + private IEnumerable ActiveEyes => EntityQuery(); public override void Initialize() { @@ -27,6 +25,9 @@ public sealed class EyeLerpingSystem : EntitySystem SubscribeLocalEvent(OnEyeStartup); SubscribeLocalEvent(OnEyeShutdown); + SubscribeLocalEvent(HandleMapChange); + SubscribeLocalEvent(OnAttached); + SubscribeLocalEvent(OnDetached); UpdatesAfter.Add(typeof(TransformSystem)); UpdatesAfter.Add(typeof(PhysicsSystem)); @@ -35,38 +36,58 @@ public sealed class EyeLerpingSystem : EntitySystem private void OnEyeStartup(EntityUid uid, EyeComponent component, ComponentStartup args) { - if (component.Eye == null) - return; - - // If the eye starts up then don't lerp at all. - var xformQuery = GetEntityQuery(); - TryComp(uid, out var mover); - xformQuery.TryGetComponent(uid, out var xform); - var lerpInfo = _activeEyes.GetOrNew(uid); - lerpInfo.TargetRotation = GetRotation(xformQuery, mover, xform); - lerpInfo.LastRotation = lerpInfo.TargetRotation; - - if (xform != null) - { - lerpInfo.MapId = xform.MapID; - } - - component.Eye.Rotation = lerpInfo.TargetRotation; + if (_playerManager.LocalPlayer?.ControlledEntity == uid) + AddEye(uid, component, true); } private void OnEyeShutdown(EntityUid uid, EyeComponent component, ComponentShutdown args) { - RemoveEye(uid); + RemCompDeferred(uid); } - public void AddEye(EntityUid uid) + // TODO replace this with some way of automatically getting and including any eyes that are associated with a viewport / render able thingy. + public void AddEye(EntityUid uid, EyeComponent? component = null, bool automatic = false) { - _activeEyes.TryAdd(uid, new EyeLerpInformation()); + if (!Resolve(uid, ref component)) + return; + + var lerpInfo = EnsureComp(uid); + lerpInfo.TargetRotation = GetRotation(uid); + lerpInfo.LastRotation = lerpInfo.TargetRotation; + lerpInfo.ManuallyAdded |= !automatic; + + if (component.Eye != null) + component.Eye.Rotation = lerpInfo.TargetRotation; } public void RemoveEye(EntityUid uid) { - _activeEyes.Remove(uid); + if (!TryComp(uid, out LerpingEyeComponent? lerp)) + return; + + // If this is the currently controlled entity, we keep the component. + if (_playerManager.LocalPlayer?.ControlledEntity == uid) + lerp.ManuallyAdded = false; + else + RemComp(uid, lerp); + } + + private void HandleMapChange(EntityUid uid, LerpingEyeComponent component, ref EntParentChangedMessage args) + { + // Is this actually a map change? If yes, stop any lerps + if (args.OldMapId != args.Transform.MapID) + component.LastRotation = GetRotation(uid, args.Transform); + } + + private void OnAttached(EntityUid uid, EyeComponent component, PlayerAttachedEvent args) + { + AddEye(uid, component, true); + } + + private void OnDetached(EntityUid uid, LerpingEyeComponent component, PlayerDetachedEvent args) + { + if (!component.ManuallyAdded) + RemCompDeferred(uid, component); } public override void Update(float frameTime) @@ -76,46 +97,21 @@ public sealed class EyeLerpingSystem : EntitySystem if (!_gameTiming.IsFirstTimePredicted) return; - var moverQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - var foundEyes = new ValueList(1); - // Set all of our eye rotations to the relevant values. - foreach (var (eye, entity) in GetEyes()) + foreach (var (lerpInfo, xform) in EntityQuery()) { - var lerpInfo = _activeEyes.GetOrNew(entity); - foundEyes.Add(entity); - moverQuery.TryGetComponent(entity, out var mover); - xformQuery.TryGetComponent(entity, out var xform); lerpInfo.LastRotation = lerpInfo.TargetRotation; - lerpInfo.TargetRotation = GetRotation(xformQuery, mover, xform); - - if (xform != null) - { - // If we traverse maps then don't lerp. - if (xform.MapID != lerpInfo.MapId) - { - lerpInfo.LastRotation = lerpInfo.TargetRotation; - } - } - } - - foreach (var eye in foundEyes) - { - if (!_activeEyes.ContainsKey(eye)) - { - _activeEyes.Remove(eye); - } + lerpInfo.TargetRotation = GetRotation(lerpInfo.Owner, xform); } } /// /// Does the eye need to lerp or is its rotation matched. /// - private bool NeedsLerp(EntityUid uid, InputMoverComponent? mover = null) + private bool NeedsLerp(InputMoverComponent? mover) { if (mover == null) - return true; + return false; if (mover.RelativeRotation.Equals(mover.TargetRelativeRotation)) return false; @@ -123,67 +119,42 @@ public sealed class EyeLerpingSystem : EntitySystem return true; } - private Angle GetRotation(EntityQuery xformQuery, InputMoverComponent? mover = null, TransformComponent? xform = null) + private Angle GetRotation(EntityUid uid, TransformComponent? xform = null, InputMoverComponent? mover = null) { + if (!Resolve(uid, ref xform)) + return Angle.Zero; + // If we can move then tie our eye to our inputs (these also get lerped so it should be fine). - if (mover != null) + if (Resolve(uid, ref mover, false)) { return -_mover.GetParentGridAngle(mover); } // if not tied to a mover then lock it to map / grid - if (xform != null) - { - var relative = xform.GridUid; - relative ??= xform.MapUid; - - if (xformQuery.TryGetComponent(relative, out var relativeXform)) - { - return -relativeXform.WorldRotation; - } - } + var relative = xform.GridUid ?? xform.MapUid; + if (relative != null) + return -Transform(relative.Value).WorldRotation; return Angle.Zero; } - private IEnumerable<(IEye Eye, EntityUid Entity)> GetEyes() - { - if (_playerManager.LocalPlayer?.ControlledEntity is { } player && !Deleted(player)) - { - yield return (_eyeManager.CurrentEye, player); - } - - if (_activeEyes.Count == 0) - yield break; - - var eyeQuery = GetEntityQuery(); - - foreach (var (ent, info) in _activeEyes) - { - if (!eyeQuery.TryGetComponent(ent, out var eyeComp) || - eyeComp.Eye == null) - { - continue; - } - - yield return (eyeComp.Eye, ent); - } - } - public override void FrameUpdate(float frameTime) { var tickFraction = (float) _gameTiming.TickFraction / ushort.MaxValue; const double lerpMinimum = 0.00001; - var xformQuery = GetEntityQuery(); - foreach (var (eye, entity) in GetEyes()) + foreach (var (lerpInfo, eye, xform) in EntityQuery()) { - if (!_activeEyes.TryGetValue(entity, out var lerpInfo)) - continue; + var entity = eye.Owner; - if (TryComp(entity, out var mover) && !NeedsLerp(entity, mover)) + TryComp(entity, out var mover); + + // This needs to be recomputed every frame, as if this is simply the grid rotation, then we need to account for grid angle lerping. + lerpInfo.TargetRotation = GetRotation(entity, xform, mover); + + if (!NeedsLerp(mover)) { - eye.Rotation = GetRotation(xformQuery, mover); + eye.Rotation = lerpInfo.TargetRotation; continue; } @@ -198,15 +169,4 @@ public sealed class EyeLerpingSystem : EntitySystem eye.Rotation = shortest * tickFraction + lerpInfo.LastRotation; } } - - private sealed class EyeLerpInformation - { - public Angle LastRotation; - public Angle TargetRotation; - - /// - /// If we go to a new map then don't lerp and snap instantly. - /// - public MapId MapId; - } } diff --git a/Content.Client/Eye/LerpingEyeComponent.cs b/Content.Client/Eye/LerpingEyeComponent.cs new file mode 100644 index 0000000000..e0c791196c --- /dev/null +++ b/Content.Client/Eye/LerpingEyeComponent.cs @@ -0,0 +1,19 @@ +namespace Content.Client.Eye; + +/// +/// Component for keeping track of client-side eye lerping. This component should only be added or removed via the . +/// +[RegisterComponent] +public sealed class LerpingEyeComponent : Component +{ + /// + /// False if this eye was automatically added when a player was attached to this entity. + /// + public bool ManuallyAdded = false; + + [ViewVariables] + public Angle LastRotation; + + [ViewVariables] + public Angle TargetRotation; +} diff --git a/Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorBoundUi.cs b/Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorBoundUi.cs index f247a0550a..c979c36cd4 100644 --- a/Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorBoundUi.cs +++ b/Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorBoundUi.cs @@ -115,6 +115,12 @@ public sealed class SurveillanceCameraMonitorBoundUserInterface : BoundUserInter { base.Dispose(disposing); + if (_currentCamera != null) + { + _eyeLerpingSystem.RemoveEye(_currentCamera.Value); + _currentCamera = null; + } + if (disposing) { _window?.Dispose();