diff --git a/src/AcDream.Plugins.MossTank/MossTankPanel.cs b/src/AcDream.Plugins.MossTank/MossTankPanel.cs index 79e19de04..45075ab2c 100644 --- a/src/AcDream.Plugins.MossTank/MossTankPanel.cs +++ b/src/AcDream.Plugins.MossTank/MossTankPanel.cs @@ -921,6 +921,44 @@ internal sealed partial class MossTankPanel public string MetaStatus => _meta.Status; public IReadOnlyList MetaRows => _metaRows; public int SelectedMetaRuleIndex => _selectedMetaRule; + + // ── Meta grid (Campaign VT S7.6: VTank's own lstMetaRules 6-column + // grid, docs/research/vtank-kb/08-ui-views.md §1 "Tab: Meta"; + // PluginCore.cs:2218-2247 — col 0 delete, col 1 move up, col 2 move + // down, cols 3-5 (State/Condition/Action text) open the rule editor). + // Delete uses a text "X" cell rather than an icon: no retail DAT + // delete-glyph id is confirmed anywhere in this codebase (unlike the + // move-up/move-down 0x060028FC/FD pair, already established by the + // Monsters/Route grids), and "X" is plain ASCII the default retail + // font already renders — see NoButtonAnywhereUsesTheUnrenderableArrowGlyphs + // for why an unconfirmed glyph id is worse than a text fallback. ── + public IReadOnlyList MetaDeleteColumn => + Enumerable.Repeat("X", _metaProfile.Rules.Count).ToArray(); + public IReadOnlyList MetaMoveUpIcons => + Enumerable.Repeat(0x060028FCu, _metaProfile.Rules.Count).ToArray(); + public IReadOnlyList MetaMoveDownIcons => + Enumerable.Repeat(0x060028FDu, _metaProfile.Rules.Count).ToArray(); + public IReadOnlyList MetaStateColumn => + _metaProfile.Rules.Select(static rule => rule.State).ToArray(); + public IReadOnlyList MetaConditionColumn => + _metaProfile.Rules.Select(static rule => DescribeMetaCondition(rule.Condition)).ToArray(); + public IReadOnlyList MetaActionColumn => + _metaProfile.Rules.Select(static rule => DescribeMetaAction(rule.Action)).ToArray(); + public Action DeleteMetaRuleAt => row => + { + SelectMetaRuleCore(row); + RemoveMetaRuleCore(); + }; + public Action MoveMetaRuleUpAt => row => + { + SelectMetaRuleCore(row); + MoveMetaRule(-1); + }; + public Action MoveMetaRuleDownAt => row => + { + SelectMetaRuleCore(row); + MoveMetaRule(1); + }; public IReadOnlyList MetaConditionNames => Enum.GetNames(); public IReadOnlyList MetaActionNames => Enum.GetNames(); diff --git a/src/AcDream.Plugins.MossTank/mosstank.xml b/src/AcDream.Plugins.MossTank/mosstank.xml index 9a80f8aed..df2c7b449 100644 --- a/src/AcDream.Plugins.MossTank/mosstank.xml +++ b/src/AcDream.Plugins.MossTank/mosstank.xml @@ -609,7 +609,13 @@ + entry; transitions/call/return use the live MetaEngine. The rules + list is VTank's own 6-column lstMetaRules grid (Campaign VT S7.6, + docs/research/vtank-kb/08-ui-views.md §1 "Tab: Meta"; + PluginCore.cs:2218-2247 — col 0 delete, cols 1/2 move up/down, cols + 3-5 State/Condition/Action open the rule editor below). PITCH: + delete/move 16+7=23 each, State 150+7=157, Condition/Action share + the remainder ("*"). --> + selected="{SelectedMetaRuleIndex}" onchange="{SelectMetaRule}" + tooltip="Click State/Condition/Action to edit; delete/move-up/move-down cells act immediately."> + + + + + + + 180. - Assert.Equal(180, controls.Length); + // S7.6 replaced the Meta tab's single-column rules list with + // VTank's own 6-column lstMetaRules grid (+6: the list itself was + // already counted, now 6 children) — net 180 -> 186. + Assert.Equal(186, controls.Length); foreach (XElement control in controls) { diff --git a/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs b/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs index 895bd026b..0717af7de 100644 --- a/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs +++ b/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs @@ -2049,6 +2049,39 @@ public sealed class MossTankPanelTests Assert.Equal("Hunt", panel.MetaState); } + [Fact] + public void MetaGridColumnsMatchTheRulesAndDeleteMoveActOnTheClickedRow() + { + // Campaign VT S7.6: VTank's own 6-column lstMetaRules grid + // (PluginCore.cs:2218-2247) — col 0 delete, cols 1/2 move up/down, + // cols 3-5 open the editor for that row (SelectMetaRule, already + // covered by MetaTabEditsAndExecutesTheLiveStateMachine). + var panel = new MossTankPanel(new FakeHost(new FakeAutomation())); + + panel.SetMetaStateDraft("Idle"); + panel.SelectMetaAction(nameof(MetaActionKind.ChatCommand)); + panel.SetMetaActionTextDraft("/mt one"); + panel.AddMetaRule(); + panel.SetMetaStateDraft("Hunt"); + panel.SetMetaActionTextDraft("/mt two"); + panel.AddMetaRule(); + + Assert.Equal(["Idle", "Hunt"], panel.MetaStateColumn); + Assert.Contains("/mt one", panel.MetaActionColumn[0], StringComparison.Ordinal); + Assert.Contains("/mt two", panel.MetaActionColumn[1], StringComparison.Ordinal); + Assert.Equal(["X", "X"], panel.MetaDeleteColumn); + Assert.All(panel.MetaMoveUpIcons, id => Assert.Equal(0x060028FCu, id)); + Assert.All(panel.MetaMoveDownIcons, id => Assert.Equal(0x060028FDu, id)); + + panel.MoveMetaRuleDownAt(0); // "Idle" <-> "Hunt" + Assert.Equal(["Hunt", "Idle"], panel.MetaStateColumn); + panel.MoveMetaRuleUpAt(1); // back + Assert.Equal(["Idle", "Hunt"], panel.MetaStateColumn); + + panel.DeleteMetaRuleAt(0); // "Idle" + Assert.Equal(["Hunt"], panel.MetaStateColumn); + } + [Fact] public void MetaProfilesAreIndependentDurableDocuments() {