fix(items): port retail useability and autowear rejection
Use the exact low USEABLE_NO-bit predicate so reset/zero-valued direct-use items such as Blackmoor's Favor reach the ordinary Use request. Port AutoWear's clothing-priority blocker lookup and exact named system notice through the shared activation owner. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
60387668d0
commit
f71f947475
13 changed files with 289 additions and 57 deletions
|
|
@ -0,0 +1,111 @@
|
|||
# Retail item useability and AutoWear rejection
|
||||
|
||||
Date: 2026-07-23
|
||||
|
||||
## Retail anchors
|
||||
|
||||
- `ItemUses::ItemUses @ 0x004FCCB0`
|
||||
- `ItemUses::IsUseable @ 0x004FCCC0`
|
||||
- `PublicWeenieDesc::Reset @ 0x005ACC70`
|
||||
- `PublicWeenieDesc::UnPack @ 0x005AD470`
|
||||
- `ItemHolder::DetermineUseResult @ 0x00588460`
|
||||
- `ItemHolder::UseObject @ 0x00588A80`
|
||||
- `CPlayerSystem::AutoWearIsLegal @ 0x0055EF40`
|
||||
- `CPlayerSystem::AutoWear @ 0x005601C0`
|
||||
- `ACCWeenieObject::GetObjectAtLocation @ 0x0058CE00`
|
||||
|
||||
The named pseudo-C is the behavioral oracle. The matching v11.4186 binary was
|
||||
also inspected because an older project note interpreted
|
||||
`ItemUses::IsUseable` as a nonzero test. The actual instructions are:
|
||||
|
||||
```text
|
||||
004fccc0 mov eax,[ecx]
|
||||
004fccc2 not eax
|
||||
004fccc4 and eax,1
|
||||
004fccc7 ret
|
||||
```
|
||||
|
||||
Therefore the method tests only `USEABLE_NO` (bit zero). A zero-valued
|
||||
`ITEM_USEABLE` is usable; `USEABLE_NEVER_WALK` is a movement modifier and does
|
||||
not make the object unusable. The literal AutoWear rejection at `0x007CC868`
|
||||
is:
|
||||
|
||||
```text
|
||||
You must remove your %s to wear that
|
||||
```
|
||||
|
||||
The staged ACE, holtburger, and ACViewer reference directories are currently
|
||||
empty in this checkout. Their established roles were still checked: ACE owns
|
||||
the authoritative use/wield outcome, while the two client references do not
|
||||
replace the retail UI-side legality and notice oracle. No server behavior is
|
||||
being inferred or moved client-side here.
|
||||
|
||||
## Pseudocode
|
||||
|
||||
```text
|
||||
ItemUses.IsUseable():
|
||||
return (useableBitfield & USEABLE_NO) == 0
|
||||
```
|
||||
|
||||
`PublicWeenieDesc::Reset` zeros the useability field. `UnPack` replaces it only
|
||||
when the optional useability word is present. Consequently an absent wire word
|
||||
and an explicitly packed zero both have retail `USEABLE_UNDEF` value zero and
|
||||
both pass `IsUseable`.
|
||||
|
||||
```text
|
||||
AutoWearIsLegal(itemId, out alreadyWorn, silent):
|
||||
alreadyWorn = false
|
||||
|
||||
if player is not ready for an inventory request:
|
||||
return false
|
||||
|
||||
item = GetWeenieObject(itemId)
|
||||
if item is null:
|
||||
return false
|
||||
if (item.validLocations & 0x08007FFF) == 0:
|
||||
return false
|
||||
|
||||
if (item.priority & playerSystem.clothingPriorityMask) == 0:
|
||||
return true
|
||||
|
||||
player = GetWeenieObject(playerId)
|
||||
if player contains item at a worn location:
|
||||
alreadyWorn = true
|
||||
|
||||
if not silent:
|
||||
if alreadyWorn:
|
||||
display "The <item appropriate name> is already being worn"
|
||||
else:
|
||||
occupied = playerSystem.inventoryMask & item.validLocations
|
||||
blocker = player.GetObjectAtLocation(occupied, item.priority)
|
||||
if blocker exists:
|
||||
display "You must remove your <blocker appropriate name> to wear that"
|
||||
|
||||
return false
|
||||
```
|
||||
|
||||
```text
|
||||
AutoWear(itemId, out alreadyWorn, silent):
|
||||
if not AutoWearIsLegal(itemId, alreadyWorn, silent):
|
||||
return false
|
||||
|
||||
item = GetWeenieObject(itemId)
|
||||
if item exists:
|
||||
item.UIAttemptWield(item.validLocations)
|
||||
return true
|
||||
```
|
||||
|
||||
## Port boundary
|
||||
|
||||
- `ItemUseability.IsUseable` is the one Core implementation of the low-bit
|
||||
rule. Toolbar enablement, item activation, and world interaction consume it.
|
||||
- `ItemInteractionController` remains the shared double-click and toolbar-hand
|
||||
path. A zero-valued carried gem such as Blackmoor's Favor therefore emits the
|
||||
ordinary Use request; ACE remains authoritative for its spell and
|
||||
consumption outcome.
|
||||
- `AutoWieldController` remains the shared inventory/paperdoll equip owner. It
|
||||
resolves AutoWear blockers from `ClientObjectTable`'s retail-ordered
|
||||
equipment projection and emits the retail literal through system chat.
|
||||
- Weapon replacement is unchanged. Retail automatically moves primary weapon,
|
||||
incompatible shield, and incompatible ammunition blockers, while AutoWear
|
||||
rejects overlapping clothing/armor and tells the player what to remove.
|
||||
Loading…
Add table
Add a link
Reference in a new issue