fix(ui): match retail item appraisal semantics

Preserve PublicWeenieDesc hook identity from CreateObject through the item model so hook appraisals suppress sentinel capacities exactly. Use appraisal-only Value and Burden presence, retain AddItemInfo paragraph and authored font-color selection, and port retail lock, page, enchantment, and spell-block formatting.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-23 18:04:19 +02:00
parent bc47bc4917
commit d3c5e06fdd
21 changed files with 982 additions and 145 deletions

View file

@ -184,6 +184,11 @@ public static class CreateObject
int? Burden = null,
int? ItemsCapacity = null,
int? ContainersCapacity = null,
// PublicWeenieDesc hook identity. ACCWeenieObject::IsHook
// @ 0x0058C660 requires both values to be non-zero.
// The wire stores HookItemTypes as u32 and HookType as u16.
uint? HookItemTypes = null,
uint? HookType = null,
uint? ContainerId = null,
uint? WielderId = null,
uint? ValidLocations = null,
@ -778,6 +783,8 @@ public static class CreateObject
int? wBurden = null;
int? wItemsCapacity = null;
int? wContainersCapacity = null;
uint? wHookItemTypes = null;
uint? wHookType = null;
uint? wContainerId = null;
uint? wWielderId = null;
uint? wValidLocations = null;
@ -852,9 +859,9 @@ public static class CreateObject
// 0x00400000 Spell u16 (skip)
// 0x02000000 HouseOwner u32 (skip)
// 0x04000000 HouseRestrictions RestrictionDB (skip, variable-length)
// 0x20000000 HookItemTypes u32 (skip)
// 0x20000000 HookItemTypes u32 CAPTURE
// 0x00000040 Monarch u32 (skip)
// 0x10000000 HookType u16 (skip)
// 0x10000000 HookType u16 CAPTURE
// 0x40000000 IconOverlay PackedDwordKnownType(0x06000000) CAPTURE
// weenieFlags2 bit 0x01:
// IconUnderlay PackedDwordKnownType(0x06000000) CAPTURE
@ -1050,7 +1057,7 @@ public static class CreateObject
if ((weenieFlags & 0x20000000u) != 0) // HookItemTypes u32
{
if (body.Length - pos < 4) throw new FormatException("trunc HookItemTypes");
pos += 4;
wHookItemTypes = ReadU32(body, ref pos);
}
if ((weenieFlags & 0x00000040u) != 0) // Monarch u32
{
@ -1060,6 +1067,7 @@ public static class CreateObject
if ((weenieFlags & 0x10000000u) != 0) // HookType u16
{
if (body.Length - pos < 2) throw new FormatException("trunc HookType");
wHookType = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(pos));
pos += 2;
}
if ((weenieFlags & 0x40000000u) != 0) // IconOverlay PackedDwordOfKnownType(0x06000000) ← CAPTURE
@ -1117,6 +1125,7 @@ public static class CreateObject
WeenieClassId: weenieClassId,
Value: wValue, StackSize: wStackSize, StackSizeMax: wMaxStackSize,
Burden: wBurden, ItemsCapacity: wItemsCapacity, ContainersCapacity: wContainersCapacity,
HookItemTypes: wHookItemTypes, HookType: wHookType,
ContainerId: wContainerId, WielderId: wWielderId,
ValidLocations: wValidLocations, CurrentWieldedLocation: wCurrentWieldedLocation,
Priority: wPriority, Structure: wStructure, MaxStructure: wMaxStructure,

View file

@ -172,6 +172,8 @@ public static class ObjectTableWiring
Priority: s.Priority,
ItemsCapacity: s.ItemsCapacity,
ContainersCapacity: s.ContainersCapacity,
HookItemTypes: s.HookItemTypes,
HookType: s.HookType,
Structure: s.Structure,
MaxStructure: s.MaxStructure,
Workmanship: s.Workmanship,

View file

@ -156,7 +156,9 @@ public sealed class WorldSession : IDisposable
uint? SpellId = null,
uint? CooldownId = null,
double? CooldownDuration = null,
PhysicsSpawnData? Physics = null);
PhysicsSpawnData? Physics = null,
uint? HookItemTypes = null,
uint? HookType = null);
/// <summary>
/// Projects the wire-level CreateObject result into the stable session
@ -219,7 +221,9 @@ public sealed class WorldSession : IDisposable
SpellId: parsed.SpellId,
CooldownId: parsed.CooldownId,
CooldownDuration: parsed.CooldownDuration,
Physics: parsed.Physics);
Physics: parsed.Physics,
HookItemTypes: parsed.HookItemTypes,
HookType: parsed.HookType);
/// <summary>Fires when the session finishes parsing a CreateObject.</summary>
public event Action<EntitySpawn>? EntitySpawned;