feat(plugins): complete Campaign LA5 cross-host hosting

This commit is contained in:
Erik 2026-08-14 18:12:59 +02:00
parent 6c4cd2bbc6
commit 95f4be94db
26 changed files with 1630 additions and 99 deletions

View file

@ -1,6 +1,7 @@
using AcDream.Headless.Configuration;
using AcDream.Headless.Credentials;
using AcDream.Headless.Diagnostics;
using AcDream.Headless.Plugins;
using AcDream.Headless.Policies;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
@ -155,6 +156,7 @@ internal sealed class HeadlessSessionHost : IDisposable
private readonly IDisposable _hostLease;
private readonly IHeadlessBotPolicy _policy;
private readonly IDisposable _policySubscription;
private readonly HeadlessPluginSession _pluginSession;
private readonly LiveSessionHost _liveSession;
private readonly RuntimeLocalPlayerFrameController _localPlayerFrame;
private readonly HeadlessProcessContentOwner.HeadlessProcessContentLease?
@ -238,7 +240,8 @@ internal sealed class HeadlessSessionHost : IDisposable
contentLease = null,
IHeadlessBotPolicy? policyOverride = null,
IRuntimePlacementProjectionSink? placementSinkOverride = null,
FellowshipAllegianceGateCoordinator? gateCoordinator = null)
FellowshipAllegianceGateCoordinator? gateCoordinator = null,
IEnumerable<string>? pluginRoots = null)
{
_descriptor = descriptor
?? throw new ArgumentNullException(nameof(descriptor));
@ -263,6 +266,7 @@ internal sealed class HeadlessSessionHost : IDisposable
IDisposable? hostLease = null;
IHeadlessBotPolicy? policy = null;
IDisposable? policySubscription = null;
HeadlessPluginSession? pluginSession = null;
try
{
var gameplay = new HeadlessGameplayOperations();
@ -297,6 +301,13 @@ internal sealed class HeadlessSessionHost : IDisposable
// descriptor.StatusFile is unset — every call site below stays
// unconditional.
var statusWriter = new SessionStatusWriter(descriptor.StatusFile);
pluginSession = HeadlessPluginSession.Start(
runtime,
diagnostics,
statusWriter,
descriptor.Id,
pluginRoots ?? [],
descriptor.Plugins);
var liveSession = new LiveSessionHost(
runtime.Session,
new LiveSessionHostBindings(
@ -402,9 +413,11 @@ internal sealed class HeadlessSessionHost : IDisposable
_hostLease = hostLease;
_policy = policy;
_policySubscription = policySubscription;
_pluginSession = pluginSession;
}
catch
{
pluginSession?.Dispose();
policySubscription?.Dispose();
policy?.Dispose();
hostLease?.Dispose();
@ -427,6 +440,7 @@ internal sealed class HeadlessSessionHost : IDisposable
/// production code uses to reach the same state.
/// </summary>
internal HeadlessCharacterOptionsSeeder? OptionsSeeder => _optionsSeeder;
internal HeadlessPluginSession Plugins => _pluginSession;
internal string SessionId => _descriptor.Id;
internal string ActiveCharacterName { get; private set; } =
string.Empty;
@ -638,22 +652,26 @@ internal sealed class HeadlessSessionHost : IDisposable
_disposeStage++;
break;
case 4:
_hostLease.Dispose();
_pluginSession.Dispose();
_disposeStage++;
break;
case 5:
_credential.Dispose();
_hostLease.Dispose();
_disposeStage++;
break;
case 6:
Runtime.Dispose();
_credential.Dispose();
_disposeStage++;
break;
case 7:
_contentLease?.Dispose();
Runtime.Dispose();
_disposeStage++;
break;
case 8:
_contentLease?.Dispose();
_disposeStage++;
break;
case 9:
_diagnostics.Message(
_descriptor.Id,
"disposed",