fix(ui): port exact selected health policy
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>
This commit is contained in:
parent
52a80dc531
commit
2644d1d527
18 changed files with 416 additions and 50 deletions
|
|
@ -0,0 +1,105 @@
|
|||
# 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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue