diff --git a/src/AcDream.App/Plugins/AppAutomationSurface.cs b/src/AcDream.App/Plugins/AppAutomationSurface.cs index f000d31b..90e929ed 100644 --- a/src/AcDream.App/Plugins/AppAutomationSurface.cs +++ b/src/AcDream.App/Plugins/AppAutomationSurface.cs @@ -384,14 +384,27 @@ internal sealed class AppAutomationSurface } // ── IMagicCommands ──────────────────────────────────────────────────── + /// + /// True while an action the server has not acknowledged is in flight. + /// + /// + /// Reads the shared busy count, which the cast path increments + /// (FreeHandsAndCastSpell @0x00566EF0) and the server's UseDone + /// decrements. NOT RuntimeSpellCastState.LastRequestedSpellId: that + /// records the last spell requested and is cleared only by Reset at session + /// teardown, so using it as a busy flag latches true after the first cast + /// and never clears -- which is exactly what stalled MossTank's first buff + /// pass at one cast. + /// public bool IsCasting { get { - RuntimeSpellCastState? cast; + GameRuntime? runtime; lock (_gate) - cast = _cast; - return cast?.LastRequestedSpellId is not null; + runtime = _runtime; + return runtime is not null + && runtime.InventoryOwner.Transactions.BusyCount > 0; } } @@ -408,6 +421,8 @@ internal sealed class AppAutomationSurface return PluginCastGate.Unavailable; if (!spellbook.Knows(spellId)) return PluginCastGate.NotKnown; + if (IsCasting) + return PluginCastGate.Busy; return cast.EvaluateCastGate(spellId) switch {