125 lines
4.5 KiB
Markdown
125 lines
4.5 KiB
Markdown
# 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::UsingItem @ 0x00562F70`
|
|
- `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`.
|
|
|
|
For a direct-use item, `ItemHolder::UseObject` then sends
|
|
`CM_Inventory::Event_UseEvent`, increments the UI busy count, and calls
|
|
`CPlayerSystem::UsingItem`. `Event_UseEvent` itself is the network send
|
|
(`0xF7B1/0x36`); the latter function handles local pickup/wield/container
|
|
presentation. Therefore neither an owned item nor a world object may be held
|
|
behind a client-side movement completion. ACE owns the authoritative approach
|
|
chain.
|
|
|
|
```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 directly from its inventory identity; it is never sent
|
|
through the world-approach query. ACE remains authoritative for its spell
|
|
and consumption outcome.
|
|
- `SelectionInteractionController` sends accepted ordinary Use immediately for
|
|
both owned and spatial objects. It does not install speculative local
|
|
TurnToObject/MoveToObject movement; ACE's authoritative movement packets use
|
|
the shared retail `MoveToManager`.
|
|
- `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.
|