using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using Cthangover.Core.Mods; using Cthangover.Core.Settings; using Cthangover.Core.UI; using Cthangover.Core.UI.Theming; using Cthangover.Core.Utils; using Godot; using Cthangover.Mods.Interface.Base.Lists; using Cthangover.Mods.Interface.Theming; using Cthangover.Mods.Interface.Tool; namespace Mods.ModManager { /// /// Fullscreen scheme editor opened from the main menu: the whole game on one /// canvas exactly like the approved mockup. The header picks the game and /// creates new games/mechanics, the canvas shows movable and resizable /// mechanic contours with freely placed mod chips and dependency lines, and /// the inspector edits the selected group or mod. /// /// All heavy lifting stays in the core: /// merges documents, expands includes /// into groups and problems, decides what to /// download and enable, and persists the /// arrangement. Edits mutate drafts in _overrides — never the loaded /// documents — and only "Сохранить" writes them back through /// , so the player can explore /// layouts without committing anything. /// public partial class ModCollectionsScreen : ModWidget { private const string RoleExtra = "extra"; private Button _close; private Button _refresh; private Button _addMechanic; private Button _newGame; private Button _apply; private OptionButton _gameSelect; private OptionButton _edgesToggle; private Button _pillProblems; private Button _pillPlan; private Label _modsCount; private Label _dirty; private Button _json; private Button _reset; private Button _save; private Label _bannerLabel; private Label _detailTitle; private Label _detailMeta; private Label _detailDescription; private VBoxContainer _detailExtra; private PanelContainer _bannerPanel; private CollectionSchemeCanvas _canvas; private Button _tabInspector; private Button _tabProblems; private Button _tabPlan; private HBoxContainer _crumbs; private Button _crumbCollections; private Label _buildName; private Label _buildInstall; private Label _buildEnable; private Label _buildProblems; private Label _buildHint; private Button _buildManager; private Button _buildApply; private List _collections = new(); private readonly Dictionary _overrides = new(StringComparer.OrdinalIgnoreCase); private string _currentId; private ModCollectionGraph _graph; private ModCollectionPlan _plan; private ModCollectionLayout _layout = new(); private string _selectedGroupKey; private string _selectedModId; private string _tab = "inspector"; /// Mods installed during this session; the registry only sees them after a restart. private HashSet _sessionInstalled = new(StringComparer.OrdinalIgnoreCase); private bool _installing; private bool _restartRequired; private string _statusMessage; private Button _duplicate; private Button _revert; private Button _delete; private Label _savesInfo; private Button _addMod; private LineEdit _canvasSearch; private Label _zoomLabel; private VBoxContainer _versionEditor; private OptionButton _versionPolicy; private LineEdit _versionValue; private Label _versionRule; private Label _versionFeedback; private ModCollectionModRef _versionReference; private bool _fitAfterBuild = true; private bool _fitQueued; /// Drafts created by "Duplicate", mapped to the game they were copied from (offer to copy its saves). private readonly Dictionary _duplicatedFrom = new(StringComparer.OrdinalIgnoreCase); /// /// When true the editor runs as the second level of the collections /// tab: the close/title/new-game/catalog-refresh header controls are /// hidden (the hosting owns navigation) and /// the game selector moves into the breadcrumb row. Editing, saving /// and applying behaviour are unchanged. /// public bool Embedded { get; set; } /// Raised when the breadcrumbs ask to go back to the games gallery. public event Action GalleryRequested; /// Raised when a "not found" problem asks to search the mod catalog; the argument is the missing id. public event Action OpenManagerRequested; /// Raised after every rebuild so the hosting hub can refresh its header. public event Action ViewChanged; /// Localized, '@key'-resolved title of the collection being edited; null when nothing is selected. public string CurrentCollectionTitle { get { var collection = EffectiveCollection(_currentId); if (collection == null) return null; var raw = collection.GetLocalizedName(TranslationServer.GetLocale()); return CollectionText.Resolve(raw); } } /// Shares the hub-wide set of mods installed this session so gallery and editor stay consistent. public void SetSessionInstalled(HashSet sessionInstalled) => _sessionInstalled = sessionInstalled ?? new HashSet(StringComparer.OrdinalIgnoreCase); /// /// Opens the scheme editor on the given collection id, falling back to /// the first available game when the id is null or unknown. Selection /// is cleared — the same fresh view a game-switch produces. /// public void OpenCollection(string collectionId) { _currentId = collectionId; _fitAfterBuild = true; _selectedGroupKey = null; _selectedModId = null; // The gallery re-reads the store on every refresh; re-read it here // too, so a collection that appeared since this editor was built // opens instead of falling back to the first game. ReloadCollections(); } /// Creates a fresh player game draft and opens it — the gallery's "build your own game" action. public void OpenNewGame() => OnNewGamePressed(); protected override void Construct() { MouseFilter = MouseFilterEnum.Stop; CthangoverTheme.Apply(this); BindSceneNodes(); SubscribeEvents(); CatalogService.LoadCache(); ReloadCollections(); StartCatalogRefreshIfNeeded(); } protected override void Destruct() { UnsubscribeEvents(); } private void BindGraphiteControls() { var tools = "Margin/Root/Tools/"; _addMod = GetNode