From b6ca604a661e2b98ed9b3e9a8f5ce88a7cd49cfa Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 13 Sep 2024 15:58:02 +0200 Subject: [PATCH] Fix replay load error screens (#32115) If an error occurs while loading a replay, it's *supposed* to show a popup "hey do you wanna continue with error tolerance", and it does do this. But because of spaghetti in the replay state code, it also immediately tries to reload the replay without input, as a consequence of trying to reset to the game's default state. Now the replay load system has a proper game state for "replay load failed", with nice presentation and explicit formatting and 10% less italian cuisine. --- .../Replay/ContentReplayPlaybackManager.cs | 50 ++++++++----------- .../Replay/UI/Loading/ReplayLoadingFailed.cs | 36 +++++++++++++ .../Loading/ReplayLoadingFailedControl.xaml | 14 ++++++ .../ReplayLoadingFailedControl.xaml.cs | 44 ++++++++++++++++ Resources/Locale/en-US/replays/replays.ftl | 1 + 5 files changed, 115 insertions(+), 30 deletions(-) create mode 100644 Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs create mode 100644 Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml create mode 100644 Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml.cs diff --git a/Content.Client/Replay/ContentReplayPlaybackManager.cs b/Content.Client/Replay/ContentReplayPlaybackManager.cs index f90731bfa7..b96eae44e9 100644 --- a/Content.Client/Replay/ContentReplayPlaybackManager.cs +++ b/Content.Client/Replay/ContentReplayPlaybackManager.cs @@ -1,10 +1,8 @@ -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; @@ -26,8 +24,6 @@ using Robust.Client.Replays.Playback; 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; @@ -60,7 +56,7 @@ 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 @@ -96,32 +92,17 @@ public sealed class ContentReplayPlaybackManager return; } - ReturnToDefaultState(); + if (_client.RunLevel == ClientRunLevel.SinglePlayerGame) + _client.StopSinglePlayer(); - // Show a popup window with the error message - var text = Loc.GetString("replay-loading-failed", ("reason", exception)); - var box = new BoxContainer + Action? retryAction = null; + Action? cancelAction = null; + + if (!_cfg.GetCVar(CVars.ReplayIgnoreErrors) && LastLoad is { } last) { - 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) - { - var button = new Button - { - Text = Loc.GetString("replay-loading-retry"), - StyleClasses = { StyleBase.ButtonCaution } - }; - - button.OnPressed += _ => + retryAction = () => { _cfg.SetCVar(CVars.ReplayIgnoreErrors, true); - popup.Dispose(); IReplayFileReader reader = last.Zip == null ? new ReplayFileReaderResources(_resMan, last.Folder) @@ -129,11 +110,20 @@ public sealed class ContentReplayPlaybackManager _loadMan.LoadAndStartReplay(reader); }; - - box.AddChild(button); } - popup.OpenCentered(); + // If we have an explicit menu to get back to (e.g. replay browser UI), show a cancel button. + if (DefaultState != null) + { + cancelAction = () => + { + _stateMan.RequestStateChange(DefaultState); + }; + } + + // Switch to a new game state to present the error and cancel/retry options. + var state = _stateMan.RequestStateChange(); + state.SetData(exception, cancelAction, retryAction); } public void ReturnToDefaultState() diff --git a/Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs b/Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs new file mode 100644 index 0000000000..223895eb29 --- /dev/null +++ b/Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs @@ -0,0 +1,36 @@ +using Content.Client.Stylesheets; +using Robust.Client.State; +using Robust.Client.UserInterface; +using Robust.Shared.Utility; + +namespace Content.Client.Replay.UI.Loading; + +/// +/// State used to display an error message if a replay failed to load. +/// +/// +/// +public sealed class ReplayLoadingFailed : State +{ + [Dependency] private readonly IStylesheetManager _stylesheetManager = default!; + [Dependency] private readonly IUserInterfaceManager _userInterface = default!; + + private ReplayLoadingFailedControl? _control; + + public void SetData(Exception exception, Action? cancelPressed, Action? retryPressed) + { + DebugTools.Assert(_control != null); + _control.SetData(exception, cancelPressed, retryPressed); + } + + protected override void Startup() + { + _control = new ReplayLoadingFailedControl(_stylesheetManager); + _userInterface.StateRoot.AddChild(_control); + } + + protected override void Shutdown() + { + _control?.Orphan(); + } +} diff --git a/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml b/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml new file mode 100644 index 0000000000..5f77a66e53 --- /dev/null +++ b/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml @@ -0,0 +1,14 @@ + + + + + + + + +