acdream/docs/research/2026-07-23-retail-item-use-and-autowear-pseudocode.md

4.5 KiB

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:

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:

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

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. That latter presentation function handles local pickup/wield/container state; it does not require a carried source to have a world position. Therefore an owned inventory object must cross the wire directly. Only world objects participate in acdream's registered AD-27 local approach compatibility seam.

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
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 keeps the AD-27 turn/approach seam only for non-owned spatial objects. Owned self, equipped, and nested-container sources have no live world pose and send immediately.
  • 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.