diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml
index 4685e1d..50ee57b 100644
--- a/.forgejo/workflows/ci.yml
+++ b/.forgejo/workflows/ci.yml
@@ -11,8 +11,8 @@ env:
# The OpenAC release whose AcDream.Plugin.Abstractions package this plugin
# builds against. PluginApiPackageVersion in Directory.Build.props has to
# name the same version.
- OPENAC_API_RELEASE_TAG: v0.1.19
- OPENAC_API_VERSION: 0.1.19
+ OPENAC_API_RELEASE_TAG: v0.1.22
+ OPENAC_API_VERSION: 0.1.22
jobs:
build-and-test:
diff --git a/Directory.Build.props b/Directory.Build.props
index 1d2b472..d18c1b5 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -10,12 +10,12 @@
true
- 0.1.0
+ 0.1.1
- 0.1.19
+ 0.1.22
false
diff --git a/README.md b/README.md
index 70d0c4a..9015d56 100644
--- a/README.md
+++ b/README.md
@@ -12,12 +12,12 @@ byte-for-byte what the DECAL plugin sent and accepted; the golden tests under
## Building
The plugin contract is `AcDream.Plugin.Abstractions` of the version named in
-`Directory.Build.props` (`PluginApiPackageVersion`), currently 0.1.19, the
-OpenAC 0.1.19 release. Put that release's package in `packages-local/`; an
-OpenAC client from 0.1.19 on loads the plugin.
+`Directory.Build.props` (`PluginApiPackageVersion`), currently 0.1.22, the
+OpenAC 0.1.22 release. Put that release's package in `packages-local/`; an
+OpenAC client from 0.1.22 on loads the plugin.
```
-gh release download v0.1.19 --repo eriknihlen/OpenAC -p "AcDream.Plugin.Abstractions.0.1.19.nupkg" -D packages-local
+gh release download v0.1.22 --repo eriknihlen/OpenAC -p "AcDream.Plugin.Abstractions.0.1.22.nupkg" -D packages-local
dotnet build OpenAC.MosswartMassacre.slnx
dotnet test OpenAC.MosswartMassacre.slnx
```
@@ -56,3 +56,9 @@ dotnet run --project tools/FrameRecorder -- record 18765 frames.jsonl
/mm ws url ws://localhost:18765/websocket/
dotnet run --project tools/FrameRecorder -- compare decal.jsonl port.jsonl
```
+
+## Local peer subscriptions
+
+Requires OpenAC 0.1.22. Combat vital sharing acquires a local state/cast subscription only while enabled, and releases it on disable, logout or unload. Dashboard vitals still stream every five seconds directly from character state without enabling local peer communication. Existing remote WebSocket frames and timing are unchanged.
+
+Before the new contract release exists, pack AcDream.Plugin.Abstractions 0.1.22 from the matching OpenAC checkout and copy its nupkg into packages-local before restore/build. CI downloads that release, so publish the OpenAC contract before dependent plugin releases.
diff --git a/src/OpenAC.MosswartMassacre/Sharing/VitalSharing.cs b/src/OpenAC.MosswartMassacre/Sharing/VitalSharing.cs
index 6baf92b..de4dd99 100644
--- a/src/OpenAC.MosswartMassacre/Sharing/VitalSharing.cs
+++ b/src/OpenAC.MosswartMassacre/Sharing/VitalSharing.cs
@@ -56,6 +56,7 @@ internal sealed class VitalSharing : IMmFeature, IBackendConnectionListener, ISh
private readonly MmContext _context;
private readonly PeerTable _peers = new();
private IDisposable? _timer;
+ private IDisposable? _peerSubscription;
private int _lastHealth = -1, _lastMaxHealth = -1;
private int _lastStamina = -1, _lastMaxStamina = -1;
@@ -103,6 +104,8 @@ internal sealed class VitalSharing : IMmFeature, IBackendConnectionListener, ISh
if (IsActive)
return;
IsActive = true;
+ _peerSubscription = _context.Automation.Network.Subscribe(
+ PluginPeerCapabilities.ClientState | PluginPeerCapabilities.Casts);
ResetOutgoingState();
_timer = _context.Scheduler.Every(TickInterval, OnTick);
_context.Send(new
@@ -121,6 +124,8 @@ internal sealed class VitalSharing : IMmFeature, IBackendConnectionListener, ISh
if (!IsActive)
return;
IsActive = false;
+ _peerSubscription?.Dispose();
+ _peerSubscription = null;
_timer?.Dispose();
_timer = null;
_context.Send(new
diff --git a/src/OpenAC.MosswartMassacre/plugin.json b/src/OpenAC.MosswartMassacre/plugin.json
index 47694d1..8f0b2e4 100644
--- a/src/OpenAC.MosswartMassacre/plugin.json
+++ b/src/OpenAC.MosswartMassacre/plugin.json
@@ -1,12 +1,12 @@
{
"id": "openac.mosswartmassacre",
"displayName": "Mosswart Massacre",
- "version": "0.1.0",
+ "version": "0.1.1",
"entryDll": "OpenAC.MosswartMassacre.dll",
"apiVersion": 1,
"kinds": ["Gameplay"],
"hosts": ["Graphical", "Headless"],
- "minHostVersion": "0.1.19",
+ "minHostVersion": "0.1.22",
"capabilitiesVersion": 1,
"capabilities": [
{ "name": "network", "note": "Streams stats, vitals, chat and inventory to Mosswart Overlord, and receives shared vitals and commands from it." },
diff --git a/tests/OpenAC.MosswartMassacre.Tests/Fakes/FakeNetwork.cs b/tests/OpenAC.MosswartMassacre.Tests/Fakes/FakeNetwork.cs
index e3a7c88..132537f 100644
--- a/tests/OpenAC.MosswartMassacre.Tests/Fakes/FakeNetwork.cs
+++ b/tests/OpenAC.MosswartMassacre.Tests/Fakes/FakeNetwork.cs
@@ -8,6 +8,16 @@ namespace OpenAC.MosswartMassacre.Tests.Fakes;
///
public sealed class FakeNetwork : INetworkAutomation
{
+ public bool SupportsSubscriptions => true;
+ public List Requests { get; } = [];
+ public int ActiveLeases { get; private set; }
+ public IDisposable Subscribe(PluginPeerCapabilities capabilities)
+ { Requests.Add(capabilities); ActiveLeases++; return new Lease(this); }
+ private sealed class Lease(FakeNetwork owner) : IDisposable
+ {
+ private bool _disposed;
+ public void Dispose() { if (_disposed) return; _disposed = true; owner.ActiveLeases--; }
+ }
public bool IsAvailable { get; set; } = true;
public List ImportedClients { get; } = [];
diff --git a/tests/OpenAC.MosswartMassacre.Tests/Sharing/VitalSharingTests.cs b/tests/OpenAC.MosswartMassacre.Tests/Sharing/VitalSharingTests.cs
index 10ac642..61f31a0 100644
--- a/tests/OpenAC.MosswartMassacre.Tests/Sharing/VitalSharingTests.cs
+++ b/tests/OpenAC.MosswartMassacre.Tests/Sharing/VitalSharingTests.cs
@@ -42,6 +42,21 @@ public sealed class VitalSharingTests
private static VitalSharing Feature(PluginHarness harness) => harness.Feature();
+ [Fact]
+ public void Local_peer_subscription_exists_only_while_combat_sharing_is_enabled()
+ {
+ using var harness = Sharing();
+ var network = harness.Host.Automation.Network;
+ Assert.Empty(network.Requests);
+ Feature(harness).Start();
+ Feature(harness).Start();
+ Assert.Equal(1, network.ActiveLeases);
+ Assert.Equal(PluginPeerCapabilities.ClientState | PluginPeerCapabilities.Casts, Assert.Single(network.Requests));
+ Feature(harness).OnLogoff();
+ Feature(harness).Dispose();
+ Assert.Equal(0, network.ActiveLeases);
+ }
+
[Fact]
public void Subscribe_on_start_carries_the_player_id_and_tags()
{