feat(D.5.1): capture IconId in CreateObject.Parsed (was discarded at cs:516)

ReadPackedDwordOfKnownType at the old line 516 was throwing the icon dat
id away. Declare iconId before the try-block, assign it there, and pass
IconId: iconId in the Parsed initializer so downstream UI (action bar /
equipment panels) can read the 0x06xxxxxx dat id without a separate lookup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-16 21:44:01 +02:00
parent 30b28c248a
commit da171cb4e3
2 changed files with 36 additions and 4 deletions

View file

@ -127,6 +127,12 @@ public static class CreateObject
// defaults (0.05f elasticity, 0.5f friction). // defaults (0.05f elasticity, 0.5f friction).
float? Friction = null, float? Friction = null,
float? Elasticity = null, float? Elasticity = null,
// D.5.1 (2026-06-16): icon dat id (0x06xxxxxx) from the WeenieHeader
// fixed prefix. Previously discarded at cs:516; surfaced so the action
// bar / equipment UI can display the correct icon sprite without a
// separate dat lookup. Zero means "not sent" (packed zero sentinel in
// ReadPackedDwordOfKnownType preserves 0 as-is).
uint IconId = 0,
// 2026-05-15: optional WeenieHeader tail. The retail // 2026-05-15: optional WeenieHeader tail. The retail
// `ITEM_USEABLE _useability` (acclient.h:6478) — gates whether the // `ITEM_USEABLE _useability` (acclient.h:6478) — gates whether the
// R-key Use action does anything. <c>(Useability &amp; USEABLE_REMOTE // R-key Use action does anything. <c>(Useability &amp; USEABLE_REMOTE
@ -506,6 +512,7 @@ public static class CreateObject
string? name = null; string? name = null;
uint? itemType = null; uint? itemType = null;
uint weenieFlags = 0; uint weenieFlags = 0;
uint iconId = 0;
if (body.Length - pos >= 4) if (body.Length - pos >= 4)
{ {
weenieFlags = ReadU32(body, ref pos); weenieFlags = ReadU32(body, ref pos);
@ -513,7 +520,7 @@ public static class CreateObject
{ {
name = ReadString16L(body, ref pos); name = ReadString16L(body, ref pos);
_ = ReadPackedDword(body, ref pos); // WeenieClassId _ = ReadPackedDword(body, ref pos); // WeenieClassId
_ = ReadPackedDwordOfKnownType(body, ref pos, IconTypePrefix); iconId = ReadPackedDwordOfKnownType(body, ref pos, IconTypePrefix);
if (body.Length - pos >= 4) if (body.Length - pos >= 4)
itemType = ReadU32(body, ref pos); itemType = ReadU32(body, ref pos);
if (body.Length - pos >= 4) if (body.Length - pos >= 4)
@ -611,7 +618,8 @@ public static class CreateObject
instanceSeq, teleportSeq, serverControlSeq, forcePositionSeq, instanceSeq, teleportSeq, serverControlSeq, forcePositionSeq,
physicsState, objectDescriptionFlags, physicsState, objectDescriptionFlags,
friction, elasticity, friction, elasticity,
useability, useRadius); IconId: iconId,
Useability: useability, UseRadius: useRadius);
// Local helper: if we ran out of fields past PhysicsData, still // Local helper: if we ran out of fields past PhysicsData, still
// return the useful prefix (guid/position/setup/animParts/textures/palettes/scale/motion). // return the useful prefix (guid/position/setup/animParts/textures/palettes/scale/motion).

View file

@ -156,6 +156,29 @@ public sealed class CreateObjectTests
Assert.Equal(2.5f, parsed.Value.UseRadius!.Value, precision: 3); Assert.Equal(2.5f, parsed.Value.UseRadius!.Value, precision: 3);
} }
// -----------------------------------------------------------------------
// D.5.1 (2026-06-16): IconId was discarded at cs:516 — surface it so the
// action bar / equipment UI can read icon dat ids from spawn messages.
// -----------------------------------------------------------------------
[Fact]
public void TryParse_IconId_Surfaced()
{
// Icon dat id 0x06001234: the wire writer strips the 0x06000000 prefix
// before packing (WritePackedDwordOfKnownType strips it), so we write
// 0x1234 as the packed value and expect 0x06001234 back.
byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
guid: 0x50000009u,
name: "SwordIcon",
itemType: (uint)ItemType.MeleeWeapon,
iconId: 0x1234u);
var parsed = CreateObject.TryParse(body);
Assert.NotNull(parsed);
Assert.Equal(0x06001234u, parsed!.Value.IconId);
}
private static byte[] BuildMinimalCreateObjectWithWeenieHeader( private static byte[] BuildMinimalCreateObjectWithWeenieHeader(
uint guid, uint guid,
string name, string name,
@ -163,6 +186,7 @@ public sealed class CreateObjectTests
uint physicsState = 0, uint physicsState = 0,
uint objectDescriptionFlags = 0, uint objectDescriptionFlags = 0,
uint weenieFlags = 0, uint weenieFlags = 0,
uint iconId = 0,
uint? value = null, uint? value = null,
uint? useability = null, uint? useability = null,
float? useRadius = null) float? useRadius = null)
@ -187,8 +211,8 @@ public sealed class CreateObjectTests
// Fixed WeenieHeader prefix per ACE SerializeCreateObject. // Fixed WeenieHeader prefix per ACE SerializeCreateObject.
WriteU32(bytes, weenieFlags); // weenieFlags WriteU32(bytes, weenieFlags); // weenieFlags
WriteString16L(bytes, name); WriteString16L(bytes, name);
WritePackedDword(bytes, 0x1234); // WeenieClassId WritePackedDword(bytes, 0x1234); // WeenieClassId
WritePackedDword(bytes, 0); // IconId via known-type writer WritePackedDword(bytes, iconId); // IconId via known-type writer (prefix stripped by ACE writer)
WriteU32(bytes, itemType); WriteU32(bytes, itemType);
WriteU32(bytes, objectDescriptionFlags); WriteU32(bytes, objectDescriptionFlags);
Align4(bytes); Align4(bytes);