Replace the toolbar's PWD-bit approximation with retail ObjectIsAttackable composed with the exact player and pet short-circuits. Carry second-header PetOwner through CreateObject, session, and ClientObject state so self, pet, and Free-PK cases match retail while friendly NPCs and attackable non-creatures remain name-only. Co-Authored-By: Codex <codex@openai.com>
3.5 KiB
Retail selected-object health policy pseudocode — 2026-07-11
Oracle
gmToolbarUI::HandleSelectionChanged @ 0x004BF380, health branch0x004BF725..0x004BF76C.ClientCombatSystem::ObjectIsAttackable @ 0x0056A600.ACCWeenieObject::IsPlayer/IsPK/IsPKLite @ 0x0058C890/0x0058C8B0/0x0058C8A0.PublicWeenieDesc::UnPack @ 0x005AD470, second-header PetOwner read0x005AD7EC..0x005AD7F8.- Struct/flag definitions:
acclient.hPublicWeenieDesc,PublicWeenieDescPackHeader2::PWD2_Packed_PetOwner = 0x8.
ACE cross-check: WorldObject_Networking.SerializeCreateObject writes the second
header after the ordinary optional tail in this order: IconUnderlay, MaterialType,
CooldownId, CooldownDuration (double), PetOwner (u32), then alignment. Its
UpdateObjectDescriptionFlags maps the same PWD player/attackable/PK/Free-PK/PKLite
bits read by retail.
Chorizite/holtburger cross-check: the independently generated
Chorizite.ACProtocol/Types/PublicWeenieDesc schema and holtburger inventory client
inventory are catalogued in deepdives/r06-items-inventory.md; that schema pins the
same Header2 0x01/0x02/0x04/0x08 order and field widths. Neither interpretation
aid changes the client behavior above; the named retail branch order remains ground truth.
Constants
ITEM_TYPE_CREATURE = 0x10
BF_PLAYER = 0x00000008
BF_ATTACKABLE = 0x00000010
BF_PLAYER_KILLER = 0x00000020
BF_FREE_PKSTATUS = 0x00200000
BF_PKLITE_PKSTATUS = 0x02000000
PWD2_PET_OWNER = 0x00000008
Exact pseudocode
ToolbarShouldQueryHealth(selectedId):
selected = lookup selectedId
if selected missing:
return false
if selected.stackSize > 1:
return false // stack UI is mutually exclusive
isPlayer = (selected.pwd.bitfield & BF_PLAYER) != 0
isPet = selected.pwd.petOwner != 0
if isPlayer OR isPet OR ObjectIsAttackable(selectedId):
QueryHealth(selectedId)
return true
return false
ObjectIsAttackable(targetId):
if targetId == 0 OR targetId == localPlayerId:
return true
target = lookup targetId
if target missing OR (target.type & ITEM_TYPE_CREATURE) == 0:
return false
if (target.pwd.bitfield & BF_FREE_PKSTATUS) != 0:
return true
player = lookup localPlayerId
if player missing:
return false
if (player.pwd.bitfield & BF_FREE_PKSTATUS) != 0:
return true
if target.IsPlayer():
if target.IsPK() AND player.IsPK():
return true
if target.IsPKLite() AND player.IsPKLite():
return true
return false
if target.pwd.petOwner != 0:
return false
return (target.pwd.bitfield & BF_ATTACKABLE) != 0
Toolbar consequence
The toolbar's outer IsPlayer || petOwner test runs before
ObjectIsAttackable. Therefore every selected player and every selected pet gets
a health query regardless of PK pairing. The full PK/PKLite branches remain part
of the faithful reusable ObjectIsAttackable port, but they do not decide toolbar
player visibility. Relative to acdream's former PWD-only approximation, the
observable missing toolbar cases are:
- the local player/self (formerly rejected explicitly),
- creatures with
PetOwner != 0, - Free-PK creatures, or ordinary creatures while the local player is Free-PK.
Friendly non-pet NPCs remain name-only. Non-creature objects remain name-only even
when they carry BF_ATTACKABLE (the existing attackable-door regression guard).