- /target and /assist find units by name like the original, and /follow walks you behind your target - right click opens clams and lockboxes, and the tooltip promises it - the freeze thaws clean, poses release instead of sticking, and you drop to neutral standing before it grips - unit frame art layers over the bars like the original Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
134 lines
6.0 KiB
Rust
134 lines
6.0 KiB
Rust
//! The bag/equipment family's `WorldWriter` sends — the item-template ask, use, auto-equip, the
|
|
//! ammo fork, the three move/swap/split shapes, and destroy. Bodies in
|
|
//! [`crate::messages::items`], whose scope this mirrors. Split out of `writer/mod.rs`
|
|
//! (decision 0636).
|
|
//!
|
|
//! Three distinct swap opcodes exist because the addressable space differs, not the intent:
|
|
//! `CMSG_SWAP_INV_ITEM` only ever names two slots in the player's own grid, while `CMSG_SWAP_ITEM`
|
|
//! and `CMSG_SPLIT_ITEM` take a `(bag, slot)` pair on each side so either endpoint may be an
|
|
//! equipped bag (decision 0216 §6). Every one of them refuses the same way —
|
|
//! `SMSG_INVENTORY_CHANGE_FAILURE` — and succeeds silently, as values deltas on both slots.
|
|
|
|
use anyhow::Result;
|
|
|
|
use crate::messages::{self, opcode};
|
|
|
|
use super::WorldWriter;
|
|
|
|
impl WorldWriter {
|
|
/// Ask an item template's display head (`CMSG_ITEM_QUERY_SINGLE`: entry + item guid, 0 for
|
|
/// template-only asks). Answered by `SMSG_ITEM_QUERY_SINGLE_RESPONSE` (an `ItemTemplate`
|
|
/// event) — the T2 container groundwork.
|
|
pub fn item_query(&mut self, entry: u32, guid: u64) -> Result<()> {
|
|
self.send(
|
|
opcode::CMSG_ITEM_QUERY_SINGLE,
|
|
&messages::item_query(entry, guid),
|
|
)
|
|
}
|
|
|
|
/// Use an item by bag position (`CMSG_USE_ITEM`, layout in [`messages::use_item`]) — eat the
|
|
/// food, drink the potion, hearthstone home. `go_target` aims the use at a GameObject, which is
|
|
/// how a KEY opens a locked door (decision 0769). The server answers with the effect (values
|
|
/// deltas, a stack decrement/destroy) or `SMSG_CAST_RESULT` on refusal.
|
|
pub fn use_item(
|
|
&mut self,
|
|
bag_index: u8,
|
|
slot: u8,
|
|
spell_slot: u8,
|
|
go_target: Option<u64>,
|
|
) -> Result<()> {
|
|
self.send(
|
|
opcode::CMSG_USE_ITEM,
|
|
&messages::use_item(bag_index, slot, spell_slot, go_target),
|
|
)
|
|
}
|
|
|
|
/// Open an item by bag position (`CMSG_OPEN_ITEM`, layout in [`messages::open_item`]) — crack
|
|
/// the clam, empty the picked lockbox, unwrap the gift. The right-click fork for an
|
|
/// [`crate::ItemInfo::openable`] item; the server answers with `SMSG_LOOT_RESPONSE` on the
|
|
/// item's **own** guid (so the loot window opens over a thing in the bag), or an equip error
|
|
/// on a refusal (still locked, dead, flying).
|
|
pub fn open_item(&mut self, bag_index: u8, slot: u8) -> Result<()> {
|
|
self.send(
|
|
opcode::CMSG_OPEN_ITEM,
|
|
&messages::open_item(bag_index, slot),
|
|
)
|
|
}
|
|
|
|
/// Equip a bag item (`CMSG_AUTOEQUIP_ITEM`, layout in [`messages::auto_equip_item`]) — the
|
|
/// server picks the destination slot. Success arrives as inventory-slot values deltas (and the
|
|
/// visible-item change everyone renders); refusal as `SMSG_INVENTORY_CHANGE_FAILURE`.
|
|
pub fn auto_equip_item(&mut self, bag_index: u8, slot: u8) -> Result<()> {
|
|
self.send(
|
|
opcode::CMSG_AUTOEQUIP_ITEM,
|
|
&messages::auto_equip_item(bag_index, slot),
|
|
)
|
|
}
|
|
|
|
/// Load ammo into the ammo slot (`CMSG_SET_AMMO`, layout in [`messages::set_ammo`]) — the
|
|
/// client's own auto-equip fork for ammo-class items (wow-re `cursor-dragdrop-slots.md`).
|
|
/// Addressed by item *entry*, not a bag slot; the stack stays in the bag and `PLAYER_AMMO_ID`
|
|
/// starts referencing it. A wrong/absent ranged weapon refuses via
|
|
/// `SMSG_INVENTORY_CHANGE_FAILURE`. Decision 0526.
|
|
pub fn set_ammo(&mut self, entry: u32) -> Result<()> {
|
|
self.send(opcode::CMSG_SET_AMMO, &messages::set_ammo(entry))
|
|
}
|
|
|
|
/// Swap two of the player's own inventory slots (`CMSG_SWAP_INV_ITEM`, layout in
|
|
/// [`messages::swap_inv_item`]) — the wire for a backpack-internal pick/place/swap (both slots
|
|
/// are `INVENTORY_SLOT_ITEM_START`+i). An empty destination is a move; the server settles both
|
|
/// slots with values deltas, or refuses via `SMSG_INVENTORY_CHANGE_FAILURE`.
|
|
pub fn swap_inv_item(&mut self, src_slot: u8, dst_slot: u8) -> Result<()> {
|
|
self.send(
|
|
opcode::CMSG_SWAP_INV_ITEM,
|
|
&messages::swap_inv_item(src_slot, dst_slot),
|
|
)
|
|
}
|
|
|
|
/// The general bag↔bag move (`CMSG_SWAP_ITEM`, layout in [`messages::swap_item`]): either
|
|
/// endpoint may be an equipped bag (unlike [`Self::swap_inv_item`], which only ever addresses
|
|
/// the player's own grid) — the wire for a whole-space bag-window pick/place/swap (decision
|
|
/// 0216 §6, slice 2). An empty destination is a move, same as `swap_inv_item`; refusal answers
|
|
/// `SMSG_INVENTORY_CHANGE_FAILURE`.
|
|
pub fn swap_item(
|
|
&mut self,
|
|
dst_bag: u8,
|
|
dst_slot: u8,
|
|
src_bag: u8,
|
|
src_slot: u8,
|
|
) -> Result<()> {
|
|
self.send(
|
|
opcode::CMSG_SWAP_ITEM,
|
|
&messages::swap_item(dst_bag, dst_slot, src_bag, src_slot),
|
|
)
|
|
}
|
|
|
|
/// Split a stack (`CMSG_SPLIT_ITEM`, layout in [`messages::split_item`]): carry `count` off
|
|
/// `(src_bag, src_slot)` onto `(dst_bag, dst_slot)` — either endpoint may be an equipped bag
|
|
/// (unlike [`Self::swap_inv_item`]). Success settles both slots via values deltas; refusal
|
|
/// answers `SMSG_INVENTORY_CHANGE_FAILURE`.
|
|
pub fn split_item(
|
|
&mut self,
|
|
src_bag: u8,
|
|
src_slot: u8,
|
|
dst_bag: u8,
|
|
dst_slot: u8,
|
|
count: u8,
|
|
) -> Result<()> {
|
|
self.send(
|
|
opcode::CMSG_SPLIT_ITEM,
|
|
&messages::split_item(src_bag, src_slot, dst_bag, dst_slot, count),
|
|
)
|
|
}
|
|
|
|
/// Destroy a bag item (`CMSG_DESTROYITEM`, layout in [`messages::destroy_item`]): `count` 0 =
|
|
/// the whole stack. The delete-confirm popup's `OnAccept` (decision 0216 §3) — no dedicated
|
|
/// answer packet; the item's disappearance is the ordinary field-update stream.
|
|
pub fn destroy_item(&mut self, bag: u8, slot: u8, count: u8) -> Result<()> {
|
|
self.send(
|
|
opcode::CMSG_DESTROYITEM,
|
|
&messages::destroy_item(bag, slot, count),
|
|
)
|
|
}
|
|
}
|