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>
105 lines
3.5 KiB
Markdown
105 lines
3.5 KiB
Markdown
# Retail selected-object health policy pseudocode — 2026-07-11
|
|
|
|
## Oracle
|
|
|
|
- `gmToolbarUI::HandleSelectionChanged @ 0x004BF380`, health branch
|
|
`0x004BF725..0x004BF76C`.
|
|
- `ClientCombatSystem::ObjectIsAttackable @ 0x0056A600`.
|
|
- `ACCWeenieObject::IsPlayer/IsPK/IsPKLite @ 0x0058C890/0x0058C8B0/0x0058C8A0`.
|
|
- `PublicWeenieDesc::UnPack @ 0x005AD470`, second-header PetOwner read
|
|
`0x005AD7EC..0x005AD7F8`.
|
|
- Struct/flag definitions: `acclient.h` `PublicWeenieDesc`,
|
|
`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
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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:
|
|
|
|
1. the local player/self (formerly rejected explicitly),
|
|
2. creatures with `PetOwner != 0`,
|
|
3. 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).
|