ContentReplayPlaybackManager.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. using Content.Client.Administration.Managers;
  2. using Content.Client.Launcher;
  3. using Content.Client.MainMenu;
  4. using Content.Client.Replay.Spectator;
  5. using Content.Client.Replay.UI.Loading;
  6. using Content.Client.UserInterface.Systems.Chat;
  7. using Content.Shared.Chat;
  8. using Content.Shared.Effects;
  9. using Content.Shared.GameTicking;
  10. using Content.Shared.GameWindow;
  11. using Content.Shared.Hands;
  12. using Content.Shared.Instruments;
  13. using Content.Shared.Popups;
  14. using Content.Shared.Projectiles;
  15. using Content.Shared.Weapons.Melee.Events;
  16. using Content.Shared.Weapons.Ranged.Events;
  17. using Content.Shared.Weapons.Ranged.Systems;
  18. using Robust.Client;
  19. using Robust.Client.Console;
  20. using Robust.Client.GameObjects;
  21. using Robust.Client.Player;
  22. using Robust.Client.Replays.Loading;
  23. using Robust.Client.Replays.Playback;
  24. using Robust.Client.State;
  25. using Robust.Client.Timing;
  26. using Robust.Client.UserInterface;
  27. using Robust.Shared;
  28. using Robust.Shared.Configuration;
  29. using Robust.Shared.ContentPack;
  30. using Robust.Shared.Serialization.Markdown.Mapping;
  31. using Robust.Shared.Utility;
  32. namespace Content.Client.Replay;
  33. public sealed class ContentReplayPlaybackManager
  34. {
  35. [Dependency] private readonly IStateManager _stateMan = default!;
  36. [Dependency] private readonly IClientGameTiming _timing = default!;
  37. [Dependency] private readonly IReplayLoadManager _loadMan = default!;
  38. [Dependency] private readonly IGameController _controller = default!;
  39. [Dependency] private readonly IClientEntityManager _entMan = default!;
  40. [Dependency] private readonly IUserInterfaceManager _uiMan = default!;
  41. [Dependency] private readonly IReplayPlaybackManager _playback = default!;
  42. [Dependency] private readonly IClientConGroupController _conGrp = default!;
  43. [Dependency] private readonly IClientAdminManager _adminMan = default!;
  44. [Dependency] private readonly IPlayerManager _player = default!;
  45. [Dependency] private readonly IBaseClient _client = default!;
  46. [Dependency] private readonly IConfigurationManager _cfg = default!;
  47. [Dependency] private readonly IResourceManager _resMan = default!;
  48. /// <summary>
  49. /// UI state to return to when stopping a replay or loading fails.
  50. /// </summary>
  51. public Type? DefaultState;
  52. public bool IsScreenshotMode = false;
  53. private bool _initialized;
  54. /// <summary>
  55. /// Most recently loaded file, for re-attempting the load with error tolerance.
  56. /// Required because the zip reader auto-disposes and I'm too lazy to change it so that
  57. /// <see cref="ReplayFileReaderZip"/> can re-open it.
  58. /// </summary>
  59. public (ResPath? Zip, ResPath Folder)? LastLoad;
  60. public void Initialize()
  61. {
  62. if (_initialized)
  63. return;
  64. _initialized = true;
  65. _playback.HandleReplayMessage += OnHandleReplayMessage;
  66. _playback.ReplayPlaybackStopped += OnReplayPlaybackStopped;
  67. _playback.ReplayPlaybackStarted += OnReplayPlaybackStarted;
  68. _playback.ReplayCheckpointReset += OnCheckpointReset;
  69. _loadMan.LoadOverride += LoadOverride;
  70. }
  71. private void LoadOverride(IReplayFileReader fileReader)
  72. {
  73. var screen = _stateMan.RequestStateChange<LoadingScreen<bool>>();
  74. screen.Job = new ContentLoadReplayJob(1 / 60f, fileReader, _loadMan, screen);
  75. screen.OnJobFinished += (_, e) => OnFinishedLoading(e);
  76. }
  77. private void OnFinishedLoading(Exception? exception)
  78. {
  79. if (exception == null)
  80. {
  81. LastLoad = null;
  82. return;
  83. }
  84. if (_client.RunLevel == ClientRunLevel.SinglePlayerGame)
  85. _client.StopSinglePlayer();
  86. Action? retryAction = null;
  87. Action? cancelAction = null;
  88. if (!_cfg.GetCVar(CVars.ReplayIgnoreErrors) && LastLoad is { } last)
  89. {
  90. retryAction = () =>
  91. {
  92. _cfg.SetCVar(CVars.ReplayIgnoreErrors, true);
  93. IReplayFileReader reader = last.Zip == null
  94. ? new ReplayFileReaderResources(_resMan, last.Folder)
  95. : new ReplayFileReaderZip(new(_resMan.UserData.OpenRead(last.Zip.Value)), last.Folder);
  96. _loadMan.LoadAndStartReplay(reader);
  97. };
  98. }
  99. // If we have an explicit menu to get back to (e.g. replay browser UI), show a cancel button.
  100. if (DefaultState != null)
  101. {
  102. cancelAction = () =>
  103. {
  104. _stateMan.RequestStateChange(DefaultState);
  105. };
  106. }
  107. // Switch to a new game state to present the error and cancel/retry options.
  108. var state = _stateMan.RequestStateChange<ReplayLoadingFailed>();
  109. state.SetData(exception, cancelAction, retryAction);
  110. }
  111. public void ReturnToDefaultState()
  112. {
  113. if (DefaultState != null)
  114. _stateMan.RequestStateChange(DefaultState);
  115. else if (_controller.LaunchState.FromLauncher)
  116. _stateMan.RequestStateChange<LauncherConnecting>().SetDisconnected();
  117. else
  118. _stateMan.RequestStateChange<MainScreen>();
  119. if (_client.RunLevel == ClientRunLevel.SinglePlayerGame)
  120. _client.StopSinglePlayer();
  121. }
  122. private void OnCheckpointReset()
  123. {
  124. // This function removes future chat messages when rewinding time.
  125. // TODO REPLAYS add chat messages when jumping forward in time.
  126. // Need to allow content to add data to checkpoint states.
  127. _uiMan.GetUIController<ChatUIController>().History.RemoveAll(x => x.Item1 > _timing.CurTick);
  128. _uiMan.GetUIController<ChatUIController>().Repopulate();
  129. }
  130. private bool OnHandleReplayMessage(object message, bool skipEffects)
  131. {
  132. // TODO REPLAYS figure out a cleaner way of doing this. This sucks.
  133. // Maybe wrap the event in another cancellable event and raise that?
  134. // This is where replays filter through networked messages and can choose to ignore or give them special treatment.
  135. // In particular, we want to avoid spamming pop-ups, sounds, and visual effect entities while fast forwarding.
  136. // E.g., when rewinding 1 tick, we really rewind back to the last checkpoint and then fast forward. Currently, this is
  137. // effectively an EntityEvent blacklist.
  138. switch (message)
  139. {
  140. case BoundUserInterfaceMessage: // TODO REPLAYS refactor BUIs
  141. case RequestWindowAttentionEvent:
  142. // Mark as handled -- the event won't get raised.
  143. return true;
  144. case TickerJoinGameEvent:
  145. if (!_entMan.EntityExists(_player.LocalEntity))
  146. _entMan.System<ReplaySpectatorSystem>().SetSpectatorPosition(default);
  147. return true;
  148. case ChatMessage chat:
  149. _uiMan.GetUIController<ChatUIController>().ProcessChatMessage(chat, speechBubble: !skipEffects);
  150. return true;
  151. }
  152. if (!skipEffects)
  153. {
  154. // Don't mark as handled -- the event get raised as a normal networked event.
  155. return false;
  156. }
  157. switch (message)
  158. {
  159. case RoundEndMessageEvent:
  160. case PopupEvent:
  161. case PickupAnimationEvent:
  162. case MeleeLungeEvent:
  163. case SharedGunSystem.HitscanEvent:
  164. case ImpactEffectEvent:
  165. case MuzzleFlashEvent:
  166. case ColorFlashEffectEvent:
  167. case InstrumentStartMidiEvent:
  168. case InstrumentMidiEventEvent:
  169. case InstrumentStopMidiEvent:
  170. // Block visual effects, pop-ups, and sounds
  171. return true;
  172. }
  173. return false;
  174. }
  175. private void OnReplayPlaybackStarted(MappingDataNode metadata, List<object> objects)
  176. {
  177. _conGrp.Implementation = new ReplayConGroup();
  178. }
  179. private void OnReplayPlaybackStopped()
  180. {
  181. _conGrp.Implementation = (IClientConGroupImplementation) _adminMan;
  182. ReturnToDefaultState();
  183. }
  184. }