diff --git a/.gitignore b/.gitignore
index 4a897d1..c875e6d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ Thumbs.db
# Frame recordings from live runs
*.jsonl
+packages-staging/
diff --git a/Directory.Build.props b/Directory.Build.props
index 5f5ccc0..1f1ab3d 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -15,7 +15,7 @@
- 0.1.19-mmpreview.1
+ 0.1.19-mm.1
false
diff --git a/src/OpenAC.MosswartMassacre/Inventory/ItemHeaderSource.cs b/src/OpenAC.MosswartMassacre/Inventory/ItemHeaderSource.cs
index 329374a..801a855 100644
--- a/src/OpenAC.MosswartMassacre/Inventory/ItemHeaderSource.cs
+++ b/src/OpenAC.MosswartMassacre/Inventory/ItemHeaderSource.cs
@@ -61,8 +61,9 @@ internal interface IItemHeaderSource
}
///
-/// The contract this plugin builds against exposes none of these words, so
-/// every one is absent. Replace this when the contract grows them.
+/// The header words from the object description the host kept for the item.
+/// An item without one (built from a vendor listing, say) has none, and every
+/// word is then absent.
///
internal sealed class HostItemHeaderSource : IItemHeaderSource
{
@@ -72,5 +73,50 @@ internal sealed class HostItemHeaderSource : IItemHeaderSource
{
}
- public ItemHeaderWords Read(in PluginInventoryItem item) => default;
+ public ItemHeaderWords Read(in PluginInventoryItem item)
+ {
+ if (item.Header is not { } header)
+ return default;
+ return new ItemHeaderWords
+ {
+ WeenieHeaderFlags = header.WeenieHeaderFlags,
+ WeenieHeaderFlags2 = header.WeenieHeaderFlags2,
+ PhysicsDescriptionFlags = header.PhysicsDescriptionFlags,
+ PhysicsWord = header.WeenieHeaderFlags is uint weenie
+ ? FrameworkFlags(weenie, header.PhysicsDescriptionFlags ?? 0u, item.ItemType, header.ObjectDescriptionFlags ?? item.PublicFlags)
+ : null,
+ SetupId = header.SetupId,
+ // The framework filed 0.0 under its scale key whenever the
+ // physics description carried a scale, whatever the scale was.
+ Scale = header.Scale is null ? null : 0.0,
+ ParentObjectId = header.ParentObjectId,
+ ParentLocation = header.ParentLocation is uint location ? unchecked((int)location) : null,
+ HookType = header.HookType is uint hook ? unchecked((int)hook) : null,
+ };
+ }
+
+ ///
+ /// The framework's own summary word it kept per object (key 218103831),
+ /// derived from the header: stackable, has a position, lockable,
+ /// inscribable, usable, a container. Checked against every recorded item
+ /// that carries both this word and the header flags.
+ ///
+ internal static uint FrameworkFlags(uint weenieFlags, uint physicsFlags, uint itemType, uint descriptionFlags)
+ {
+ uint flags = 0u;
+ if ((weenieFlags & 0x3000u) != 0u)
+ flags |= 0x01u; // stack size or maximum stack size sent
+ if ((physicsFlags & 0x8000u) != 0u)
+ flags |= 0x02u; // a position sent
+ if ((itemType & 0x20000u) != 0u)
+ flags |= 0x04u; // a lockable type
+ if ((descriptionFlags & 0x2u) != 0u)
+ flags |= 0x10u; // inscribable
+ if ((weenieFlags & 0xC00u) != 0u)
+ flags |= 0x20u; // uses left or total uses sent
+ if ((weenieFlags & 0x6u) != 0u)
+ flags |= 0x80u; // item or pack capacity sent
+ return flags;
+ }
}
+
diff --git a/src/OpenAC.MosswartMassacre/plugin.json b/src/OpenAC.MosswartMassacre/plugin.json
index 8587c2c..44fc070 100644
--- a/src/OpenAC.MosswartMassacre/plugin.json
+++ b/src/OpenAC.MosswartMassacre/plugin.json
@@ -6,7 +6,7 @@
"apiVersion": 1,
"kinds": ["Gameplay"],
"hosts": ["Graphical", "Headless"],
- "minHostVersion": "0.1.18",
+ "minHostVersion": "0.1.19",
"capabilitiesVersion": 1,
"capabilities": [
{ "name": "network", "note": "Streams telemetry, vitals, chat, inventory and stats to the Mosswart Overlord backend, and receives shared vitals and chat-box commands from it." },
diff --git a/tests/OpenAC.MosswartMassacre.Tests/Inventory/RecordedItemTests.cs b/tests/OpenAC.MosswartMassacre.Tests/Inventory/RecordedItemTests.cs
new file mode 100644
index 0000000..1e38f5c
--- /dev/null
+++ b/tests/OpenAC.MosswartMassacre.Tests/Inventory/RecordedItemTests.cs
@@ -0,0 +1,69 @@
+using AcDream.Plugin.Abstractions;
+using OpenAC.MosswartMassacre.Inventory;
+
+namespace OpenAC.MosswartMassacre.Tests.Inventory;
+
+///
+/// Items exactly as the original recorded them from a live game (the dumps in
+/// the Mag-Plugins repository), rebuilt from the header words the host now
+/// reports: the key tables must come out the same, value for value.
+///
+public sealed class RecordedItemTests
+{
+ [Fact]
+ public void A_recorded_pyreal_mote_rebuilds_key_for_key()
+ {
+ // Barris.json, the first item: a Pyreal Mote in a side pack, never
+ // appraised. Its IntValues and DoubleValues as recorded:
+ var expectedInts = new Dictionary
+ {
+ [218103847] = 137345, [218103830] = 33556406, [218103832] = 271151128,
+ [19] = 10, [218103808] = 6353, [218103809] = 7208, [218103834] = 128,
+ [218103843] = 524296, [218103835] = 18, [218103826] = 128, [218103831] = 17,
+ [218103814] = 1, [218103815] = 1, [5] = 1, [218103810] = 1342431769,
+ [218103822] = 0, [218103828] = 2,
+ };
+ var expectedDoubles = new Dictionary { [167772170] = 0.0 };
+
+ var item = new PluginInventoryItem(
+ unchecked((uint)-2126483018), 6353u, "Pyreal Mote", 128u, 1342431769u, 0u, 0u, 0u,
+ 524296u, 128u, 18u, 1, 0, 0, 0u, 0, 0, 0u, false, 0d, 0, 0, 0, 0d, 0, 0, 0)
+ {
+ ObjectClass = PluginObjectClass.Misc,
+ IconId = 0x06000000u + 7208u,
+ MaximumStackSize = 1,
+ Burden = 1,
+ Value = 10,
+ Header = new PluginObjectHeader(
+ WeenieHeaderFlags: 271151128u,
+ WeenieHeaderFlags2: null,
+ PhysicsDescriptionFlags: 137345u,
+ PhysicsState: null,
+ ObjectDescriptionFlags: 18u,
+ SetupId: 33556406u,
+ Scale: 1.0f,
+ HookType: 2u,
+ ParentObjectId: null,
+ ParentLocation: null,
+ UseRadius: null),
+ };
+
+ MyWorldObject record = DecalItemBuilder.Create(new ItemSource(
+ item, null, false, 0, HostItemHeaderSource.Instance.Read(item)));
+
+ Assert.Equal(expectedInts.OrderBy(p => p.Key), record.IntValues.OrderBy(p => p.Key));
+ Assert.Equal(expectedDoubles.OrderBy(p => p.Key), record.DoubleValues.OrderBy(p => p.Key));
+ Assert.Equal("Pyreal Mote", record.StringValues[1]);
+ }
+
+ [Theory]
+ // Values of key 218103831 recorded for items, with their header words.
+ [InlineData(0x1000u, 0u, 0u, 0u, 0x01u)] // a stack
+ [InlineData(0u, 0x8000u, 0u, 0u, 0x02u)] // on the ground
+ [InlineData(0u, 0u, 0x20000u, 0u, 0x04u)] // lockable
+ [InlineData(0u, 0u, 0u, 0x2u, 0x10u)] // inscribable
+ [InlineData(0x800u, 0u, 0u, 0u, 0x20u)] // uses
+ [InlineData(0x2u, 0u, 0u, 0x12u, 0x90u)] // a pack
+ public void The_frameworks_summary_word(uint weenie, uint physics, uint itemType, uint description, uint expected) =>
+ Assert.Equal(expected, HostItemHeaderSource.FrameworkFlags(weenie, physics, itemType, description));
+}