Event-driven dialog queue, story flag command, mod unit tests
Push-based event chains, runtime fixes (single end, goto lifecycle, if, busy box, held delay, empty options, timed/animated/effect actions), the 'flag name value' DSL command, autoplay --dialogs=N, 19 mod unit tests.
This commit is contained in:
1 parent
9f86bdbb0c
commit
bd55716349
86 files changed
+3173
-2181
No files matched your search
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Cthangover.Core.Events;
|
||||
using Cthangover.Core.UI.Dialog;
|
||||
using Cthangover.Core.Utils;
|
||||
using Godot;
|
||||
@@ -23,6 +24,9 @@ namespace Mods.TestRunner
|
||||
/// <c>IsDialogEnded</c> истинно до загрузки первой очереди, поэтому
|
||||
/// end-обработка ждёт, пока диалог реально наблюдался запущенным, плюс
|
||||
/// стартовая пауза 0.5с на построение сцены.
|
||||
/// With <c>--dialogs=N</c> the run passes after N <see cref="DialogEndedEvent"/>s
|
||||
/// instead: hub games never go idle, because the next scene's dialog starts
|
||||
/// in the same frame the previous one ends.
|
||||
/// </summary>
|
||||
public partial class DialogAutoDriverNode : Node
|
||||
{
|
||||
@@ -40,6 +44,7 @@ namespace Mods.TestRunner
|
||||
|
||||
private readonly List<int> choices;
|
||||
private readonly float? timeoutSeconds;
|
||||
private readonly int? dialogsTarget;
|
||||
|
||||
private IDialogBox dialogBox;
|
||||
private int choiceIndex;
|
||||
@@ -47,6 +52,7 @@ namespace Mods.TestRunner
|
||||
private double dialogFoundAtSeconds;
|
||||
private bool hasSeenRunningDialog;
|
||||
private bool finished;
|
||||
private int endedDialogs;
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт драйвер; <paramref name="choices"/> — индексы вариантов по
|
||||
@@ -56,11 +62,16 @@ namespace Mods.TestRunner
|
||||
/// вне source-генератора Godot): WaitTime 0.1с с автостартом
|
||||
/// воспроизводит ритм оригинального драйвера, а сигнал
|
||||
/// <c>Timer.Timeout</c> — Godot-сигнал и работает из мод-сборок.
|
||||
/// <paramref name="dialogsTarget"/> — number of finished dialogs that passes
|
||||
/// the run (null — pass when the box goes idle).
|
||||
/// </summary>
|
||||
public DialogAutoDriverNode(List<int> choices, float? timeoutSeconds)
|
||||
public DialogAutoDriverNode(List<int> choices, float? timeoutSeconds, int? dialogsTarget)
|
||||
{
|
||||
this.choices = choices ?? new List<int>();
|
||||
this.timeoutSeconds = timeoutSeconds;
|
||||
this.dialogsTarget = dialogsTarget;
|
||||
if (dialogsTarget.HasValue)
|
||||
ModEventBus.Subscribe<DialogEndedEvent>(OnDialogEnded);
|
||||
|
||||
var timer = new Timer
|
||||
{
|
||||
@@ -112,7 +123,7 @@ namespace Mods.TestRunner
|
||||
var waitingForFirstDialog = !hasSeenRunningDialog
|
||||
&& elapsedSeconds - dialogFoundAtSeconds < FirstDialogGraceSeconds;
|
||||
|
||||
if (dialogBox.IsDialogEnded && !waitingForFirstDialog)
|
||||
if (!dialogsTarget.HasValue && dialogBox.IsDialogEnded && !waitingForFirstDialog)
|
||||
{
|
||||
GameLogger.Log("TEST", "DialogAutoDriverNode: dialog ended, quitting");
|
||||
Finish(1, 0, null);
|
||||
@@ -128,6 +139,15 @@ namespace Mods.TestRunner
|
||||
dialogBox.NextAction();
|
||||
}
|
||||
|
||||
/// <summary>Counts finished dialogs and passes the run once the --dialogs= target is reached.</summary>
|
||||
private void OnDialogEnded(DialogEndedEvent evt)
|
||||
{
|
||||
endedDialogs++;
|
||||
GameLogger.Log("TEST", $"DialogAutoDriverNode: dialog #{endedDialogs} ended");
|
||||
if (endedDialogs >= dialogsTarget.Value)
|
||||
Finish(1, 0, $"{endedDialogs} dialog(s) ended, quitting");
|
||||
}
|
||||
|
||||
/// <summary>Выбирает показанный вариант по индексу из --choices= (вне диапазона — первый).</summary>
|
||||
private void HandleSelection()
|
||||
{
|
||||
@@ -167,6 +187,8 @@ namespace Mods.TestRunner
|
||||
if (finished)
|
||||
return;
|
||||
finished = true;
|
||||
if (dialogsTarget.HasValue)
|
||||
ModEventBus.Unsubscribe<DialogEndedEvent>(OnDialogEnded);
|
||||
|
||||
if (reason != null)
|
||||
GameLogger.Log("TEST", $"DialogAutoDriverNode: {reason}");
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace Mods.TestRunner
|
||||
|
||||
var choices = ParseChoices(args);
|
||||
var timeout = ParseTimeout(args);
|
||||
var dialogs = ParseDialogs(args);
|
||||
|
||||
// Godot. квалифицирован: в dev-компиляции модов (ToolsModCompileTests)
|
||||
// в глобальном пространстве имён существует namespace Engine из тестовой сборки,
|
||||
@@ -57,12 +58,12 @@ namespace Mods.TestRunner
|
||||
|
||||
driverQueued = true;
|
||||
GameLogger.Log("TEST",
|
||||
$"TestRunnerInitializer: activating dialog auto-driver, choices=[{string.Join(",", choices)}], timeout={timeout?.ToString() ?? "none"}s");
|
||||
$"TestRunnerInitializer: activating dialog auto-driver, choices=[{string.Join(",", choices)}], timeout={timeout?.ToString() ?? "none"}s, dialogs={dialogs?.ToString() ?? "idle"}");
|
||||
|
||||
// Deferred: сцена ещё строится; драйвер внутри сам выдерживает
|
||||
// стартовую паузу и лениво ищет DialogBox, поэтому порядок с
|
||||
// загрузкой сцены не важен.
|
||||
root.CallDeferred("add_child", new DialogAutoDriverNode(choices, timeout));
|
||||
root.CallDeferred("add_child", new DialogAutoDriverNode(choices, timeout, dialogs));
|
||||
}
|
||||
|
||||
private static List<int> ParseChoices(string[] args)
|
||||
@@ -80,6 +81,15 @@ namespace Mods.TestRunner
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static int? ParseDialogs(string[] args)
|
||||
{
|
||||
var raw = args
|
||||
.Where(a => a.StartsWith("--dialogs=", System.StringComparison.Ordinal))
|
||||
.Select(a => a.Substring("--dialogs=".Length))
|
||||
.FirstOrDefault();
|
||||
return int.TryParse(raw, out var n) && n > 0 ? n : null;
|
||||
}
|
||||
|
||||
private static float? ParseTimeout(string[] args)
|
||||
{
|
||||
var raw = args
|
||||
|
||||
Reference in new issue
Block a user