diff --git a/Content.Client/Eye/EyeLerpingSystem.cs b/Content.Client/Eye/EyeLerpingSystem.cs index 2d7abb7d7a..95d9123b87 100644 --- a/Content.Client/Eye/EyeLerpingSystem.cs +++ b/Content.Client/Eye/EyeLerpingSystem.cs @@ -6,6 +6,7 @@ using Robust.Client.Physics; using Robust.Client.Player; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Timing; @@ -14,37 +15,81 @@ namespace Content.Client.Eye; public sealed class EyeLerpingSystem : EntitySystem { [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - private Angle? _lastGridAngle; - private Angle? _lerpTo; - private Angle _lerpStartRotation; - private float _accumulator; - // How fast the camera rotates in radians / s private const float CameraRotateSpeed = MathF.PI; // Safety override private const float LerpTimeMax = 1.5f; + // Lerping information for the player's active eye. + private readonly EyeLerpInformation _playerActiveEye = new(); + + // Eyes other than the primary eye that are currently active. + private readonly Dictionary _activeEyes = new(); + private readonly List _toRemove = new(); public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnEyeShutdown); + UpdatesAfter.Add(typeof(TransformSystem)); UpdatesAfter.Add(typeof(PhysicsSystem)); UpdatesBefore.Add(typeof(EyeUpdateSystem)); } + private void OnEyeShutdown(EntityUid uid, EyeComponent component, ComponentShutdown args) + { + RemoveEye(uid); + } + + public void AddEye(EntityUid uid) + { + if (!_activeEyes.ContainsKey(uid)) + { + _activeEyes.Add(uid, new()); + } + } + + public void RemoveEye(EntityUid uid) + { + if (_activeEyes.ContainsKey(uid)) + { + _activeEyes.Remove(uid); + } + } + public override void FrameUpdate(float frameTime) { if (!_gameTiming.IsFirstTimePredicted) return; - var currentEye = _eyeManager.CurrentEye; + // Always do this one. + LerpPlayerEye(frameTime); + foreach (var (entity, info) in _activeEyes) + { + LerpEntityEye(entity, info, frameTime); + } + + if (_toRemove.Count != 0) + { + foreach (var entity in _toRemove) + { + RemoveEye(entity); + } + + _toRemove.Clear(); + } + } + + private void LerpPlayerEye(float frameTime) + { if (_playerManager.LocalPlayer?.ControlledEntity is not {} mob || Deleted(mob)) return; @@ -52,62 +97,72 @@ public sealed class EyeLerpingSystem : EntitySystem if (!TryComp(mob, out IMoverComponent? mover)) return; - var moverLastGridAngle = mover.LastGridAngle; + LerpEye(_eyeManager.CurrentEye, frameTime, mover.LastGridAngle, _playerActiveEye); + } + + private void LerpEntityEye(EntityUid uid, EyeLerpInformation info, float frameTime) + { + if (!TryComp(uid, out TransformComponent? transform) + || !TryComp(uid, out EyeComponent? eye) + || eye.Eye == null + || !_mapManager.TryGetGrid(transform.GridID, out var grid)) + { + _toRemove.Add(uid); + return; + } + + LerpEye(eye.Eye, frameTime, grid.WorldRotation, info); + } + + private void LerpEye(IEye eye, float frameTime, Angle lastAngle, EyeLerpInformation lerpInfo) + { // Let's not turn the camera into a washing machine when the game starts. - if (_lastGridAngle == null) + if (lerpInfo.LastGridAngle == null) { - _lastGridAngle = moverLastGridAngle; - currentEye.Rotation = -moverLastGridAngle; + lerpInfo.LastGridAngle = lastAngle; + eye.Rotation = -lastAngle; return; } // Check if the last lerp grid angle we have is not the same as the last mover grid angle... - if (!_lastGridAngle.Value.EqualsApprox(moverLastGridAngle)) + if (!lerpInfo.LastGridAngle.Value.EqualsApprox(lastAngle)) { // And now, we start lerping. - _lerpTo = moverLastGridAngle; - _lastGridAngle = moverLastGridAngle; - _lerpStartRotation = currentEye.Rotation; - _accumulator = 0f; + lerpInfo.LerpTo = lastAngle; + lerpInfo.LastGridAngle = lastAngle; + lerpInfo.LerpStartRotation = eye.Rotation; + lerpInfo.Accumulator = 0f; } - if (_lerpTo != null) + if (lerpInfo.LerpTo != null) { - _accumulator += frameTime; + lerpInfo.Accumulator += frameTime; - var lerpRot = -_lerpTo.Value.FlipPositive().Reduced(); - var startRot = _lerpStartRotation.FlipPositive().Reduced(); + var lerpRot = -lerpInfo.LerpTo.Value.FlipPositive().Reduced(); + var startRot = lerpInfo.LerpStartRotation.FlipPositive().Reduced(); var changeNeeded = Angle.ShortestDistance(startRot, lerpRot); if (changeNeeded.EqualsApprox(Angle.Zero)) { // Nothing to do here! - CleanupLerp(); + lerpInfo.Cleanup(eye); return; } // Get how much the camera should have moved by now. Make it faster depending on the change needed. - var changeRot = (CameraRotateSpeed * Math.Max(1f, Math.Abs(changeNeeded) * 0.75f)) * _accumulator * Math.Sign(changeNeeded); + var changeRot = (CameraRotateSpeed * Math.Max(1f, Math.Abs(changeNeeded) * 0.75f)) * lerpInfo.Accumulator * Math.Sign(changeNeeded); // How close is this from reaching the end? var percentage = (float)Math.Abs(changeRot / changeNeeded); - currentEye.Rotation = Angle.Lerp(startRot, lerpRot, percentage); + eye.Rotation = Angle.Lerp(startRot, lerpRot, percentage); // Either we have overshot, or we have taken way too long on this, emergency reset time - if (percentage >= 1.0f || _accumulator >= LerpTimeMax) + if (percentage >= 1.0f || lerpInfo.Accumulator >= LerpTimeMax) { - CleanupLerp(); - } - - void CleanupLerp() - { - currentEye.Rotation = -_lerpTo.Value; - _lerpStartRotation = currentEye.Rotation; - _lerpTo = null; - _accumulator = 0f; + lerpInfo.Cleanup(eye); } } else @@ -117,7 +172,23 @@ public sealed class EyeLerpingSystem : EntitySystem // ghosting, this system listening for attached mob changes, and the eye rotation being reset after our // changes back to zero because of an EyeComponent state coming from the server being applied. // At some point we'll need to come up with a solution for that. But for now, I just want to fix this. - currentEye.Rotation = -moverLastGridAngle; + eye.Rotation = -lastAngle; + } + } + + private sealed class EyeLerpInformation + { + public Angle? LastGridAngle { get; set; } + public Angle? LerpTo { get; set; } + public Angle LerpStartRotation { get; set; } + public float Accumulator { get; set; } + + public void Cleanup(IEye eye) + { + eye.Rotation = -LerpTo ?? Angle.Zero; + LerpStartRotation = eye.Rotation; + LerpTo = null; + Accumulator = 0; } } }