diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 6e9d8c4021e..7dde4557cd8 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -70,6 +70,7 @@ public sealed class EntryPoint : GameClient [Dependency] private readonly IResourceManager _resourceManager = default!; [Dependency] private readonly IReplayLoadManager _replayLoad = default!; [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly ContentReplayPlaybackManager _replayMan = default!; public override void Init() { @@ -191,6 +192,7 @@ private void SwitchToDefaultState(bool disconnected = false) _resourceManager, ReplayConstants.ReplayZipFolder.ToRootedPath()); + _replayMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath()); _replayLoad.LoadAndStartReplay(reader); } else if (_gameController.LaunchState.FromLauncher) diff --git a/Content.Client/Replay/ContentReplayPlaybackManager.cs b/Content.Client/Replay/ContentReplayPlaybackManager.cs index bc979575f58..f90731bfa75 100644 --- a/Content.Client/Replay/ContentReplayPlaybackManager.cs +++ b/Content.Client/Replay/ContentReplayPlaybackManager.cs @@ -1,8 +1,10 @@ +using System.IO.Compression; using Content.Client.Administration.Managers; using Content.Client.Launcher; using Content.Client.MainMenu; using Content.Client.Replay.Spectator; using Content.Client.Replay.UI.Loading; +using Content.Client.Stylesheets; using Content.Client.UserInterface.Systems.Chat; using Content.Shared.Chat; using Content.Shared.Effects; @@ -24,7 +26,13 @@ using Robust.Client.State; using Robust.Client.Timing; using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared; +using Robust.Shared.Configuration; +using Robust.Shared.ContentPack; using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Utility; namespace Content.Client.Replay; @@ -41,6 +49,8 @@ public sealed class ContentReplayPlaybackManager [Dependency] private readonly IClientAdminManager _adminMan = default!; [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IBaseClient _client = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IResourceManager _resMan = default!; /// /// UI state to return to when stopping a replay or loading fails. @@ -50,6 +60,13 @@ public sealed class ContentReplayPlaybackManager public bool IsScreenshotMode = false; private bool _initialized; + + /// + /// Most recently loaded file, for re-attempting the load with error tolerance. + /// Required because the zip reader auto-disposes and I'm too lazy to change it so that + /// can re-open it. + /// + public (ResPath? Zip, ResPath Folder)? LastLoad; public void Initialize() { @@ -73,11 +90,50 @@ private void LoadOverride(IReplayFileReader fileReader) private void OnFinishedLoading(Exception? exception) { - if (exception != null) + if (exception == null) + { + LastLoad = null; + return; + } + + ReturnToDefaultState(); + + // Show a popup window with the error message + var text = Loc.GetString("replay-loading-failed", ("reason", exception)); + var box = new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Vertical, + Children = {new Label {Text = text}} + }; + + var popup = new DefaultWindow { Title = "Error!" }; + popup.Contents.AddChild(box); + + // Add button for attempting to re-load the replay while ignoring some errors. + if (!_cfg.GetCVar(CVars.ReplayIgnoreErrors) && LastLoad is {} last) { - ReturnToDefaultState(); - _uiMan.Popup(Loc.GetString("replay-loading-failed", ("reason", exception))); + var button = new Button + { + Text = Loc.GetString("replay-loading-retry"), + StyleClasses = { StyleBase.ButtonCaution } + }; + + button.OnPressed += _ => + { + _cfg.SetCVar(CVars.ReplayIgnoreErrors, true); + popup.Dispose(); + + IReplayFileReader reader = last.Zip == null + ? new ReplayFileReaderResources(_resMan, last.Folder) + : new ReplayFileReaderZip(new(_resMan.UserData.OpenRead(last.Zip.Value)), last.Folder); + + _loadMan.LoadAndStartReplay(reader); + }; + + box.AddChild(button); } + + popup.OpenCentered(); } public void ReturnToDefaultState() diff --git a/Content.Replay/Menu/ReplayMainMenu.cs b/Content.Replay/Menu/ReplayMainMenu.cs index 8bd99f82fb2..85c39c59dac 100644 --- a/Content.Replay/Menu/ReplayMainMenu.cs +++ b/Content.Replay/Menu/ReplayMainMenu.cs @@ -1,6 +1,7 @@ using System.IO.Compression; using System.Linq; using Content.Client.Message; +using Content.Client.Replay; using Content.Client.UserInterface.Systems.EscapeMenu; using Robust.Client; using Robust.Client.Replays.Loading; @@ -31,6 +32,7 @@ public sealed class ReplayMainScreen : State [Dependency] private readonly IGameController _controllerProxy = default!; [Dependency] private readonly IClientRobustSerializer _serializer = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly ContentReplayPlaybackManager _replayMan = default!; private ReplayMainMenuControl _mainMenuControl = default!; private SelectReplayWindow? _selectWindow; @@ -207,12 +209,13 @@ private void OnFolderPressed(BaseButton.ButtonEventArgs obj) private void OnLoadPressed(BaseButton.ButtonEventArgs obj) { - if (_selected.HasValue) - { - var fileReader = new ReplayFileReaderZip( - new ZipArchive(_resMan.UserData.OpenRead(_selected.Value)), ReplayZipFolder); - _loadMan.LoadAndStartReplay(fileReader); - } + if (!_selected.HasValue) + return; + + _replayMan.LastLoad = (_selected.Value, ReplayZipFolder); + var fileReader = new ReplayFileReaderZip( + new ZipArchive(_resMan.UserData.OpenRead(_selected.Value)), ReplayZipFolder); + _loadMan.LoadAndStartReplay(fileReader); } private void RefreshReplays() diff --git a/Resources/Locale/en-US/replays/replays.ftl b/Resources/Locale/en-US/replays/replays.ftl index 560285cbb1b..7a7e551b3e9 100644 --- a/Resources/Locale/en-US/replays/replays.ftl +++ b/Resources/Locale/en-US/replays/replays.ftl @@ -6,8 +6,9 @@ replay-loading-processing = Processing Files replay-loading-spawning = Spawning Entities replay-loading-initializing = Initializing Entities replay-loading-starting= Starting Entities -replay-loading-failed = Failed to load replay: +replay-loading-failed = Failed to load replay. Error: {$reason} +replay-loading-retry = Try load with more exception tolerance - MAY CAUSE BUGS! # Main Menu replay-menu-subtext = Replay Client