feat(D.5.1): parse item IconOverlay/IconUnderlay from CreateObject -> faithful icon overlay layer

The CreateObject optional-tail walker previously stopped at UseRadius (~20 fields
before IconOverlay). This left ItemInstance.IconOverlayId/IconUnderlayId always 0,
so IconComposer's underlay/overlay layers were never drawn on toolbar icons.

Exact field order verified against ACE WorldObject_Networking.cs:87-219 (the
serializer is the authority; acdream connects to a local ACE server):
  UseRadius → TargetType(u32) → UiEffects(u32) → CombatUse(sbyte) →
  Structure(u16) → MaxStructure(u16) → StackSize(u16) → MaxStackSize(u16) →
  Container(u32) → Wielder(u32) → ValidLocations(u32) →
  CurrentlyWieldedLocation(u32) → Priority(u32) → RadarBlipColor(u8) →
  RadarBehavior(u8) → PScript(u16) → Workmanship(f32) → Burden(u16) →
  Spell(u16) → HouseOwner(u32) → HouseRestrictions(variable RestrictionDB) →
  HookItemTypes(u32) → Monarch(u32) → HookType(u16) →
  IconOverlay(PackedDwordKnownType) ← CAPTURE →
  IconUnderlay from weenieFlags2 bit 0x01 ← CAPTURE

RestrictionDB handled correctly: Version(u32) + OpenStatus(u32) + MonarchId(u32)
+ count(u16) + numBuckets(u16) + count×8 bytes entries. Length-aware skip, not a
fixed constant.

weenieFlags2 is now CAPTURED (not skipped) when IncludesSecondHeader
(objDescFlags bit 0x04000000) is set, so the IconUnderlay bit can be tested.

The entire extended walk is inside try/catch: truncated packets degrade to
IconOverlayId=0 / IconUnderlayId=0 (no overlay drawn), never corrupting.

Threading: CreateObject.Parsed → WorldSession.EntitySpawn → GameWindow
OnLiveEntitySpawned → Items.EnrichItem — both ids thread through all three
seams. EnrichItem extended with optional iconOverlayId + iconUnderlayId params
(defaulted 0, backward-compatible).

No change to IconComposer or ToolbarController (they already consume the ids).

Tests: 4 new CreateObject tests (IconOverlay only, overlay+underlay, no-overlay
regression, intermediate-fields cursor arithmetic). Full suite: 0 failures,
2636 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-17 14:34:47 +02:00
parent a7cad5566b
commit 8a42066192
5 changed files with 421 additions and 39 deletions

View file

@ -144,13 +144,22 @@ public sealed class ItemRepository
/// Raises ItemPropertiesUpdated whenever the item is found (matching the
/// UpdateProperties convention — it fires on found regardless of whether a field
/// actually changed) so bound widgets (the toolbar) re-render.
/// <para>
/// D.5.1 (2026-06-17): also accepts <paramref name="iconOverlayId"/> and
/// <paramref name="iconUnderlayId"/> from the extended WeenieHeader tail. Both
/// default to 0 (not sent by server). IconComposer.GetIcon already composites
/// underlay/base/overlay in the correct retail layer order and early-returns on 0.
/// </para>
/// </summary>
public bool EnrichItem(uint objectId, uint iconId, string name, ItemType type)
public bool EnrichItem(uint objectId, uint iconId, string name, ItemType type,
uint iconOverlayId = 0, uint iconUnderlayId = 0)
{
if (!_items.TryGetValue(objectId, out var item)) return false;
if (iconId != 0) item.IconId = iconId;
if (!string.IsNullOrEmpty(name)) item.Name = name;
if (type != default) item.Type = type;
if (iconOverlayId != 0) item.IconOverlayId = iconOverlayId;
if (iconUnderlayId != 0) item.IconUnderlayId = iconUnderlayId;
ItemPropertiesUpdated?.Invoke(item);
return true;
}