acdream/src/AcDream.Plugins.MossTank/MossTankPlugin.cs
Erik 06c9b95222 feat(vtank): slice 7 fix round B item 5 — Meta tab down to VTank's real 5 controls, rule editor as a popup
VTank's own Meta tab (docs/research/vtank-kb/08-ui-views.md §1 "Tab: Meta")
is only 5 controls: the lstMetaRules grid, cmdMetaCreate ("Create"), a bold
"Current State:" caption, and a SETTABLE cmbMetaCurrentState choice — no
profile toolbar, no "Enable Meta" checkbox, no inline editor at all.

- Removed from the Meta tab entirely: the profile menu/name-draft field/
  New/CopyTo/Clear/Delete row (Profiles already carries the Meta combo/
  CopyTo/Delete) and the "Enable Meta" toggle (already on Options) — same
  accepted-regression shape as fix round A's Macro/Nav CopyTo-name-field
  loss (no Profiles-tab equivalent exists for these, and VTank's own tab
  has none either).
- Moved the inline State/Condition/Action editor (fields, condition/action
  menus, numeric steppers, Apply/Remove/MoveUp/MoveDown) into a new popup,
  mosstank-metaeditor.xml, registered the same StartVisible=true/
  ShowInSidePanel=false way as the buff picker. Opened by a grid text-cell
  click (SelectMetaRule, still populating the draft via SelectMetaRuleCore)
  or the tab's own "Create" button (CreateMetaRule — new, distinct from the
  still-existing AddMetaRule the tests call directly); Apply and Cancel
  (HideMetaEditor) both close it. DeleteMetaRuleAt/MoveMetaRuleUpAt/
  MoveMetaRuleDownAt call SelectMetaRuleCore directly and do NOT open the
  popup.
- "Add" -> "Create": CreateMetaRule adds a default rule (delegating to the
  existing AddMetaRuleCore) and opens the editor so it isn't left silently
  default-valued.
- Added the settable current-state menu: MetaCurrentStateNames/
  SelectedMetaCurrentState/SetMetaCurrentState expose MetaEngine's own
  already-public States/Transition(string) — choosing a state here forces
  the live engine into it, matching VTank's own manual override.
- The grid returns to VTank's full-width 856x116 proportion (848x116 here)
  now nothing else shares the tab.
- Bold text isn't representable in the plain retail UI font (0x40000000 has
  no bold face); "Current State:" uses the same bright caption color other
  tab headers use instead — documented in mosstank.xml's own comment, not
  silently dropped.

New tests: MetaEditorPopupOpensOnCellClickOrCreateAndClosesOnApplyOrCancel,
MetaCurrentStateMenuForcesTheLiveEngineIntoTheChosenState. Both mutation-
checked: commenting out SelectMetaRule's `_metaEditorVisible = true` turned
the first red ("Expected: True, Actual: False"); commenting out
SetMetaCurrentState's `_meta.Transition(value)` turned the second red
("Expected: Hunt, Actual: Default"). Restoring both turns them green.

