feat(mosstank): add VTank-style automation PoC

This commit is contained in:
Erik 2026-08-27 18:57:21 +02:00
parent f6fe0f2a4f
commit 4e6e9bc9d9
212 changed files with 49462 additions and 416 deletions

View file

@ -15,7 +15,7 @@ internal sealed class HeadlessMovementInputSource(
public MovementInput Capture()
{
if (_movement.HasCommandInput)
return _movement.CommandInput;
return _movement.CommandInput with { IsPersistentCommand = true };
return new MovementInput(
Forward: _movement.AutoRunActive,
Run: true);

View file

@ -341,7 +341,13 @@ internal sealed class HeadlessSessionHost : IDisposable
// descriptor.StatusFile is unset — every call site below stays
// unconditional.
var statusWriter = new SessionStatusWriter(descriptor.StatusFile);
var chatCommandSurface = new LiveChatCommandSurface();
var pluginCommands = new AcDream.Core.Plugins.PluginCommandRegistry(
(verb, error) => diagnostics.Failure(
descriptor.Id,
$"plugin-command-{verb}",
error));
var chatCommandSurface = new LiveChatCommandSurface(
pluginCommands.TryHandle);
var loginCommands = new LoginCommandSequence(
descriptor.LoginCommands,
TimeSpan.FromMilliseconds(descriptor.LoginCommandDelayMs),
@ -359,7 +365,8 @@ internal sealed class HeadlessSessionHost : IDisposable
statusWriter,
descriptor.Id,
pluginRoots ?? [],
descriptor.Plugins);
descriptor.Plugins,
pluginCommands);
var liveSession = new LiveSessionHost(
runtime.Session,
new LiveSessionHostBindings(
@ -1201,6 +1208,7 @@ internal sealed class HeadlessSessionHost : IDisposable
{
Runtime.InventoryOwner.ExternalContainers
.ApplyUseDone(error);
Runtime.ActionOwner.SpellCast.CompleteUse(error);
Runtime.ActionOwner.Transactions.CompleteUse(error);
},
Runtime.InventoryOwner.ItemMana,

View file

@ -38,15 +38,18 @@ internal sealed class HeadlessPluginHost
internal HeadlessPluginHost(
GameRuntime runtime,
IPluginLogger logger)
IPluginLogger logger,
IPluginCommandRegistry? commands = null)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
Log = logger ?? throw new ArgumentNullException(nameof(logger));
Commands = commands ?? NoOpPluginCommandRegistry.Instance;
_eventSubscription = runtime.Subscribe(this);
}
public bool HasUi => false;
public IPluginLogger Log { get; }
public IPluginCommandRegistry Commands { get; }
public IGameState State => this;
public IEvents Events => this;
public ISelectionService Selection => _runtime.ActionOwner.Selection;

View file

@ -44,7 +44,8 @@ internal sealed class HeadlessPluginSession : IDisposable
SessionStatusWriter statusWriter,
string sessionId,
IEnumerable<string> roots,
IReadOnlyList<string>? allowList)
IReadOnlyList<string>? allowList,
IPluginCommandRegistry? commands = null)
{
ArgumentNullException.ThrowIfNull(runtime);
ArgumentNullException.ThrowIfNull(diagnostics);
@ -57,7 +58,8 @@ internal sealed class HeadlessPluginSession : IDisposable
new HeadlessPluginLogger(
diagnostics,
sessionId,
() => runtime.Generation.Value));
() => runtime.Generation.Value),
commands);
var plugins = new PluginSession(
host,
status => Report(statusWriter, sessionId, status),