# Retail inventory weapon-switch pseudocode ## Scope Inventory double-click of a weapon while another primary weapon is equipped, or dragging an item onto a specific paperdoll slot, in both peace and combat modes. The UI request sequence is shared; combat-mode animation remains server-authoritative. ## Retail oracle - `ItemHolder::DetermineUseResult @ 0x00588460` classifies an owned combat-use item as `WieldRight`. - `CPlayerSystem::UsingItem @ 0x00562F70` dispatches that result to `AutoWield(item, SLOT_SIDE_RIGHT, 0, 1, 0, 1)`. - `CPlayerSystem::AutoWield @ 0x00560A60` treats `0x03500000` (`MeleeWeapon | MissileWeapon | Held | TwoHanded`) as one weapon-ready group. If occupied, it records the requested item, sends the blocking weapon to the player container, and does not wield the requested item yet. - `CPlayerSystem::RecvNotice_ServerSaysMoveItem @ 0x00563260` retries `AutoWield` only after the server confirms the blocking item moved. - `ACCWeenieObject::UIAttemptWield @ 0x0058D590` sends `GetAndWieldItem(item, equipMask)`. - `gmPaperDollUI::HandleDropRelease @ 0x004A4D80` routes an accepted physical item through `AcceptDragObject @ 0x004A3B10`, which calls `AutoWield` (or `AutoWear`) with the paperdoll's resolved target side. It does not send a direct `GetAndWieldItem` while a blocker remains equipped. - `ClientCombatSystem::OnQualityChanged @ 0x0056C1B0` reads player PropertyInt 40 (`CombatMode`) and applies it through `SetCombatMode`. Addresses are from `docs/research/named-retail/symbols.json`; pseudo-C is in `docs/research/named-retail/acclient_2013_pseudo_c.txt`. ## Cross-reference - ACE `Player_Inventory.cs` says the client normally sends the conflicting item's dequip before `GetAndWieldItem`. `TryShuffleStance` changes a player who is already in combat to Missile, Magic, or Melee after the new item is equipped; it leaves a player in peace mode in peace. - ACE `Creature_Combat.cs::SwitchCombatStyles` performs the visible old-stance -> peace -> new-stance motion chain and sends `PrivateUpdatePropertyInt(CombatMode, ...)`. - holtburger independently confirms the action/event shapes: `GetAndWieldItem { item_guid, equip_mask }`, `WieldObject`, and PropertyInt 40 as `CombatMode`. ## Faithful pseudocode ```text on inventory item double-click(item): decision = DetermineUseResult(item) if decision is WieldRight or WieldLeft or AutoSort: auto_wield(item) on paperdoll drop(item, target slot): resolvedMask = resolve item.ValidLocations against the authored slot side auto_wield(item, resolvedMask) auto_wield(item): if another auto-wield transaction is waiting for the server: consume the request without sending another inventory action if item has no valid equip location or is already equipped: return false if item.validLocations intersects WEAPON_READY_MASK: desiredMask = item.validLocations & WEAPON_READY_MASK blocker = player's equipped item intersecting WEAPON_READY_MASK if blocker exists: print system text "Moving to your backpack" pending = (requestedItem = item.id, blockingItem = blocker.id, resolvedMask = desiredMask) send PutItemInContainer(blocker.id, player.id, placement = 0) return true if BlocksUseOfShield(item) and a shield is equipped: begin the same confirmed blocker transaction for the shield if item.ammoType is not AMMO_NONE and equipped ammo has a different ammoType: begin the same confirmed blocker transaction for the ammo send_wield(item, desiredMask) return true use the existing retail slot/auto-wear selection rules send_wield(item, selectedMask) on server-confirmed item move(blocker): if blocker matches pending.blockingItem and blocker is now loose in the player's main pack: requested = pending.requestedItem clear pending before re-entry auto_wield(requested, pending.resolvedMask) on server inventory-request failure(item, error): if item matches pending.blockingItem or pending.requestedItem: clear pending send_wield(item, mask): pending = (requestedItem = item.id, blockingItem = none) update the retained inventory/paperdoll projection optimistically send GetAndWieldItem(item.id, mask) on server-confirmed WieldObject(item): reconcile the item's optimistic rollback snapshot separately if item matches pending.requestedItem and pending.blockingItem is none: clear pending if the transaction began in an active combat mode: // ACE compatibility boundary; see below. retain the ready mode implied by the confirmed equip location on combat-mode notices after confirmed WieldObject: if the first notice is the retained ready mode: clear the retained mode; the server already settled correctly else if notices form NonCombat -> retained ready mode -> NonCombat: clear the retained mode request that ready mode from ACE's trailing NonCombat notice on PrivateUpdatePropertyInt(property = CombatMode, value): if value is exactly NonCombat, Melee, Missile, or Magic: CombatState.SetCombatMode(value) ``` ## Animation ownership The client must not synthesize a local bow-to-sword animation. In peace mode, ACE leaves `CombatMode` as `NonCombat`. The inventory controller sequences the two wire requests and waits for the old weapon's confirmed move; the existing motion and equipped-child render paths consume the server events. ### ACE active-combat compatibility boundary A connected 2026-07-17 trace of Magic wand -> missile weapon showed ACE emit `Magic -> Melee -> NonCombat -> Missile -> NonCombat`. This is a consequence of ACE `Player_Inventory.TryShuffleStance` plus its queued dequip callbacks: the server does select Missile for the new weapon, but a later queued callback publishes NonCombat after the authoritative `WieldObject`. Retail's client does not need a second request against the retail server. acdream therefore preserves the user's desired ready mode only when AutoWield begins in active combat. Authoritative `WieldObject` arms settlement. A server whose first subsequent notice is already the desired mode needs no request. Local ACE instead emits `NonCombat -> desired mode -> NonCombat`; the client sends one normal `ChangeCombatMode` request from that trailing notice, necessarily after the queued callback that caused it. All resulting state and animation remain server- authoritative. No update is ignored, delayed, or locally fabricated, and a transaction begun in peace sends no mode request. This narrow server- compatibility adaptation is recorded as AP-118. The request is not complete when `GetAndWieldItem` is merely sent. Retail `UIAttemptWield` records `prevRequest = IR_WIELD` and leaves the item waiting until `ServerSaysMoveItem` reconciles it. acdream therefore keeps AutoWield busy through the final authoritative `WieldObject`; allowing another switch during the optimistic interval makes ACE reject otherwise valid items. Retail `ACCWeenieObject::ServerSaysMoveItem @ 0x0058DBB0` clears the matching request directly; that UI completion is intentionally independent of the object's separate optimistic rollback counter. ## Known remaining retail surface This port covers inventory activation and paperdoll drops through the same primary weapon-ready transaction, plus retail's secondary incompatible-shield and mismatched-ammo blockers. `CreateObject` retains the exact `AmmoType` needed for those decisions, and paperdoll target-side intent survives every authoritative blocker retry. Full `AutoWieldIsLegal` and dual-wield/off-hand rules remain tracked by divergence row AP-108. Retail also appends `" - cannot unwield the %s"` when `AttemptToPlaceInContainer` rejects synchronously. ACDream's current send seam is void and ACE reports that rejection asynchronously, so the successful status line is exact while that failure suffix remains tracked instead of being fabricated at the wrong transaction boundary.