EveryInteractiveControlDeclaresARealHandlerBinding's pinned control count:
186 -> 166 (-7 controls removed for good, -14 moved into the new popup file
this scan doesn't cover, +1 the new current-state menu).
SecondaryPopupPanelsFitTheirOwnBoundsAndEveryBindingResolves gained
mosstank-metaeditor.xml (630x236... — 630x160, corrected below).

tests/AcDream.Plugins.MossTank.Tests: 665/665 (was 663/663, +2 new tests).
tests/AcDream.App.Tests --filter Markup|Plugin|UiMenu|Slider: 276/3 skipped/279 (unchanged).

Screenshot with at least two rules (item 17) is owed with the round's other
live captures.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 12:05:27 +02:00

152 lines
6.5 KiB
C#

using AcDream.Plugin.Abstractions;
namespace AcDream.Plugins.MossTank;
/// <summary>
/// MossTank — a self-buffing plugin, and the first consumer of acdream's
/// plugin automation surface.
/// </summary>
/// <remarks>
/// Named for the mosswart, and for the Virindi Tank lineage this milestone is
/// modelled on. The buff policy lives here rather than in the host on purpose:
/// the host publishes spell data and a cast primitive, the plugin decides what
/// to cast. See <c>docs/research/2026-07-29-vtank-plugin-automation-requirements.md</c>.
/// </remarks>
public sealed class MossTankPlugin : IAcDreamPlugin
{
private IPluginHost? _host;
private MossTankPanel? _panel;
private Action<double>? _tick;
private IDisposable? _commandRegistration;
public void Initialize(IPluginHost host)
{
_host = host;
_panel = new MossTankPanel(host);
host.Log.Info("MossTank initialized");
}
public void Enable()
{
if (_host is null || _panel is null)
return;
// Markup ships beside the plugin assembly, so it is found relative to
// this DLL rather than the host's working directory -- plugins are
// loaded from their own directory and the two are not the same.
string directory =
Path.GetDirectoryName(typeof(MossTankPlugin).Assembly.Location) ?? ".";
_host.Ui.AddPanel(
new PluginPanelDescriptor("main", "MossTank")
{
IconText = "MT",
// A real installed portal.dat RenderSurface — see
// MossTankIconInstalledDatTests for the DAT-presence proof.
// IconText remains the fallback if this id is ever absent
// from an install.
IconSurfaceId = 0x06002C41u,
StartVisible = true,
ShowInSidePanel = true,
},
Path.Combine(directory, "mosstank.xml"),
_panel);
// Fix round A (2026-09-07): Advanced Options / Loot Editor were
// in-panel groups sharing the main window (forcing it to 350px
// tall with dead space under the nine normal tabs); they are now
// their own plugin panels, matching VTank's own separate popup
// windows (docs/research/vtank-kb/08-ui-views.md §1's secondary-
// view table). ShowInSidePanel=false — VTank's popups have no
// independent shelf/taskbar entry, they only exist while opened
// from the Options/Profiles tab checkbox
// (AdvancedOptionsVisible/LootEditorVisible). Each popup's own
// <panel visible="{...}"> binding is the SAME flag the checkbox
// flips and the popup's own "Back" button clears, so closing the
// popup unchecks the box for free — no new IUiRegistry show/hide
// API needed.
//
// StartVisible=true (NOT false) here — confirmed against a live
// build after StartVisible=false left both popups permanently
// invisible despite AdvancedOptionsVisible/LootEditorVisible
// reading true. RetailUiRuntime.MountPlugins wraps every plugin
// window's markup-level visible={Prop} ("availability") in a
// PluginWindowVisibilityController that ANDs it with a SEPARATE
// "requested visible" axis seeded from StartVisible and only ever
// flipped back to true by OnShown() (the shelf's un-minimize
// click). A ShowInSidePanel=false window has no shelf entry to
// click, so StartVisible=false would leave "requested visible"
// permanently false and availability's AND can never rescue it.
// StartVisible=true keeps "requested visible" true from the start
// (and PluginWindowVisibilityController.OnHidden only ever latches
// it back to false on a genuine minimize — it explicitly no-ops
// when hidden because availability itself went false, which is
// exactly what closing via "Back" does), leaving availability
// (AdvancedOptionsVisible/LootEditorVisible) as the sole effective
// gate, same as the main panel's own always-true
// ShowInSidePanel=true default.
_host.Ui.AddPanel(
new PluginPanelDescriptor("advanced-options", "MossTank Advanced Options")
{
StartVisible = true,
ShowInSidePanel = false,
},
Path.Combine(directory, "mosstank-advanced.xml"),
_panel);
_host.Ui.AddPanel(
new PluginPanelDescriptor("loot-editor", "MossTank Loot Editor")
{
StartVisible = true,
ShowInSidePanel = false,
},
Path.Combine(directory, "mosstank-loot-editor.xml"),
_panel);
// Campaign VT S7.4: the Buffs tab's "Add..." picker (VTank's own
// SelfBuffChoiceView), same StartVisible=true/ShowInSidePanel=false
// popup pattern as the two panels above — see their own comments
// for why StartVisible must be true.
_host.Ui.AddPanel(
new PluginPanelDescriptor("buff-picker", "MossTank Add Buff")
{
StartVisible = true,
ShowInSidePanel = false,
},
Path.Combine(directory, "mosstank-buffpicker.xml"),
_panel);
// Fix round B item 5: the Meta tab's inline rule editor moved to its
// own popup, same StartVisible=true/ShowInSidePanel=false pattern
// as the three panels above.
_host.Ui.AddPanel(
new PluginPanelDescriptor("meta-editor", "MossTank Meta Rule Editor")
{
StartVisible = true,
ShowInSidePanel = false,
},
Path.Combine(directory, "mosstank-metaeditor.xml"),
_panel);
_commandRegistration = _host.Commands.Register(
"vt",
_panel.ExecuteVtankCommand);
_tick = _panel.OnTick;
_host.Events.Tick += _tick;
_host.Log.Info(
_host.Automation.IsAvailable
? "MossTank enabled"
: "MossTank enabled (no live session yet; the Buff button will "
+ "report 'Not in world' until one is up)");
}
public void Disable()
{
if (_host is not null && _tick is not null)
_host.Events.Tick -= _tick;
_commandRegistration?.Dispose();
_commandRegistration = null;
_tick = null;
_panel?.Disable();
_host?.Log.Info("MossTank disabled");
}
}