From b83f17a927efed4e37d381a9ad3607d07453dbdf Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 18 Jun 2026 15:57:12 +0200 Subject: [PATCH] feat(D.5.4): add item fields to ClientObject + WeenieData ingest DTO Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.Core/Items/ClientObject.cs | 42 ++++++++++++++++++- .../Items/ClientObjectTableTests.cs | 32 ++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/AcDream.Core/Items/ClientObject.cs b/src/AcDream.Core/Items/ClientObject.cs index 773c1c4e..04d9c873 100644 --- a/src/AcDream.Core/Items/ClientObject.cs +++ b/src/AcDream.Core/Items/ClientObject.cs @@ -127,7 +127,7 @@ public sealed class PropertyBundle public sealed class ClientObject { public uint ObjectId { get; init; } - public uint WeenieClassId { get; init; } // "blueprint" + public uint WeenieClassId { get; set; } // "blueprint" public string Name { get; set; } = ""; public ItemType Type { get; set; } public EquipMask ValidLocations { get; set; } @@ -150,9 +150,49 @@ public sealed class ClientObject public int ContainerSlot { get; set; } = -1; public bool Attuned { get; set; } public bool Bonded { get; set; } + public uint WielderId { get; set; } // PropertyInstanceId.Wielder; 0 = not wielded + public int ItemsCapacity { get; set; } // main-pack slots (containers) + public int ContainersCapacity{ get; set; } // side-pack slots (containers) + public uint Priority { get; set; } // ClothingPriority / CoverageMask layer order + public int Structure { get; set; } // charges/uses remaining + public int MaxStructure { get; set; } + public float Workmanship { get; set; } // 0..10 (fractional on the wire) public PropertyBundle Properties { get; } = new(); } +/// +/// The wire-delivered patch from a CreateObject (0xF745). Nullable fields +/// were gated by a WeenieHeader flag that was ABSENT โ€” the merge upsert +/// (ClientObjectTable.Ingest) leaves the existing value untouched +/// for those, matching retail's SetWeenieDesc (patches only present fields). +/// Non-nullable id/effect fields use 0 = "not sent". Effects is assigned +/// unconditionally (0 clears) โ€” the D.5.2 icon contract. Quantity fields are +/// int? (ACE PropertyInt convention); id/mask fields are uint?. +/// +public readonly record struct WeenieData( + uint Guid, + string? Name, + ItemType? Type, + uint WeenieClassId, + uint IconId, + uint IconOverlayId, + uint IconUnderlayId, + uint Effects, + int? Value, + int? StackSize, + int? StackSizeMax, + int? Burden, + uint? ContainerId, + uint? WielderId, + uint? ValidLocations, + uint? CurrentWieldedLocation, + uint? Priority, + int? ItemsCapacity, + int? ContainersCapacity, + int? Structure, + int? MaxStructure, + float? Workmanship); + /// /// Container = inventory pack. Hierarchy is strictly 2-deep: character /// โ†’ side packs; a side pack cannot hold another side pack (r06 ยง7). diff --git a/tests/AcDream.Core.Tests/Items/ClientObjectTableTests.cs b/tests/AcDream.Core.Tests/Items/ClientObjectTableTests.cs index e11de2b4..a975d327 100644 --- a/tests/AcDream.Core.Tests/Items/ClientObjectTableTests.cs +++ b/tests/AcDream.Core.Tests/Items/ClientObjectTableTests.cs @@ -198,4 +198,36 @@ public sealed class ClientObjectTableTests Assert.True(ok); Assert.Equal(0u, repo.Get(0x500000ADu)!.Effects); } + + [Fact] + public void ClientObject_NewFields_DefaultAndSettable() + { + var o = new ClientObject + { + ObjectId = 1, WielderId = 0x42u, ItemsCapacity = 24, ContainersCapacity = 7, + Priority = 8u, Structure = 5, MaxStructure = 10, Workmanship = 7.5f, + }; + o.WeenieClassId = 0xABCDu; // now settable + Assert.Equal(0x42u, o.WielderId); + Assert.Equal(24, o.ItemsCapacity); + Assert.Equal(7, o.ContainersCapacity); + Assert.Equal(8u, o.Priority); + Assert.Equal(5, o.Structure); + Assert.Equal(10, o.MaxStructure); + Assert.Equal(7.5f, o.Workmanship); + Assert.Equal(0xABCDu, o.WeenieClassId); + } + + [Fact] + public void WeenieData_Construct() + { + var d = new WeenieData(Guid: 1, Name: "x", Type: ItemType.Misc, WeenieClassId: 2, + IconId: 0x06001234u, IconOverlayId: 0, IconUnderlayId: 0, Effects: 0, + Value: 5, StackSize: 1, StackSizeMax: 1, Burden: 10, + ContainerId: 0x99u, WielderId: null, ValidLocations: null, + CurrentWieldedLocation: null, Priority: null, + ItemsCapacity: null, ContainersCapacity: null, + Structure: null, MaxStructure: null, Workmanship: null); + Assert.Equal(0x99u, d.ContainerId); + } }