# Retail inventory weapon-switch pseudocode ## Scope Inventory double-click of a weapon while another primary weapon is equipped, 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)`. - `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) 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: pending = (requestedItem = item.id, blockingItem = blocker.id) 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) 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 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`. In combat mode, ACE selects the new weapon's stance and sends the authoritative motion stream plus PropertyInt 40. The inventory controller only 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. 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 the primary weapon-ready group plus retail's secondary incompatible-shield and mismatched-ammo blockers. `CreateObject` now retains the exact `AmmoType` needed for those decisions. Full `AutoWieldIsLegal`, Aetheria, and dual-wield/off-hand rules remain tracked by divergence row AP-108.