using System.Collections.Generic; using System.Linq; using Cthangover.Core.Mods; using Cthangover.Core.Utils; using Godot; namespace Mods.TestRunner { /// /// Точка входа тестового мода: переносит интеграционную обвязку диалогов /// (tests DialogAutoDriver + SceneSwitcher) в engine-мод. Активируется /// только при наличии пользовательских флагов --choices= или /// --timeout= (как OS.GetCmdlineUserArgs(); флаг --scene= /// мод не читает — навигацией по сценам управляет ядро). При активации /// создаёт и добавляет его в корень /// дерева через deferred-вызов: OnModResourcesReady срабатывает до /// окончания построения сцены, прямой AddChild в этот момент отклоняется /// движком, а драйверу всё равно нужно дождаться диалога (та же задержка /// 0.5с, что у SceneSwitcher.ScheduleDriverAttach). Повторный /// ModManager.Reload() не должен плодить драйверы — защищено /// статическим флагом. /// public class TestRunnerInitializer : IModInitializer { private static bool driverQueued; /// Инициализация per-mod не требуется — драйвер ставится на resources-ready. public void OnModLoaded(string modId) { } /// /// Разбирает пользовательские аргументы командной строки и при /// активационном флаге ставит драйвер автопроигрывания в корень дерева. /// 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); var dialogs = ParseDialogs(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, dialogs={dialogs?.ToString() ?? "idle"}"); // Deferred: сцена ещё строится; драйвер внутри сам выдерживает // стартовую паузу и лениво ищет DialogBox, поэтому порядок с // загрузкой сцены не важен. ModRoot.AddDeferred(new DialogAutoDriverNode(choices, timeout, dialogs)); } private static List 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(); return raw.Split(',') .Select(s => int.TryParse(s.Trim(), out var n) ? n : -1) .Where(n => n >= 0) .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 .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; } } }