diff --git a/src/ScenarioEditor/ScenarioEditorWindow.cs b/src/ScenarioEditor/ScenarioEditorWindow.cs index a4a9a70..c17fb14 100644 --- a/src/ScenarioEditor/ScenarioEditorWindow.cs +++ b/src/ScenarioEditor/ScenarioEditorWindow.cs @@ -36,6 +36,7 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor private Label _filePathLabel; private bool _playActive; + private string _returnRoute; private CanvasLayer _stopOverlay; public ScenarioEditorWindow() : base("tools/scenario_editor/title") @@ -294,6 +295,13 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor Visible = false; + // The screen the editor was opened from (the novel_editor hub, a + // mod menu) is freed by the shell load below; remember its route so + // the play-test returns there instead of to the engine's main menu. + var sceneService = GetNodeOrNull("/root/GodotSceneService"); + var current = sceneService?.CurrentGodotSceneName; + _returnRoute = !string.IsNullOrEmpty(current) && SceneRouteRegistry.TryGet(current, out _) ? current : null; + _stopOverlay = CreateStopOverlay(); var tree = Godot.Engine.GetMainLoop() as SceneTree; tree?.Root.AddChild(_stopOverlay); @@ -305,7 +313,6 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor sceneManager.PendingSceneName = metadata.Scene; } - var sceneService = GetNodeOrNull("/root/GodotSceneService"); sceneService?.LoadShell(); _playActive = true; @@ -329,7 +336,11 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor } var sceneService = GetNodeOrNull("/root/GodotSceneService"); - sceneService?.SwitchToMenu(); + if (_returnRoute != null && SceneRouteRegistry.TryGet(_returnRoute, out var route)) + sceneService?.SwitchRoute(route); + else + sceneService?.SwitchToMenu(); + _returnRoute = null; SetStatus(ToolUi.Tr("tools/scenario_editor/play_stopped")); } diff --git a/test_suites/windows/scenarios/playtest_return.scenario b/test_suites/windows/scenarios/playtest_return.scenario new file mode 100644 index 0000000..b7e948e --- /dev/null +++ b/test_suites/windows/scenarios/playtest_return.scenario @@ -0,0 +1,5 @@ +scene: home_basement +priority: 0 +--- +delay 60 hidden=true +end diff --git a/test_suites/windows/src/ScenarioPlayReturnCheck.cs b/test_suites/windows/src/ScenarioPlayReturnCheck.cs new file mode 100644 index 0000000..e8a422b --- /dev/null +++ b/test_suites/windows/src/ScenarioPlayReturnCheck.cs @@ -0,0 +1,105 @@ +using System; +using System.Linq; +using Cthangover.Core.Scenes; +using Cthangover.Core.UI.Tool.ScenarioEditor; +using Cthangover.Mods.Interface.Tool; +using Cthangover.Tools.Common; +using Cthangover.Tools.Hub; +using Cthangover.Tools.Services; +using Godot; + +namespace Cthangover.Tools.Tests.Windows +{ + /// + /// Play-test round trip of the scenario editor opened from the + /// : plays this suite's own scenario (so the + /// editor's save-before-play rewrites a test file, not game content), lets + /// the play end the way a finished dialog ends it + /// ( drops), then plays again and + /// presses Stop. Both times the game must come back to the hub with the + /// editor visible — not to the engine's main menu. Advanced one step per + /// driver tick; returns true when done. + /// + public class ScenarioPlayReturnCheck + { + private const string SuiteModId = "tools__windows"; + private const string ScenarioPath = "scenarios/playtest_return.scenario"; + private const int ShellSettleTicks = 3; + + private readonly Action _check; + private ScenarioEditorWindow _window; + private int _step; + private int _settle; + + /// Creates the check; records one assertion. + public ScenarioPlayReturnCheck(Action check) + { + _check = check; + } + + /// Advances the round trip by one step; true once finished. + public bool Tick(SceneTree tree) + { + switch (_step) + { + case 0: + _window = (ScenarioEditorWindow)ToolWindow.ShowWindow(new ScenarioEditorWindow()); + var tabs = _window.FindChildren("*", "", true, false).OfType().FirstOrDefault(); + tabs?.OpenFile(ScenarioFileService.MakeKey(SuiteModId, ScenarioPath)); + _check(tabs?.HasActiveFile == true, "scenario_editor: test scenario opened"); + break; + + case 1: + case 4: + PressButton(_window, "tools/scenario_editor/play"); + _settle = ShellSettleTicks; + break; + + case 2: + if (!InGame(tree)) + return false; + SceneManager.IsTestPlayActive = false; + break; + + case 5: + if (!InGame(tree)) + return false; + PressButton(tree.Root, "tools/scenario_editor/stop"); + break; + + case 3: + case 6: + if (tree.CurrentScene is not NovelEditorHub || !_window.Visible) + return false; + _check(true, _step == 3 + ? "scenario_editor: the scenario's end returns to the editor hub" + : "scenario_editor: Stop returns to the editor hub"); + if (_step == 6) + { + _window.QueueFree(); + return true; + } + break; + } + + _step++; + return false; + } + + /// The play-test left the hub for the game shell and the shell settled. + private bool InGame(SceneTree tree) + { + if (tree.CurrentScene is NovelEditorHub || GodotSceneService.IsTransitioning || --_settle > 0) + return false; + _check(!_window.Visible, "scenario_editor: play hides the editor and enters the game"); + return true; + } + + private static void PressButton(Node root, string textKey) + { + var text = ToolUi.Tr(textKey); + var button = root.FindChildren("*", nameof(Button), true, false).OfType