feat(D.5.4): capture full item field set in CreateObject parser

WeenieClassId + Value/StackSize/MaxStackSize/Burden/capacities/Container/Wielder/
ValidLocations/CurrentWieldedLocation/Priority/Structure/Workmanship. Nullable =
flag absent (don't clobber on merge). Cursor walk unchanged; +cursor-integrity test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-18 15:41:15 +02:00
parent b506f53633
commit 91970c4fe9
2 changed files with 142 additions and 28 deletions

View file

@ -378,6 +378,66 @@ public sealed class CreateObjectTests
Assert.Equal(0u, parsed!.Value.UiEffects);
}
[Fact]
public void TryParse_WeenieClassId_Surfaced()
{
byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
guid: 0x50000020u, name: "Sword", itemType: (uint)ItemType.MeleeWeapon,
weenieClassId: 0xABCDu);
var parsed = CreateObject.TryParse(body);
Assert.NotNull(parsed);
Assert.Equal(0xABCDu, parsed!.Value.WeenieClassId);
}
[Fact]
public void TryParse_FullItemFields_Captured()
{
uint flags =
0x00000008u | 0x00001000u | 0x00002000u | 0x00200000u |
0x00000002u | 0x00000004u | 0x00004000u | 0x00008000u |
0x00010000u | 0x00020000u | 0x00040000u | 0x00000400u |
0x00000800u | 0x01000000u;
byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
guid: 0x50000021u, name: "Pack", itemType: (uint)ItemType.Container,
weenieFlags: flags,
value: 250u, stackSize: 7, maxStackSize: 100u, burden: 42,
itemsCapacity: 24, containersCapacity: 7,
container: 0x50000099u, wielder: 0x5000009Au,
validLocations: 0x02000000u, currentWieldedLocation: 0x02000000u,
priority: 8u, structure: 5, maxStructure: 10, workmanship: 7.5f);
var parsed = CreateObject.TryParse(body);
Assert.NotNull(parsed);
var p = parsed!.Value;
Assert.Equal(250, p.Value);
Assert.Equal(7, p.StackSize);
Assert.Equal(100, p.StackSizeMax);
Assert.Equal(42, p.Burden);
Assert.Equal(24, p.ItemsCapacity);
Assert.Equal(7, p.ContainersCapacity);
Assert.Equal(0x50000099u, p.ContainerId);
Assert.Equal(0x5000009Au, p.WielderId);
Assert.Equal(0x02000000u, p.ValidLocations);
Assert.Equal(0x02000000u, p.CurrentWieldedLocation);
Assert.Equal(8u, p.Priority);
Assert.Equal(5, p.Structure);
Assert.Equal(10, p.MaxStructure);
Assert.Equal(7.5f, p.Workmanship);
}
[Fact]
public void TryParse_MidTailFieldsSet_StillReachesIconOverlay()
{
uint flags = 0x00001000u | 0x00004000u | 0x40000000u;
byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
guid: 0x50000022u, name: "Ring", itemType: (uint)ItemType.Jewelry,
weenieFlags: flags, stackSize: 1, container: 0x500000F0u,
iconOverlayId: 0x4321u);
var parsed = CreateObject.TryParse(body);
Assert.NotNull(parsed);
Assert.Equal(0x06004321u, parsed!.Value.IconOverlayId);
Assert.Equal(0x500000F0u, parsed.Value.ContainerId);
}
private static byte[] BuildMinimalCreateObjectWithWeenieHeader(
uint guid,
string name,
@ -397,7 +457,17 @@ public sealed class CreateObjectTests
ushort? structure = null,
ushort? maxStructure = null,
ushort? stackSize = null,
ushort? burden = null)
ushort? burden = null,
uint weenieClassId = 0x1234,
uint? maxStackSize = null,
byte? itemsCapacity = null,
byte? containersCapacity = null,
uint? container = null,
uint? wielder = null,
uint? validLocations = null,
uint? currentWieldedLocation = null,
uint? priority = null,
float? workmanship = null)
{
var bytes = new List<byte>();
WriteU32(bytes, CreateObject.Opcode);
@ -419,7 +489,7 @@ public sealed class CreateObjectTests
// Fixed WeenieHeader prefix per ACE SerializeCreateObject.
WriteU32(bytes, weenieFlags); // weenieFlags
WriteString16L(bytes, name);
WritePackedDword(bytes, 0x1234); // WeenieClassId
WritePackedDword(bytes, weenieClassId); // WeenieClassId
WritePackedDword(bytes, iconId); // IconId via known-type writer (prefix stripped by ACE writer)
WriteU32(bytes, itemType);
WriteU32(bytes, objectDescriptionFlags);
@ -435,8 +505,8 @@ public sealed class CreateObjectTests
// its weenieFlags bit is set, matching the parser's walker exactly.
// Fields not parameterized above default to 0.
if ((weenieFlags & 0x00000001u) != 0) { /* PluralName — not parameterized */ }
if ((weenieFlags & 0x00000002u) != 0) bytes.Add(0); // ItemsCapacity u8
if ((weenieFlags & 0x00000004u) != 0) bytes.Add(0); // ContainersCapacity u8
if ((weenieFlags & 0x00000002u) != 0) bytes.Add(itemsCapacity ?? 0); // ItemsCapacity u8
if ((weenieFlags & 0x00000004u) != 0) bytes.Add(containersCapacity ?? 0); // ContainersCapacity u8
if ((weenieFlags & 0x00000100u) != 0) WriteU16(bytes, 0); // AmmoType u16
if ((weenieFlags & 0x00000008u) != 0) WriteU32(bytes, value ?? 0u); // Value u32
if ((weenieFlags & 0x00000010u) != 0) WriteU32(bytes, useability ?? 0u); // Usable u32
@ -452,19 +522,19 @@ public sealed class CreateObjectTests
if ((weenieFlags & 0x00000400u) != 0) WriteU16(bytes, structure ?? 0); // Structure u16
if ((weenieFlags & 0x00000800u) != 0) WriteU16(bytes, maxStructure ?? 0); // MaxStructure u16
if ((weenieFlags & 0x00001000u) != 0) WriteU16(bytes, stackSize ?? 0); // StackSize u16
if ((weenieFlags & 0x00002000u) != 0) WriteU16(bytes, 0); // MaxStackSize u16
if ((weenieFlags & 0x00004000u) != 0) WriteU32(bytes, 0); // Container u32
if ((weenieFlags & 0x00008000u) != 0) WriteU32(bytes, 0); // Wielder u32
if ((weenieFlags & 0x00010000u) != 0) WriteU32(bytes, 0); // ValidLocations u32
if ((weenieFlags & 0x00020000u) != 0) WriteU32(bytes, 0); // CurrentlyWieldedLocation u32
if ((weenieFlags & 0x00040000u) != 0) WriteU32(bytes, 0); // Priority u32
if ((weenieFlags & 0x00002000u) != 0) WriteU16(bytes, (ushort)(maxStackSize ?? 0)); // MaxStackSize u16
if ((weenieFlags & 0x00004000u) != 0) WriteU32(bytes, container ?? 0); // Container u32
if ((weenieFlags & 0x00008000u) != 0) WriteU32(bytes, wielder ?? 0); // Wielder u32
if ((weenieFlags & 0x00010000u) != 0) WriteU32(bytes, validLocations ?? 0); // ValidLocations u32
if ((weenieFlags & 0x00020000u) != 0) WriteU32(bytes, currentWieldedLocation ?? 0); // CurrentlyWieldedLocation u32
if ((weenieFlags & 0x00040000u) != 0) WriteU32(bytes, priority ?? 0); // Priority u32
if ((weenieFlags & 0x00100000u) != 0) bytes.Add(0); // RadarBlipColor u8
if ((weenieFlags & 0x00800000u) != 0) bytes.Add(0); // RadarBehavior u8
if ((weenieFlags & 0x08000000u) != 0) WriteU16(bytes, 0); // PScript u16
if ((weenieFlags & 0x01000000u) != 0) // Workmanship f32
{
Span<byte> tmp = stackalloc byte[4];
BinaryPrimitives.WriteSingleLittleEndian(tmp, 0f);
BinaryPrimitives.WriteSingleLittleEndian(tmp, workmanship ?? 0f);
bytes.AddRange(tmp.ToArray());
}
if ((weenieFlags & 0x00200000u) != 0) WriteU16(bytes, burden ?? 0); // Burden u16