diff --git a/src/AcDream.Plugins.MossTank/MossTankPanel.cs b/src/AcDream.Plugins.MossTank/MossTankPanel.cs index 42e22e134..b7fe023dc 100644 --- a/src/AcDream.Plugins.MossTank/MossTankPanel.cs +++ b/src/AcDream.Plugins.MossTank/MossTankPanel.cs @@ -188,6 +188,11 @@ internal sealed partial class MossTankPanel private bool _lootEditorVisible; private bool _advancedOptionsVisible; private int _selectedAdvancedOption; + // Fix round B item 9: one enabled flag per VtankOptionCatalog.CategoryBits + // entry, all true by default (no filter narrows the list until the user + // unchecks something). + private readonly bool[] _advancedOptionCategoryEnabled = + Enumerable.Repeat(true, VtankOptionCatalog.CategoryBits.Length).ToArray(); private string _advancedOptionValueDraft = string.Empty; private string _advancedOptionNotice = "All VTank settings are available here."; @@ -398,15 +403,65 @@ internal sealed partial class MossTankPanel public bool MetaVisible => MetaSelected; public bool LootEditorVisible => _lootEditorVisible; public bool AdvancedOptionsVisible => _advancedOptionsVisible; - public IReadOnlyList AdvancedOptionNames => VtankOptionCatalog.Names; + // Fix round B item 9: VTank's own AdvancedOptionsView (docs/research/ + // vtank-kb/08-ui-views.md §1) pairs lOptionList's clOpt name column + // with a clVal VALUE column, and filters both through lFilterList — a + // category checklist keyed by SettingsCategories' own per-setting + // bitmask (VtankDefaultSettingsDatabase.SettingCategoryBitmasks, 136 + // rows straight from the embedded .usd). This worktree has no + // refs/vtank/ checkout, so VTank's own category NAME strings aren't + // available anywhere in this repo — the filter groups are labeled by + // their raw bit value ("0x04" etc.) instead of a guessed name; a + // setting with no recorded bitmask always shows regardless of filter + // state (never silently hidden by a filter that doesn't apply to it). + public IReadOnlyList AdvancedOptionNames => FilteredAdvancedOptionNames(); + public IReadOnlyList AdvancedOptionValueColumn => FilteredAdvancedOptionNames() + .Select(name => GetMetaOption(name).ToDisplayString()) + .ToArray(); + public IReadOnlyList AdvancedOptionCategoryNames { get; } = + VtankOptionCatalog.CategoryBits.Select(bit => $"0x{bit:X}").ToArray(); + public IReadOnlyList AdvancedOptionCategoryEnabled => _advancedOptionCategoryEnabled; + public Action ToggleAdvancedOptionCategoryAt => index => + { + if ((uint)index >= (uint)_advancedOptionCategoryEnabled.Length) + return; + _advancedOptionCategoryEnabled[index] = !_advancedOptionCategoryEnabled[index]; + _selectedAdvancedOption = ClampRow(_selectedAdvancedOption, AdvancedOptionNames.Count); + LoadAdvancedOptionDraft(); + }; public int SelectedAdvancedOptionIndex => _selectedAdvancedOption; - public string AdvancedOptionName => VtankOptionCatalog.Names[ - Math.Clamp( - _selectedAdvancedOption, - 0, - VtankOptionCatalog.Names.Length - 1)]; + public string AdvancedOptionName + { + get + { + IReadOnlyList names = FilteredAdvancedOptionNames(); + return names.Count == 0 + ? string.Empty + : names[Math.Clamp(_selectedAdvancedOption, 0, names.Count - 1)]; + } + } public string AdvancedOptionValueDraft => _advancedOptionValueDraft; + // Fix round B item 9: VTank's own Settings.Description column (93 of + // 137 rows non-empty, real retail help text) — fills the 384x80 + // txtInfo readout. + public string AdvancedOptionDescription => + VtankDefaultSettingsDatabase.SettingDescriptions.TryGetValue(AdvancedOptionName, out string? text) + ? $"{AdvancedOptionName}: {text}" + : AdvancedOptionName; public string AdvancedOptionNotice => _advancedOptionNotice; + + private IReadOnlyList FilteredAdvancedOptionNames() + { + int enabledMask = 0; + for (int i = 0; i < VtankOptionCatalog.CategoryBits.Length; i++) + if (_advancedOptionCategoryEnabled[i]) + enabledMask |= VtankOptionCatalog.CategoryBits[i]; + + return VtankOptionCatalog.Names.Where(name => + !VtankDefaultSettingsDatabase.SettingCategoryBitmasks.TryGetValue(name, out int mask) + || mask == 0 + || (mask & enabledMask) != 0).ToArray(); + } public Action ShowAdvancedOptions => () => { _advancedOptionsVisible = true; @@ -429,10 +484,7 @@ internal sealed partial class MossTankPanel }; public Action SelectAdvancedOption => index => { - _selectedAdvancedOption = Math.Clamp( - index, - 0, - VtankOptionCatalog.Names.Length - 1); + _selectedAdvancedOption = ClampRow(index, FilteredAdvancedOptionNames().Count); LoadAdvancedOptionDraft(); }; public Action SetAdvancedOptionValueDraft => value => diff --git a/src/AcDream.Plugins.MossTank/VtankDefaultSettingsDatabase.cs b/src/AcDream.Plugins.MossTank/VtankDefaultSettingsDatabase.cs index c739de778..62735d192 100644 --- a/src/AcDream.Plugins.MossTank/VtankDefaultSettingsDatabase.cs +++ b/src/AcDream.Plugins.MossTank/VtankDefaultSettingsDatabase.cs @@ -22,6 +22,10 @@ internal static class VtankDefaultSettingsDatabase private const string ResourceSuffix = ".VtankDefaultSettings.usd"; private static readonly Lazy RawText = new(LoadText); private static readonly Lazy DefaultRows = new(LoadDefaultRows); + private static readonly Lazy> CategoryBitmasksByName = + new(LoadCategoryBitmasks); + private static readonly Lazy> DescriptionsByName = + new(LoadDescriptions); /// A fresh, independently mutable parse of the embedded document — every call gets its own object graph. public static VtankDatabase Parse() => VtankDatabase.Parse(RawText.Value); @@ -29,6 +33,63 @@ internal static class VtankDefaultSettingsDatabase /// VTank's own shipped RechargeHandlerSet rows (26, in file order). public static IReadOnlyList DefaultRechargeHandlerRows => DefaultRows.Value; + /// + /// VTank's own SettingsCategories table (2 columns, 136 rows: + /// Setting name, Categories bitmask) — the data + /// lFilterList (docs/research/vtank-kb/08-ui-views.md §1's + /// AdvancedOptionsView table) filters lOptionList by. This + /// worktree has no access to VTank's own category NAME strings (no + /// refs/vtank/ checkout here — see fix round B item 9's own + /// binding-site comment); only the raw per-setting bitmask is real + /// data we can port from the embedded .usd. + /// + public static IReadOnlyDictionary SettingCategoryBitmasks => CategoryBitmasksByName.Value; + + /// + /// VTank's own Settings table Description column (93 of + /// 137 rows non-empty) — real retail help text, e.g. "DoHelp" => + /// "If true, allies are healed/restamed/given mana. The fellowship + /// window must be open to help fellows." Powers fix round B item 9's + /// txtInfo readout. + /// + public static IReadOnlyDictionary SettingDescriptions => DescriptionsByName.Value; + + private static IReadOnlyDictionary LoadDescriptions() + { + VtankDatabase database = VtankDatabase.Parse(RawText.Value); + VtankTable? settings = database.Find("Settings"); + var map = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (settings is null) + return map; + int nameColumn = settings.ColumnIndex("Setting"); + int descriptionColumn = settings.ColumnIndex("Description"); + if (nameColumn < 0 || descriptionColumn < 0) + return map; + foreach (VtankRow row in settings.Rows) + { + string description = row.Cells[descriptionColumn].AsString(); + if (!string.IsNullOrWhiteSpace(description)) + map[row.Cells[nameColumn].AsString()] = description; + } + return map; + } + + private static IReadOnlyDictionary LoadCategoryBitmasks() + { + VtankDatabase database = VtankDatabase.Parse(RawText.Value); + VtankTable? categories = database.Find("SettingsCategories"); + var map = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (categories is null) + return map; + int nameColumn = categories.ColumnIndex("Setting"); + int bitsColumn = categories.ColumnIndex("Categories"); + if (nameColumn < 0 || bitsColumn < 0) + return map; + foreach (VtankRow row in categories.Rows) + map[row.Cells[nameColumn].AsString()] = row.Cells[bitsColumn].AsInt(); + return map; + } + private static string LoadText() { Assembly assembly = typeof(VtankDefaultSettingsDatabase).Assembly; diff --git a/src/AcDream.Plugins.MossTank/VtankOptionCatalog.cs b/src/AcDream.Plugins.MossTank/VtankOptionCatalog.cs index 8dcb1ecfe..e5abaa754 100644 --- a/src/AcDream.Plugins.MossTank/VtankOptionCatalog.cs +++ b/src/AcDream.Plugins.MossTank/VtankOptionCatalog.cs @@ -389,4 +389,25 @@ internal static class VtankOptionCatalog DeclaredTypes.TryGetValue(name, out VtankSettingValueType type) ? type : VtankSettingValueType.Double; + + /// + /// Every individual bit (power of two) actually used across + /// 's + /// values, ascending — the real filter groups fix round B item 9's + /// lFilterList checklist offers, since a row's own mask can be a + /// sum of several category bits (e.g. 12 = 4+8). + /// + internal static readonly int[] CategoryBits = VtankDefaultSettingsDatabase + .SettingCategoryBitmasks.Values + .SelectMany(DecomposeBits) + .Distinct() + .Order() + .ToArray(); + + private static IEnumerable DecomposeBits(int mask) + { + for (int bit = 1; bit != 0 && bit <= mask; bit <<= 1) + if ((mask & bit) != 0) + yield return bit; + } } diff --git a/src/AcDream.Plugins.MossTank/mosstank-advanced.xml b/src/AcDream.Plugins.MossTank/mosstank-advanced.xml index 9e0900793..91dab2938 100644 --- a/src/AcDream.Plugins.MossTank/mosstank-advanced.xml +++ b/src/AcDream.Plugins.MossTank/mosstank-advanced.xml @@ -45,45 +45,73 @@ stepper (a real duplicate of Options' own editable FollowNavMinimumValueText field — kept for symmetry with the other extras, not deleted, since both write the same - NavigationSettings.MinimumDistanceMeters). Panel height grows 300->392 - to fit this section without touching the retail editor above it; item 9 - is expected to reshuffle the retail portion further (clVal column, - lFilterList checklist, description readout) without moving this section. - Bold text isn't representable in the plain retail UI font (0x40000000 - has no bold face) — the section header uses the same bright caption - color other headers use instead. --> -476 for the taller two-list row plus the + description block; the "MossTank Extras" section (item 8) shifts down + to keep sitting below everything the retail portion now needs. Bold + text isn't representable in the plain retail UI font (0x40000000 has no + bold face) — the section header uses the same bright caption color + other headers use instead. --> + - -