the bank is the reference's own window, and far sight freezes your body

- the bank window is the reference's own `BankFrame.xml` run off the player's chain — our 890-line copy is deleted whole; putting bags into the bank's bag slots works (the two verbs its buttons are wired to now exist), the purse is a real `SmallMoneyFrameTemplate` splitting 987654321 copper into three coin buttons instead of one string, `BANKFRAME_OPENED` loses the argument the real client never fires, and the bank stops auto-opening your bags while the vendor and mailbox keep doing it; the empty-slot click still plays the bag sound, because the reference's own line plays it on every click the cursor did not consume — named as faithful rather than quietly fixed
- underneath it, the bank's inventory band is a VIEW over the container feed, not a second copy — inventory ids 40..69 had answered nil because the doll array stops at 24, so the stock file swapped in would have drawn an empty bank; the six bank bag slots become real inventory slots (64..=69) fed off the player descriptor, the three bag verbs are carved from the binary (`PutItemInBackpack` IS `PutItemInBag(0xFF)` through a ten-byte thunk, the occupied-by-an-ordinary-item leg auto-stores with no destination slot at all, and the return is the Lua number 1 or nil — the nil being the whole reason a plain left-click opens a bag), and the swap caught `BankButtonIDToInvSlotID`'s bag arm six slots wrong: it took a bag number 1..6 where the reference passes the button's own id, which is the container id
- far sight freezes your own body while the view is out — the fifth conjunct of the movement predicates' shared precondition, joining the health term so the walk, the turn and the follow teardown go down together; both halves are load-bearing: the `IsActivePlayer` half is what keeps a Mind Control victim walking (possession rides the same PLAYER_FARSIGHT field), and the latch half is the RESOLVED engage rather than the raw field — and the camera is untouched, you still look around freely, which is the whole point of the spell
- the reset offer no longer outlives the character that earned it — log out after walking out of the Deadmines and your next character was offered a reset the first one had earned — and the one lockout leg no probe can ever reach (vmangos writes a last-instance map only from raid binds, which the client's handler discards as not-a-party-dungeon) is now covered end to end through the real system, its module doc saying plainly: tested and never observed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-31 20:18:51 +07:00
co-authored by Claude Fable 5
parent 25c865c00a
commit 14bd5ea1a4
35 changed files with 1804 additions and 1347 deletions
+62
View File
@@ -124,6 +124,68 @@ pub(super) fn drain_container_autoequips(
}
}
/// Drain the auto-stores `PutItemInBag`/`PutItemInBackpack` queued (`benilla_ui`'s
/// `cursor::bag_verbs`) and send them on the wire.
///
/// **The destination is a BAG, not a slot** — that is the finding this drain exists to carry
/// (wow-re `bag-verbs-law.md`): `CMSG_AUTOSTORE_BAG_ITEM` names `(srcbag, srcslot, dstbag)` and
/// the server picks where inside it the item lands, which is why an ordinary item dropped on a
/// bag BUTTON goes in the bag rather than swapping with it. The destination bag byte is
/// [`wire_pos`]'s own answer for that container's first slot (255 for the backpack, the bag's
/// player-array slot 19..22 / 63..68 for an equipped or bank bag), so one map serves both ends.
///
/// A **split carry** takes `CMSG_SPLIT_ITEM` instead — the reference's own fork on `[0xb4b40c]`
/// — with the literal `0xFF` where a destination slot would go, because that wire has a slot
/// field and this one does not.
///
/// No pending-lock recording, matching [`drain_container_autoequips`]'s own precedent for the same
/// class of send: the destination is not a slot, so there is no second end to lock.
pub(super) fn drain_bag_autostores(
script: Option<NonSendMut<UiScript>>,
commands: Res<NetCommands>,
) {
let Some(mut script) = script else {
return;
};
for a in script.take_bag_autostores() {
let (Some((src_bag, src_slot)), Some((dst_bag, _))) =
(wire_pos(a.src_bag, a.src_slot), wire_pos(a.dst_bag, 1))
else {
debug!("ui_items: bag autostore {a:?} out of range — ignored");
continue;
};
match a.count {
None => {
debug!(
"ui_items: autostore lua {}/{} → bag {} (wire {src_bag}/{src_slot} → {dst_bag})",
a.src_bag, a.src_slot, a.dst_bag
);
let _ = commands.0.send(ClientCommand::AutoStoreBagItem {
src_bag,
src_slot,
dst_bag,
});
}
Some(n) => {
let count = n.min(u32::from(u8::MAX)) as u8;
debug!(
"ui_items: autostore split lua {}/{} → bag {} × {count} (wire {src_bag}/{src_slot} → {dst_bag})",
a.src_bag, a.src_slot, a.dst_bag
);
let _ = commands.0.send(ClientCommand::SplitItem {
src_bag,
src_slot,
dst_bag,
// `0xFF` — the reference's own literal for "no destination slot"; the split
// wire has the field, the verb has no value for it.
dst_slot: 0xFF,
count,
});
}
}
}
}
/// Drain the inventory-slot ids `UseInventoryItem` queued (decision 0208 phase 1b: the doll
/// slot's right-click) and route the equipped position (bag 255 plus the 0-based wire slot —
/// `HandleUseItemOpcode` takes equipped positions the same as bag ones, vmangos `ItemHandler.cpp`)
+3 -2
View File
@@ -53,8 +53,8 @@ pub(crate) mod feed;
pub(crate) use drain::send_auto_equip;
use drain::{
drain_container_autoequips, drain_container_destroys, drain_container_moves,
drain_container_uses, drain_inventory_uses,
drain_bag_autostores, drain_container_autoequips, drain_container_destroys,
drain_container_moves, drain_container_uses, drain_inventory_uses,
};
use feed::{
feed_containers, feed_item_sets, feed_item_stats, feed_player_req, feed_random_properties,
@@ -1112,6 +1112,7 @@ impl Plugin for UiItemsPlugin {
drain_container_destroys.after(UiInput),
// AutoEquipCursorItem's queue (decision 0208 phase 1b) → CMSG_AUTOEQUIP_ITEM.
drain_container_autoequips.after(UiInput),
drain_bag_autostores.after(UiInput),
// UseInventoryItem's queue (decision 0208 phase 1b) → CMSG_USE_ITEM against
// the equipped position.
drain_inventory_uses.after(UiInput),