From 563b42ea9f8266f0b19400c3c94ae30ffb528a72 Mon Sep 17 00:00:00 2001 From: iisaev Date: Sat, 17 Oct 2026 14:27:29 +0000 Subject: [PATCH] Menu: conditional buttons, OpenScene returns the opened node - MenuBuilder skips a button whose MenuButtonEntry.Visible predicate is false (e.g. "Continue" only when a last session exists); a throwing predicate is logged and the button stays visible. - MenuBuilder.OpenScene returns the overlay it opened (null when the scene failed to build), so callers can configure it. - Text for mods left out of a game's mod set: mods/not_in_game_reason. --- locale/ui_en.properties | 1 + locale/ui_ru.properties | 1 + src/Menu/MenuBuilder.cs | 22 ++++++++++++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/locale/ui_en.properties b/locale/ui_en.properties index 7a3c136..e678674 100644 --- a/locale/ui_en.properties +++ b/locale/ui_en.properties @@ -26,6 +26,7 @@ mods/enabled = Enabled mods/disabled = Disabled mods/disabled_reason = Disabled mods/dep_disabled_reason = Dependency "{0}" is disabled +mods/not_in_game_reason = Not part of the game "{0}" mods/dependency_disabled = Dependency disabled mods/invalid_dep_format = Invalid dependency format: "{0}" mods/required_mod_not_loaded = Required mod "{0}" is not loaded diff --git a/locale/ui_ru.properties b/locale/ui_ru.properties index f988597..4b6db4e 100644 --- a/locale/ui_ru.properties +++ b/locale/ui_ru.properties @@ -24,6 +24,7 @@ mods/enabled = Включён mods/disabled = Выключен mods/disabled_reason = Выключен mods/dep_disabled_reason = Зависимость "{0}" выключена +mods/not_in_game_reason = Не входит в игру "{0}" mods/dependency_disabled = Зависимость отключена mods/invalid_dep_format = Неверный формат зависимости: "{0}" mods/required_mod_not_loaded = Требуемый мод "{0}" не загружен diff --git a/src/Menu/MenuBuilder.cs b/src/Menu/MenuBuilder.cs index 08e4fe6..2241724 100644 --- a/src/Menu/MenuBuilder.cs +++ b/src/Menu/MenuBuilder.cs @@ -40,6 +40,9 @@ namespace Cthangover.Mods.Interface.Menu foreach (var entry in AppServices.Current.Menus.Buttons) { + if (entry.Visible != null && !SafeVisible(entry)) + continue; + if (!sections.TryGetValue(entry.Section ?? MenuButtonEntry.SectionMods, out var container) && !sections.TryGetValue(MenuButtonEntry.SectionMods, out container)) { @@ -62,6 +65,19 @@ namespace Cthangover.Mods.Interface.Menu /// carry a locale meta key into the host's translation map, so the host /// keeps them localized across language switches. /// + private static bool SafeVisible(MenuButtonEntry entry) + { + try + { + return entry.Visible(); + } + catch (System.Exception ex) + { + GameLogger.Log("MENU_BUILDER", $"Visibility check of button '{entry.Id}' failed: {ex.Message}", LogLevel.Warning); + return true; + } + } + public static void CollectLocaleKeys(Container container, Dictionary keys) { if (container == null || keys == null) @@ -116,13 +132,14 @@ namespace Cthangover.Mods.Interface.Menu /// owns its lifetime and closes itself, or is replaced by another menu /// scene opening over it. /// - public static void OpenScene(Control menuRoot, string scenePath, string label = null) + /// Opens a mod scene as a full-rect overlay of the menu and returns it (null when it fails to build). + public static Node OpenScene(Control menuRoot, string scenePath, string label = null) { var node = new ModScenePrefab(scenePath).Instantiate(); if (node == null) { GameLogger.Log("MENU_BUILDER", $"Open '{label ?? scenePath}': scene '{scenePath}' failed to build", LogLevel.Error); - return; + return null; } menuRoot.AddChild(node); @@ -135,6 +152,7 @@ namespace Cthangover.Mods.Interface.Menu modWidget.EnsureConstructed(); GameLogger.Log("MENU_BUILDER", $"Opened '{label ?? scenePath}' → {scenePath}"); + return node; } } }