From 525c38b7942be29a74a36a417c899562db38c40b Mon Sep 17 00:00:00 2001
From: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com>
Date: Mon, 27 Dec 2021 18:50:00 +0100
Subject: [PATCH] Move Eye Lerping to content and fix/improve it a bunch.
(#5900)
---
Content.Client/Eye/EyeLerpingSystem.cs | 123 ++++++++++++++++++
.../Physics/Controllers/MoverController.cs | 7 +-
.../SharedPlayerInputMoverComponent.cs | 1 +
.../Movement/SharedMoverController.cs | 15 ++-
4 files changed, 142 insertions(+), 4 deletions(-)
create mode 100644 Content.Client/Eye/EyeLerpingSystem.cs
diff --git a/Content.Client/Eye/EyeLerpingSystem.cs b/Content.Client/Eye/EyeLerpingSystem.cs
new file mode 100644
index 0000000000..92cde50108
--- /dev/null
+++ b/Content.Client/Eye/EyeLerpingSystem.cs
@@ -0,0 +1,123 @@
+using System;
+using Content.Shared.Movement.Components;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.Physics;
+using Robust.Client.Player;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Maths;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Eye;
+
+public class EyeLerpingSystem : EntitySystem
+{
+ [Dependency] private readonly IEyeManager _eyeManager = 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;
+
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ UpdatesAfter.Add(typeof(TransformSystem));
+ UpdatesAfter.Add(typeof(PhysicsSystem));
+ UpdatesBefore.Add(typeof(EyeUpdateSystem));
+ }
+
+ public override void FrameUpdate(float frameTime)
+ {
+ if (!_gameTiming.IsFirstTimePredicted)
+ return;
+
+ var currentEye = _eyeManager.CurrentEye;
+
+ if (_playerManager.LocalPlayer?.ControlledEntity is not {} mob || Deleted(mob))
+ return;
+
+ // We can't lerp if the mob can't move!
+ if (!TryComp(mob, out IMoverComponent? mover))
+ return;
+
+ var moverLastGridAngle = mover.LastGridAngle;
+
+ // Let's not turn the camera into a washing machine when the game starts.
+ if (_lastGridAngle == null)
+ {
+ _lastGridAngle = moverLastGridAngle;
+ currentEye.Rotation = -moverLastGridAngle;
+ 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))
+ {
+ // And now, we start lerping.
+ _lerpTo = moverLastGridAngle;
+ _lastGridAngle = moverLastGridAngle;
+ _lerpStartRotation = currentEye.Rotation;
+ _accumulator = 0f;
+ }
+
+ if (_lerpTo != null)
+ {
+ _accumulator += frameTime;
+
+ var lerpRot = -_lerpTo.Value.FlipPositive().Reduced();
+ var startRot = _lerpStartRotation.FlipPositive().Reduced();
+
+ var changeNeeded = Angle.ShortestDistance(startRot, lerpRot);
+
+ if (changeNeeded.EqualsApprox(Angle.Zero))
+ {
+ // Nothing to do here!
+ CleanupLerp();
+ 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);
+
+ // How close is this from reaching the end?
+ var percentage = (float)Math.Abs(changeRot / changeNeeded);
+
+ currentEye.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)
+ {
+ CleanupLerp();
+ }
+
+ void CleanupLerp()
+ {
+ currentEye.Rotation = -_lerpTo.Value;
+ _lerpStartRotation = currentEye.Rotation;
+ _lerpTo = null;
+ _accumulator = 0f;
+ }
+ }
+ else
+ {
+ // This makes it so rotating the camera manually is impossible...
+ // However, it is needed. Why? Because of a funny (hilarious, even) race condition involving
+ // 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;
+ }
+ }
+}
diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs
index e9fbeafee1..2979af236b 100644
--- a/Content.Client/Physics/Controllers/MoverController.cs
+++ b/Content.Client/Physics/Controllers/MoverController.cs
@@ -5,6 +5,7 @@ using Content.Shared.Pulling.Components;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
+using Robust.Shared.Map;
using Robust.Shared.Physics;
namespace Content.Client.Physics.Controllers
@@ -19,11 +20,15 @@ namespace Content.Client.Physics.Controllers
if (_playerManager.LocalPlayer?.ControlledEntity is not {Valid: true} player ||
!EntityManager.TryGetComponent(player, out IMoverComponent? mover) ||
- !EntityManager.TryGetComponent(player, out PhysicsComponent? body))
+ !EntityManager.TryGetComponent(player, out PhysicsComponent? body) ||
+ !EntityManager.TryGetComponent(player, out TransformComponent? xform))
{
return;
}
+ if (xform.GridID != GridId.Invalid)
+ mover.LastGridAngle = GetParentGridAngle(xform, mover);
+
// Essentially we only want to set our mob to predicted so every other entity we just interpolate
// (i.e. only see what the server has sent us).
// The exception to this is joints.
diff --git a/Content.Shared/Movement/Components/SharedPlayerInputMoverComponent.cs b/Content.Shared/Movement/Components/SharedPlayerInputMoverComponent.cs
index 8dfdd9aaf6..f07b9651aa 100644
--- a/Content.Shared/Movement/Components/SharedPlayerInputMoverComponent.cs
+++ b/Content.Shared/Movement/Components/SharedPlayerInputMoverComponent.cs
@@ -50,6 +50,7 @@ namespace Content.Shared.Movement.Components
private MoveButtons _heldMoveButtons = MoveButtons.None;
+ [ViewVariables]
public Angle LastGridAngle { get; set; } = new(0);
public float CurrentWalkSpeed => _movementSpeed?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
diff --git a/Content.Shared/Movement/SharedMoverController.cs b/Content.Shared/Movement/SharedMoverController.cs
index 363f8a19d0..56bc2838af 100644
--- a/Content.Shared/Movement/SharedMoverController.cs
+++ b/Content.Shared/Movement/SharedMoverController.cs
@@ -5,6 +5,7 @@ using Content.Shared.Friction;
using Content.Shared.MobState.Components;
using Content.Shared.Movement.Components;
using Content.Shared.Pulling.Components;
+using JetBrains.Annotations;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -60,6 +61,14 @@ namespace Content.Shared.Movement
UsedMobMovement.Clear();
}
+ protected Angle GetParentGridAngle(TransformComponent xform, IMoverComponent mover)
+ {
+ if (xform.GridID == GridId.Invalid || !_mapManager.TryGetGrid(xform.GridID, out var grid))
+ return mover.LastGridAngle;
+
+ return grid.WorldRotation;
+ }
+
///
/// A generic kinematic mover for entities.
///
@@ -68,7 +77,7 @@ namespace Content.Shared.Movement
var (walkDir, sprintDir) = mover.VelocityDir;
var transform = EntityManager.GetComponent(mover.Owner);
- var parentRotation = transform.Parent!.WorldRotation;
+ var parentRotation = GetParentGridAngle(transform, mover);
// Regular movement.
// Target velocity.
@@ -118,7 +127,7 @@ namespace Content.Shared.Movement
if (!touching)
{
if (transform.GridID != GridId.Invalid)
- mover.LastGridAngle = transform.Parent!.WorldRotation;
+ mover.LastGridAngle = GetParentGridAngle(transform, mover);
transform.WorldRotation = physicsComponent.LinearVelocity.GetDir().ToAngle();
return;
@@ -130,7 +139,7 @@ namespace Content.Shared.Movement
// This is relative to the map / grid we're on.
var total = walkDir * mover.CurrentWalkSpeed + sprintDir * mover.CurrentSprintSpeed;
- var parentRotation = transform.Parent!.WorldRotation;
+ var parentRotation = GetParentGridAngle(transform, mover);
var worldTotal = _relativeMovement ? parentRotation.RotateVec(total) : total;