fix(mosstank): unclickable button, empty skill list, dev font, and chat output
Four defects from the first in-world look, three of them with a definite root cause rather than a plausible one. **The Buff button did nothing.** Not a hit-testing problem -- the pointer found the button perfectly. UiRoot's press handling asks the pressed widget whether it owns the pointer; a widget that does not claim the press falls through to "move the ancestor window", and a window drag returns early on release without ever emitting a Click. UiButton and UiClickablePanel both override HandlesClick for exactly this reason; UiSimpleButton never did. Latent since that class was written, and invisible until it was put inside a draggable window -- which is precisely what a markup plugin panel is. Found by reproducing it headlessly through the real UiRoot dispatcher rather than by reasoning about it: MarkupPanelClickTests drives press-and-release over the button and asserts the bound action ran, with a separate test asserting the pointer finds the button at all, so a future failure says which half broke. My earlier guess -- that a modal at character select was swallowing the click -- was wrong, and the screenshot of the panel live in world disproved it. **"0 trained skills".** The skill-name table was read in OnLoad *before* GameWindowCompositionPipeline.Run, which is what publishes the DAT collection, so _dats was still null, the whole block was skipped, and the surface reported an empty skill list with nothing to explain it. Bound in PublishDatCollection instead -- the moment the data exists -- so it cannot run early again whatever the phase ordering does, and a genuinely missing SkillTable now says so. **Plugin text used the development bitmap font.** UiLabel and UiSimpleButton gained a DatFont, and MarkupDocument now takes the retail interface font from the host, so plugin panels render through the same glyph path (including retail's two-plane outline) as authored panels. **MossTank now writes to chat.** New BCL-only IPluginChat routes to retail's ClientLocal log type (0x1A) -- the channel the client uses for its own notices, local to this client, so a plugin cannot speak in the player's name. MossTank announces the start, the finish with a cast count, and a stall. Not addressed here: the cursor showing blue rather than amber. Traced but not fixed -- CursorFeedbackController picks the cursor family from combat mode, and CombatMode.Magic selects the blue Magic cursor where Default is amber. That is a combat-mode question, unrelated to this change, and worth its own look rather than a speculative fix folded in here. Solution builds clean; 14,437 tests pass on the standard hermetic lane filter, 0 failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
17ebfc434d
commit
b9674b1f1e
8 changed files with 240 additions and 22 deletions
|
|
@ -1,3 +1,4 @@
|
|||
using AcDream.Core.Chat;
|
||||
using AcDream.Core.Player;
|
||||
using AcDream.Core.Spells;
|
||||
using AcDream.Plugin.Abstractions;
|
||||
|
|
@ -25,11 +26,13 @@ namespace AcDream.App.Plugins;
|
|||
/// </para>
|
||||
/// </remarks>
|
||||
internal sealed class AppAutomationSurface
|
||||
: IAutomationSurface, ICharacterInfo, ISpellCatalog, IMagicCommands, IDisposable
|
||||
: IAutomationSurface, ICharacterInfo, ISpellCatalog, IMagicCommands, IPluginChat,
|
||||
IDisposable
|
||||
{
|
||||
private readonly object _gate = new();
|
||||
|
||||
private GameRuntime? _runtime;
|
||||
private RuntimeCommunicationState? _communication;
|
||||
private RuntimeCharacterState? _character;
|
||||
private RuntimeSpellCastState? _cast;
|
||||
private Spellbook? _spellbook;
|
||||
|
|
@ -74,6 +77,7 @@ internal sealed class AppAutomationSurface
|
|||
public ICharacterInfo Character => this;
|
||||
public ISpellCatalog Spells => this;
|
||||
public IMagicCommands Magic => this;
|
||||
public IPluginChat Chat => this;
|
||||
|
||||
/// <summary>Bind the surface to the runtime's gameplay owners.</summary>
|
||||
public void Bind(
|
||||
|
|
@ -90,6 +94,7 @@ internal sealed class AppAutomationSurface
|
|||
return;
|
||||
DetachLocked();
|
||||
_runtime = runtime;
|
||||
_communication = runtime.CommunicationOwner;
|
||||
_character = character;
|
||||
_cast = cast;
|
||||
_spellbook = spellbook;
|
||||
|
|
@ -132,6 +137,7 @@ internal sealed class AppAutomationSurface
|
|||
_character = null;
|
||||
_cast = null;
|
||||
_runtime = null;
|
||||
_communication = null;
|
||||
}
|
||||
|
||||
private void OnSpellbookChanged() => RebuildSpellbook();
|
||||
|
|
@ -361,6 +367,22 @@ internal sealed class AppAutomationSurface
|
|||
return false;
|
||||
}
|
||||
|
||||
// ── IPluginChat ───────────────────────────────────────────────────────
|
||||
/// <summary>
|
||||
/// Routed to retail's ClientLocal log type (0x1A) — the channel the client
|
||||
/// uses for its own notices. Nothing reaches the server, so a plugin cannot
|
||||
/// accidentally speak in the player's name.
|
||||
/// </summary>
|
||||
public void PostSystemMessage(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return;
|
||||
RuntimeCommunicationState? communication;
|
||||
lock (_gate)
|
||||
communication = _communication;
|
||||
communication?.AddText(text, RetailLogTextType.ClientLocal);
|
||||
}
|
||||
|
||||
// ── IMagicCommands ────────────────────────────────────────────────────
|
||||
public bool IsCasting
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue