druids get their relic slot, and the loot window is the reference's own
- `UnitHasRelicSlot` is real in 1.12 and benilla had no binding for it: `0x519e50` reads `ChrClasses.dbc` field 16 — set for Paladin, Shaman and Druid in `patch.MPQ`'s 17-column copy (the base `dbc.MPQ` copy has 16 columns and no such field, which is how the "always false in vanilla" belief survived twice in decisions and seven times in code) — and stock `PaperDollFrame.lua` calls it unconditionally, so the character sheet would have raised for EVERY class; a druid, paladin or shaman's sheet now hides the ammo slot, shows the relic plate where the bow was, tooltips "Relic", and reports ranged Attack/Damage/Power as N/A, all four branches transcribed from the reference - the loot window is the reference's own `LootFrame.xml`: `LootButton` is built as a real widget type rather than aliased (a Button plus one slot dword and one overridden OnClick — right-click loots exactly like left, no shift/ctrl/alt takes, a row whose Lua handler errored still loots, a fast double-click takes two slots), `LOOT_SLOT_CLEARED` fires per row again because the stock file hangs hide-in-place AND the emptied-page auto-advance off it, `EditBox:Insert(nil)` is a no-op as `lua_tostring` makes it, and an alias `GameTooltip.xml` had quietly become a reader of would have turned every item tooltip white, silently, had it just gone nil - the vendor cursor: `PickupMerchantItem` is two verbs behind one name, decided by what the cursor holds and never by the caller — a held bag item SELLS (index ignored, no merchant-open gate), otherwise the vendor row is grabbed as cursor mode 5, which is invisible to `CursorHasItem` (modes 1 and 9 only) so all thirteen stock gates stay closed while it is held; the drop is the buy, `CMSG_BUY_ITEM_IN_SLOT`, count 1, into a bag or an equipment slot, and every refusal is silent and clears the cursor; the four `Model_*` rotation handlers land in UIParent where the reference keeps them - the gap report reads `:Method(` sites now — it found `SetMerchantCompareItem`, which would have raised on the first vendor-row hover with every check green, and `LootButton:SetSlot`, and 28 "free" windows became 25; the shared coin kit moved out of the merchant window into `MoneyFrame.xml` so the eight windows that draw coins through it can reach it without loading a vendor Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -246,8 +246,8 @@ pub use update_object::{
|
||||
PLAYER_QUEST_LOG_SLOTS, PLAYER_SKILL_SLOTS, UNIT_AURA_POSITIVE_SLOTS, UNIT_AURA_SLOTS,
|
||||
};
|
||||
pub use vendor::{
|
||||
buy_item, buy_result, buyback_item, list_inventory, repair_item, sell_item, sell_result,
|
||||
VendorItem,
|
||||
buy_item, buy_item_in_slot, buy_result, buyback_item, list_inventory, repair_item, sell_item,
|
||||
sell_result, VendorItem,
|
||||
};
|
||||
pub use world_state::InitWorldStates;
|
||||
|
||||
|
||||
@@ -780,8 +780,9 @@ pub const SMSG_LIST_INVENTORY: u16 = 0x019F; // 415
|
||||
pub const CMSG_SELL_ITEM: u16 = 0x01A0; // 416
|
||||
pub const SMSG_SELL_ITEM: u16 = 0x01A1; // 417
|
||||
pub const CMSG_BUY_ITEM: u16 = 0x01A2; // 418
|
||||
/// Buy into a specific bag slot — value pinned for completeness; unused (out of scope, plain
|
||||
/// `CMSG_BUY_ITEM` auto-place is sufficient for v1, decision 0081).
|
||||
/// Buy into a **specific** bag slot. This is what a vendor row dropped out of the merchant
|
||||
/// cursor sends (`PickupMerchantItem` mode 5 → the container/doll drop), as against
|
||||
/// `CMSG_BUY_ITEM`'s auto-place, which is what clicking the row sends. Both ship (decision 1786).
|
||||
pub const CMSG_BUY_ITEM_IN_SLOT: u16 = 0x01A3; // 419
|
||||
pub const SMSG_BUY_ITEM: u16 = 0x01A4; // 420
|
||||
pub const SMSG_BUY_FAILED: u16 = 0x01A5; // 421
|
||||
|
||||
@@ -59,9 +59,8 @@ pub fn list_inventory(vendor_guid: u64) -> Vec<u8> {
|
||||
|
||||
/// Body of `CMSG_BUY_ITEM` (vmangos `Item.cpp:104-110`): `u64 vendorGuid, u32 item` (the template
|
||||
/// **entry**, not the vendor row's `muid`), `u8 count` (stacks to buy), `u8 unk1` (always 0).
|
||||
/// Auto-places into the first free bag slot — the specific-bag-slot variant
|
||||
/// (`CMSG_BUY_ITEM_IN_SLOT`, [`super::opcode::CMSG_BUY_ITEM_IN_SLOT`]) is out of scope for v1
|
||||
/// (decision 0081). Handler → `BuyItemFromVendor` (`ItemHandler.cpp:688`).
|
||||
/// Auto-places into the first free bag slot — the specific-bag-slot variant is
|
||||
/// [`buy_item_in_slot`]. Handler → `BuyItemFromVendor` (`ItemHandler.cpp:688`).
|
||||
pub fn buy_item(vendor_guid: u64, entry: u32, count: u8) -> Vec<u8> {
|
||||
let mut body = Vec::with_capacity(14);
|
||||
body.extend_from_slice(&vendor_guid.to_le_bytes());
|
||||
@@ -71,6 +70,34 @@ pub fn buy_item(vendor_guid: u64, entry: u32, count: u8) -> Vec<u8> {
|
||||
body
|
||||
}
|
||||
|
||||
/// Body of `CMSG_BUY_ITEM_IN_SLOT` (vmangos `Item.cpp:113-120`): `u64 vendorGuid, u32 item`
|
||||
/// (the template **entry**, like [`buy_item`]), `u64 bagGuid`, `u8 bagSlot`, `u8 count`.
|
||||
///
|
||||
/// The buy-into-a-named-slot variant, and the packet the **merchant cursor** sends: the reference
|
||||
/// puts a vendor row on the cursor as payload mode 5 (`PickupMerchantItem 0x4fb760`) and the
|
||||
/// three container drop handlers each call the sender `0x5e1f30` with `count = 1`
|
||||
/// (wow-re `system/ui/scratch/merchant-cursor-law.md` §5). Clicking a row is the auto-place
|
||||
/// [`buy_item`]; dragging it into a slot is this.
|
||||
///
|
||||
/// `bag_guid` is the **container object's** guid, or the player's own for the backpack and the
|
||||
/// equipment slots; `bag_slot` is the destination within it. Handler → `BuyItemFromVendor`, the
|
||||
/// same one `CMSG_BUY_ITEM` reaches, with the slot pair filled instead of `NULL_BAG`/`NULL_SLOT`.
|
||||
pub fn buy_item_in_slot(
|
||||
vendor_guid: u64,
|
||||
entry: u32,
|
||||
bag_guid: u64,
|
||||
bag_slot: u8,
|
||||
count: u8,
|
||||
) -> Vec<u8> {
|
||||
let mut body = Vec::with_capacity(22);
|
||||
body.extend_from_slice(&vendor_guid.to_le_bytes());
|
||||
body.extend_from_slice(&entry.to_le_bytes());
|
||||
body.extend_from_slice(&bag_guid.to_le_bytes());
|
||||
body.push(bag_slot);
|
||||
body.push(count);
|
||||
body
|
||||
}
|
||||
|
||||
/// Body of `CMSG_SELL_ITEM` (vmangos `Item.cpp:87-92`): `u64 vendorGuid, u64 itemGuid, u8 count`
|
||||
/// (0 = sell the whole stack). Handler `ItemHandler.cpp:442`.
|
||||
pub fn sell_item(vendor_guid: u64, item_guid: u64, count: u8) -> Vec<u8> {
|
||||
|
||||
@@ -502,6 +502,21 @@ impl WorldSession {
|
||||
)
|
||||
}
|
||||
|
||||
/// `CMSG_BUY_ITEM_IN_SLOT` — buy into a named container slot, the merchant cursor's drop.
|
||||
pub fn buy_item_in_slot(
|
||||
&mut self,
|
||||
vendor_guid: u64,
|
||||
entry: u32,
|
||||
bag_guid: u64,
|
||||
bag_slot: u8,
|
||||
count: u8,
|
||||
) -> Result<()> {
|
||||
self.send(
|
||||
opcode::CMSG_BUY_ITEM_IN_SLOT,
|
||||
&messages::buy_item_in_slot(vendor_guid, entry, bag_guid, bag_slot, count),
|
||||
)
|
||||
}
|
||||
|
||||
/// Sell an item to a vendor (`CMSG_SELL_ITEM`) — the unsplit twin of [`WorldWriter::sell_item`];
|
||||
/// `count` 0 = the whole stack.
|
||||
pub fn sell_item(&mut self, vendor_guid: u64, item_guid: u64, count: u8) -> Result<()> {
|
||||
|
||||
@@ -36,6 +36,24 @@ impl WorldWriter {
|
||||
)
|
||||
}
|
||||
|
||||
/// Buy into a **named** container slot (`CMSG_BUY_ITEM_IN_SLOT`, layout in
|
||||
/// [`messages::buy_item_in_slot`]) — the merchant cursor's drop, as against [`Self::buy_item`]'s
|
||||
/// auto-place from a row click. `bag_guid` is the container object's guid, or the player's own
|
||||
/// for the backpack and the equipment slots.
|
||||
pub fn buy_item_in_slot(
|
||||
&mut self,
|
||||
vendor_guid: u64,
|
||||
entry: u32,
|
||||
bag_guid: u64,
|
||||
bag_slot: u8,
|
||||
count: u8,
|
||||
) -> Result<()> {
|
||||
self.send(
|
||||
opcode::CMSG_BUY_ITEM_IN_SLOT,
|
||||
&messages::buy_item_in_slot(vendor_guid, entry, bag_guid, bag_slot, count),
|
||||
)
|
||||
}
|
||||
|
||||
/// Sell an item to a vendor (`CMSG_SELL_ITEM`, layout in [`messages::sell_item`]): `count` 0 =
|
||||
/// sell the whole stack. Success is silent (the item vanishes + coinage rises via
|
||||
/// `UPDATE_OBJECT`); refusal answers `SMSG_SELL_ITEM`'s error shape (a `VendorSellFailed`
|
||||
|
||||
Reference in New Issue
Block a user