ReplaySpectatorSystem.Movement.cs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. using Content.Shared.Movement.Components;
  2. using Robust.Shared.Input;
  3. using Robust.Shared.Input.Binding;
  4. using Robust.Shared.Map;
  5. using Robust.Shared.Player;
  6. namespace Content.Client.Replay.Spectator;
  7. // Partial class handles movement logic for observers.
  8. public sealed partial class ReplaySpectatorSystem
  9. {
  10. public DirectionFlag Direction;
  11. /// <summary>
  12. /// Fallback speed if the observer ghost has no <see cref="MovementSpeedModifierComponent"/>.
  13. /// </summary>
  14. public const float DefaultSpeed = 12;
  15. private void InitializeMovement()
  16. {
  17. var moveUpCmdHandler = new MoverHandler(this, DirectionFlag.North);
  18. var moveLeftCmdHandler = new MoverHandler(this, DirectionFlag.West);
  19. var moveRightCmdHandler = new MoverHandler(this, DirectionFlag.East);
  20. var moveDownCmdHandler = new MoverHandler(this, DirectionFlag.South);
  21. CommandBinds.Builder
  22. .Bind(EngineKeyFunctions.MoveUp, moveUpCmdHandler)
  23. .Bind(EngineKeyFunctions.MoveLeft, moveLeftCmdHandler)
  24. .Bind(EngineKeyFunctions.MoveRight, moveRightCmdHandler)
  25. .Bind(EngineKeyFunctions.MoveDown, moveDownCmdHandler)
  26. .Register<ReplaySpectatorSystem>();
  27. }
  28. private void ShutdownMovement()
  29. {
  30. CommandBinds.Unregister<ReplaySpectatorSystem>();
  31. }
  32. // Normal mover code works via physics. Replays don't do prediction/physics. You can fudge it by relying on the
  33. // fact that only local-player physics is currently predicted, but instead I've just added crude mover logic here.
  34. // This just runs on frame updates, no acceleration or friction here.
  35. public override void FrameUpdate(float frameTime)
  36. {
  37. if (_replayPlayback.Replay == null)
  38. return;
  39. if (_player.LocalEntity is not { } player)
  40. return;
  41. if (Direction == DirectionFlag.None)
  42. {
  43. if (TryComp(player, out InputMoverComponent? cmp))
  44. _mover.LerpRotation(player, cmp, frameTime);
  45. return;
  46. }
  47. if (!IsClientSide(player) || !HasComp<ReplaySpectatorComponent>(player))
  48. {
  49. // Player is trying to move -> behave like the ghost-on-move component.
  50. SpawnSpectatorGhost(new EntityCoordinates(player, default), true);
  51. return;
  52. }
  53. if (!TryComp(player, out InputMoverComponent? mover))
  54. return;
  55. _mover.LerpRotation(player, mover, frameTime);
  56. var effectiveDir = Direction;
  57. if ((Direction & DirectionFlag.North) != 0)
  58. effectiveDir &= ~DirectionFlag.South;
  59. if ((Direction & DirectionFlag.East) != 0)
  60. effectiveDir &= ~DirectionFlag.West;
  61. var query = GetEntityQuery<TransformComponent>();
  62. var xform = query.GetComponent(player);
  63. var pos = _transform.GetWorldPosition(xform);
  64. if (!xform.ParentUid.IsValid())
  65. {
  66. // Were they sitting on a grid as it was getting deleted?
  67. SetSpectatorPosition(default);
  68. return;
  69. }
  70. // A poor mans grid-traversal system. Should also interrupt ghost-following.
  71. // This is very hacky and has already caused bugs.
  72. // This is done the way it is because grid traversal gets processed in physics' SimulateWorld() update.
  73. // TODO do this properly somehow.
  74. _transform.SetGridId(player, xform, null);
  75. _transform.AttachToGridOrMap(player);
  76. if (xform.ParentUid.IsValid())
  77. _transform.SetGridId(player, xform, Transform(xform.ParentUid).GridUid);
  78. var parentRotation = _mover.GetParentGridAngle(mover);
  79. var localVec = effectiveDir.AsDir().ToAngle().ToWorldVec();
  80. var worldVec = parentRotation.RotateVec(localVec);
  81. var speed = CompOrNull<MovementSpeedModifierComponent>(player)?.BaseSprintSpeed ?? DefaultSpeed;
  82. var delta = worldVec * frameTime * speed;
  83. _transform.SetWorldPositionRotation(player, pos + delta, delta.ToWorldAngle(), xform);
  84. }
  85. private sealed class MoverHandler : InputCmdHandler
  86. {
  87. private readonly ReplaySpectatorSystem _sys;
  88. private readonly DirectionFlag _dir;
  89. public MoverHandler(ReplaySpectatorSystem sys, DirectionFlag dir)
  90. {
  91. _sys = sys;
  92. _dir = dir;
  93. }
  94. public override bool HandleCmdMessage(IEntityManager entManager, ICommonSession? session, IFullInputCmdMessage message)
  95. {
  96. if (message.State == BoundKeyState.Down)
  97. _sys.Direction |= _dir;
  98. else
  99. _sys.Direction &= ~_dir;
  100. return true;
  101. }
  102. }
  103. }