Scenario play-test returns to the screen the editor was opened from

The play-test loads the game shell, which frees the screen the editor was
opened from (the novel_editor hub). Both the scenario's end and Stop used to
switch to the engine's main menu; the editor now remembers the route it left
and switches back to it, falling back to the main menu only when it was not
opened from a route. The tools_hub run of the windows suite plays its own
scenario and checks both ways back.
This commit is contained in:
cthangover committed 2026-10-17 21:44:34 +02:00
1 parent 55ef9416ef
commit 7b6ae80616
4 files changed
+140 -5

No files matched your search

+12 -1
View File
@@ -36,6 +36,7 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor
private Label _filePathLabel; private Label _filePathLabel;
private bool _playActive; private bool _playActive;
private string _returnRoute;
private CanvasLayer _stopOverlay; private CanvasLayer _stopOverlay;
public ScenarioEditorWindow() : base("tools/scenario_editor/title") public ScenarioEditorWindow() : base("tools/scenario_editor/title")
@@ -294,6 +295,13 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor
Visible = false; 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<GodotSceneService>("/root/GodotSceneService");
var current = sceneService?.CurrentGodotSceneName;
_returnRoute = !string.IsNullOrEmpty(current) && SceneRouteRegistry.TryGet(current, out _) ? current : null;
_stopOverlay = CreateStopOverlay(); _stopOverlay = CreateStopOverlay();
var tree = Godot.Engine.GetMainLoop() as SceneTree; var tree = Godot.Engine.GetMainLoop() as SceneTree;
tree?.Root.AddChild(_stopOverlay); tree?.Root.AddChild(_stopOverlay);
@@ -305,7 +313,6 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor
sceneManager.PendingSceneName = metadata.Scene; sceneManager.PendingSceneName = metadata.Scene;
} }
var sceneService = GetNodeOrNull<GodotSceneService>("/root/GodotSceneService");
sceneService?.LoadShell(); sceneService?.LoadShell();
_playActive = true; _playActive = true;
@@ -329,7 +336,11 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor
} }
var sceneService = GetNodeOrNull<GodotSceneService>("/root/GodotSceneService"); var sceneService = GetNodeOrNull<GodotSceneService>("/root/GodotSceneService");
if (_returnRoute != null && SceneRouteRegistry.TryGet(_returnRoute, out var route))
sceneService?.SwitchRoute(route);
else
sceneService?.SwitchToMenu(); sceneService?.SwitchToMenu();
_returnRoute = null;
SetStatus(ToolUi.Tr("tools/scenario_editor/play_stopped")); SetStatus(ToolUi.Tr("tools/scenario_editor/play_stopped"));
} }
@@ -0,0 +1,5 @@
scene: home_basement
priority: 0
---
delay 60 hidden=true
end
@@ -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
{
/// <summary>
/// Play-test round trip of the scenario editor opened from the
/// <see cref="NovelEditorHub"/>: 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
/// (<see cref="SceneManager.IsTestPlayActive"/> 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; <see cref="Tick"/> returns true when done.
/// </summary>
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<bool, string> _check;
private ScenarioEditorWindow _window;
private int _step;
private int _settle;
/// <summary>Creates the check; <paramref name="check"/> records one assertion.</summary>
public ScenarioPlayReturnCheck(Action<bool, string> check)
{
_check = check;
}
/// <summary>Advances the round trip by one step; true once finished.</summary>
public bool Tick(SceneTree tree)
{
switch (_step)
{
case 0:
_window = (ScenarioEditorWindow)ToolWindow.ShowWindow(new ScenarioEditorWindow());
var tabs = _window.FindChildren("*", "", true, false).OfType<ScenarioEditTabs>().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;
}
/// <summary>The play-test left the hub for the game shell and the shell settled.</summary>
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<Button>().FirstOrDefault(b => b.Text == text);
button?.EmitSignal(BaseButton.SignalName.Pressed);
}
}
}
@@ -23,7 +23,9 @@ namespace Cthangover.Tools.Tests.Windows
/// Launched as the <c>novel_editor</c> game (<c>--game=novel_editor --new</c>) /// Launched as the <c>novel_editor</c> game (<c>--game=novel_editor --new</c>)
/// it first waits for the collection's entry scene, the /// it first waits for the collection's entry scene, the
/// <see cref="NovelEditorHub"/>, and checks it lists a launcher button for /// <see cref="NovelEditorHub"/>, and checks it lists a launcher button for
/// every registered tool before opening the windows. /// every registered tool before opening the windows; after the windows it
/// plays a scenario from the editor and checks that both the scenario's
/// end and Stop return to the hub (<see cref="ScenarioPlayReturnCheck"/>).
/// </summary> /// </summary>
public partial class ToolWindowsTestDriverNode : Node public partial class ToolWindowsTestDriverNode : Node
{ {
@@ -42,6 +44,8 @@ namespace Cthangover.Tools.Tests.Windows
private double _elapsed; private double _elapsed;
private bool _finished; private bool _finished;
private Window _window; private Window _window;
private bool _inHub;
private ScenarioPlayReturnCheck _playCheck;
private readonly ToolWindowsErrorLogger _errors = new(); private readonly ToolWindowsErrorLogger _errors = new();
/// <summary>Creates the driver; <paramref name="shotsDir"/> null or empty disables screenshots.</summary> /// <summary>Creates the driver; <paramref name="shotsDir"/> null or empty disables screenshots.</summary>
@@ -73,18 +77,25 @@ namespace Cthangover.Tools.Tests.Windows
if (!Boot.IsReady || tree?.CurrentScene == null) if (!Boot.IsReady || tree?.CurrentScene == null)
return; return;
var inHub = ActiveGame.Id == NovelEditorHub.SceneName; _inHub = ActiveGame.Id == NovelEditorHub.SceneName;
if (inHub && tree.CurrentScene is not NovelEditorHub) if (_inHub && tree.CurrentScene is not NovelEditorHub)
return; return;
_tools.AddRange(ToolFactory.Instance.GetAll()); _tools.AddRange(ToolFactory.Instance.GetAll());
Check(_tools.Count > 0, $"tools registered ({_tools.Count})"); Check(_tools.Count > 0, $"tools registered ({_tools.Count})");
if (inHub) if (_inHub)
CheckHub(tree.CurrentScene); CheckHub(tree.CurrentScene);
OpenNext(); OpenNext();
return; return;
} }
if (_playCheck != null)
{
if (_playCheck.Tick(GetTree()))
Finish();
return;
}
if (_window == null) if (_window == null)
return; return;
@@ -112,6 +123,9 @@ namespace Cthangover.Tools.Tests.Windows
_toolIndex++; _toolIndex++;
if (_toolIndex >= _tools.Count) if (_toolIndex >= _tools.Count)
{ {
if (_inHub)
_playCheck = new ScenarioPlayReturnCheck(Check);
else
Finish(); Finish();
return; return;
} }