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);
+ }
}