This commit is contained in:
iisaev committed 2026-10-18 12:41:38 +03:00
commit 5de3fd7ad8
121 files changed
+6632

No files matched your search

@@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Linq;
using Cthangover.Core.Mods;
using Cthangover.Core.Utils;
using Godot;
namespace Mods.TestRunner
{
/// <summary>
/// Точка входа тестового мода: переносит интеграционную обвязку диалогов
/// (tests DialogAutoDriver + SceneSwitcher) в engine-мод. Активируется
/// только при наличии пользовательских флагов <c>--choices=</c> или
/// <c>--timeout=</c> (как <c>OS.GetCmdlineUserArgs()</c>; флаг <c>--scene=</c>
/// мод не читает — навигацией по сценам управляет ядро). При активации
/// создаёт <see cref="DialogAutoDriverNode"/> и добавляет его в корень
/// дерева через deferred-вызов: <c>OnModResourcesReady</c> срабатывает до
/// окончания построения сцены, прямой AddChild в этот момент отклоняется
/// движком, а драйверу всё равно нужно дождаться диалога (та же задержка
/// 0.5с, что у SceneSwitcher.ScheduleDriverAttach). Повторный
/// <c>ModManager.Reload()</c> не должен плодить драйверы — защищено
/// статическим флагом.
/// </summary>
public class TestRunnerInitializer : IModInitializer
{
private static bool driverQueued;
/// <summary>Инициализация per-mod не требуется — драйвер ставится на resources-ready.</summary>
public void OnModLoaded(string modId) { }
/// <summary>
/// Разбирает пользовательские аргументы командной строки и при
/// активационном флаге ставит драйвер автопроигрывания в корень дерева.
/// </summary>
public void OnModResourcesReady()
{
if (driverQueued)
return;
var args = OS.GetCmdlineUserArgs();
var hasChoices = args.Any(a => a.StartsWith("--choices=", System.StringComparison.Ordinal));
var hasTimeout = args.Any(a => a.StartsWith("--timeout=", System.StringComparison.Ordinal));
if (!hasChoices && !hasTimeout)
return;
var choices = ParseChoices(args);
var timeout = ParseTimeout(args);
// Godot. квалифицирован: в dev-компиляции модов (ToolsModCompileTests)
// в глобальном пространстве имён существует namespace Engine из тестовой сборки,
// и неквалифицированное имя резолвится в него вместо Godot.Engine.
var root = (Godot.Engine.GetMainLoop() as SceneTree)?.Root;
if (root == null)
{
GameLogger.Log("TEST", "TestRunnerInitializer: tree root not available, driver not attached", LogLevel.Error);
return;
}
driverQueued = true;
GameLogger.Log("TEST",
$"TestRunnerInitializer: activating dialog auto-driver, choices=[{string.Join(",", choices)}], timeout={timeout?.ToString() ?? "none"}s");
// Deferred: сцена ещё строится; драйвер внутри сам выдерживает
// стартовую паузу и лениво ищет DialogBox, поэтому порядок с
// загрузкой сцены не важен.
root.CallDeferred("add_child", new DialogAutoDriverNode(choices, timeout));
}
private static List<int> ParseChoices(string[] args)
{
var raw = args
.Where(a => a.StartsWith("--choices=", System.StringComparison.Ordinal))
.Select(a => a.Substring("--choices=".Length))
.FirstOrDefault();
if (string.IsNullOrEmpty(raw))
return new List<int>();
return raw.Split(',')
.Select(s => int.TryParse(s.Trim(), out var n) ? n : -1)
.Where(n => n >= 0)
.ToList();
}
private static float? ParseTimeout(string[] args)
{
var raw = args
.Where(a => a.StartsWith("--timeout=", System.StringComparison.Ordinal))
.Select(a => a.Substring("--timeout=".Length))
.FirstOrDefault();
return float.TryParse(raw, System.Globalization.CultureInfo.InvariantCulture, out var t) && t > 0
? t
: null;
}
}
}