EyeLerpingSystem.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. using System.Numerics;
  2. using Content.Shared.Movement.Components;
  3. using Content.Shared.Movement.Systems;
  4. using JetBrains.Annotations;
  5. using Robust.Client.GameObjects;
  6. using Robust.Client.Physics;
  7. using Robust.Client.Player;
  8. using Robust.Shared.Player;
  9. using Robust.Shared.Timing;
  10. namespace Content.Client.Eye;
  11. public sealed class EyeLerpingSystem : EntitySystem
  12. {
  13. [Dependency] private readonly IPlayerManager _playerManager = default!;
  14. [Dependency] private readonly IGameTiming _gameTiming = default!;
  15. [Dependency] private readonly SharedEyeSystem _eye = default!;
  16. [Dependency] private readonly SharedMoverController _mover = default!;
  17. [Dependency] private readonly SharedTransformSystem _transform = default!;
  18. // Convenience variable for for VV.
  19. [ViewVariables, UsedImplicitly]
  20. private IEnumerable<LerpingEyeComponent> ActiveEyes => EntityQuery<LerpingEyeComponent>();
  21. public override void Initialize()
  22. {
  23. base.Initialize();
  24. SubscribeLocalEvent<EyeComponent, ComponentStartup>(OnEyeStartup);
  25. SubscribeLocalEvent<EyeComponent, ComponentShutdown>(OnEyeShutdown);
  26. SubscribeLocalEvent<EyeAttachedEvent>(OnAttached);
  27. SubscribeLocalEvent<LerpingEyeComponent, EntParentChangedMessage>(HandleMapChange);
  28. SubscribeLocalEvent<LerpingEyeComponent, LocalPlayerDetachedEvent>(OnDetached);
  29. UpdatesAfter.Add(typeof(TransformSystem));
  30. UpdatesAfter.Add(typeof(PhysicsSystem));
  31. UpdatesBefore.Add(typeof(SharedEyeSystem));
  32. UpdatesOutsidePrediction = true;
  33. }
  34. private void OnEyeStartup(EntityUid uid, EyeComponent component, ComponentStartup args)
  35. {
  36. if (_playerManager.LocalEntity == uid)
  37. AddEye(uid, component, true);
  38. }
  39. private void OnEyeShutdown(EntityUid uid, EyeComponent component, ComponentShutdown args)
  40. {
  41. RemCompDeferred<LerpingEyeComponent>(uid);
  42. }
  43. // TODO replace this with some way of automatically getting and including any eyes that are associated with a viewport / render able thingy.
  44. public void AddEye(EntityUid uid, EyeComponent? component = null, bool automatic = false)
  45. {
  46. if (!Resolve(uid, ref component))
  47. return;
  48. var lerpInfo = EnsureComp<LerpingEyeComponent>(uid);
  49. lerpInfo.TargetRotation = GetRotation(uid);
  50. lerpInfo.LastRotation = lerpInfo.TargetRotation;
  51. lerpInfo.ManuallyAdded |= !automatic;
  52. lerpInfo.TargetZoom = component.Zoom;
  53. lerpInfo.LastZoom = lerpInfo.TargetZoom;
  54. if (component.Eye != null)
  55. {
  56. _eye.SetRotation(uid, lerpInfo.TargetRotation, component);
  57. _eye.SetZoom(uid, lerpInfo.TargetZoom, component);
  58. }
  59. }
  60. public void RemoveEye(EntityUid uid)
  61. {
  62. if (!TryComp(uid, out LerpingEyeComponent? lerp))
  63. return;
  64. // If this is the currently controlled entity, we keep the component.
  65. if (_playerManager.LocalEntity == uid)
  66. lerp.ManuallyAdded = false;
  67. else
  68. RemComp(uid, lerp);
  69. }
  70. private void HandleMapChange(EntityUid uid, LerpingEyeComponent component, ref EntParentChangedMessage args)
  71. {
  72. // Is this actually a map change? If yes, stop any lerps
  73. if (args.OldMapId != args.Transform.MapUid)
  74. component.LastRotation = GetRotation(uid, args.Transform);
  75. }
  76. private void OnAttached(ref EyeAttachedEvent ev)
  77. {
  78. AddEye(ev.Entity, ev.Component, true);
  79. }
  80. private void OnDetached(EntityUid uid, LerpingEyeComponent component, LocalPlayerDetachedEvent args)
  81. {
  82. if (!component.ManuallyAdded)
  83. RemCompDeferred(uid, component);
  84. }
  85. public override void Update(float frameTime)
  86. {
  87. base.Update(frameTime);
  88. if (!_gameTiming.IsFirstTimePredicted)
  89. return;
  90. // Set all of our eye rotations to the relevant values.
  91. var query = AllEntityQuery<LerpingEyeComponent, TransformComponent>();
  92. while (query.MoveNext(out var uid, out var lerpInfo, out var xform))
  93. {
  94. lerpInfo.LastRotation = lerpInfo.TargetRotation;
  95. lerpInfo.TargetRotation = GetRotation(uid, xform);
  96. lerpInfo.LastZoom = lerpInfo.TargetZoom;
  97. lerpInfo.TargetZoom = UpdateZoom(uid, frameTime);
  98. }
  99. }
  100. private Vector2 UpdateZoom(EntityUid uid, float frameTime, EyeComponent? eye = null, ContentEyeComponent? content = null)
  101. {
  102. if (!Resolve(uid, ref content, ref eye, false))
  103. return Vector2.One;
  104. var diff = content.TargetZoom - eye.Zoom;
  105. if (diff.LengthSquared() < 0.00001f)
  106. {
  107. return content.TargetZoom;
  108. }
  109. var change = diff * Math.Min(8f * frameTime, 1);
  110. return eye.Zoom + change;
  111. }
  112. /// <summary>
  113. /// Does the eye need to lerp or is its rotation matched.
  114. /// </summary>
  115. private bool NeedsLerp(InputMoverComponent? mover)
  116. {
  117. if (mover == null)
  118. return false;
  119. if (mover.RelativeRotation.Equals(mover.TargetRelativeRotation))
  120. return false;
  121. return true;
  122. }
  123. private Angle GetRotation(EntityUid uid, TransformComponent? xform = null, InputMoverComponent? mover = null)
  124. {
  125. if (!Resolve(uid, ref xform))
  126. return Angle.Zero;
  127. // If we can move then tie our eye to our inputs (these also get lerped so it should be fine).
  128. if (Resolve(uid, ref mover, false))
  129. {
  130. return -_mover.GetParentGridAngle(mover);
  131. }
  132. // if not tied to a mover then lock it to map / grid
  133. var relative = xform.GridUid ?? xform.MapUid;
  134. if (relative != null)
  135. return -_transform.GetWorldRotation(relative.Value);
  136. return Angle.Zero;
  137. }
  138. public override void FrameUpdate(float frameTime)
  139. {
  140. var tickFraction = (float) _gameTiming.TickFraction / ushort.MaxValue;
  141. const double lerpMinimum = 0.00001;
  142. var query = AllEntityQuery<LerpingEyeComponent, EyeComponent, TransformComponent>();
  143. while (query.MoveNext(out var entity, out var lerpInfo, out var eye, out var xform))
  144. {
  145. // Handle zoom
  146. var zoomDiff = Vector2.Lerp(lerpInfo.LastZoom, lerpInfo.TargetZoom, tickFraction);
  147. if ((zoomDiff - lerpInfo.TargetZoom).Length() < lerpMinimum)
  148. {
  149. _eye.SetZoom(entity, lerpInfo.TargetZoom, eye);
  150. }
  151. else
  152. {
  153. _eye.SetZoom(entity, zoomDiff, eye);
  154. }
  155. // Handle Rotation
  156. TryComp<InputMoverComponent>(entity, out var mover);
  157. // This needs to be recomputed every frame, as if this is simply the grid rotation, then we need to account for grid angle lerping.
  158. lerpInfo.TargetRotation = GetRotation(entity, xform, mover);
  159. if (!NeedsLerp(mover))
  160. {
  161. _eye.SetRotation(entity, lerpInfo.TargetRotation, eye);
  162. continue;
  163. }
  164. var shortest = Angle.ShortestDistance(lerpInfo.LastRotation, lerpInfo.TargetRotation);
  165. if (Math.Abs(shortest.Theta) < lerpMinimum)
  166. {
  167. _eye.SetRotation(entity, lerpInfo.TargetRotation, eye);
  168. continue;
  169. }
  170. _eye.SetRotation(entity, shortest * tickFraction + lerpInfo.LastRotation, eye);
  171. }
  172. }
  173. }