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:
Sam
2026-09-02 17:00:40 +07:00
co-authored by Claude Fable 5.1
parent d017ca1cde
commit ed524ab184
59 changed files with 2084 additions and 1231 deletions
@@ -53,11 +53,26 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
Ok(text.trim().parse::<f64>().unwrap_or(0.0))
})?,
)?;
// Insert(text) — **a nil is a no-op, not an error.** The reference's C `Insert` reads its
// argument through `lua_tostring`, which answers NULL for a nil and leaves the buffer alone;
// stock FrameXML relies on that. `LootFrame.lua:152` is the plain case:
//
// ChatFrameEditBox:Insert(GetLootSlotLink(this.slot));
//
// with no guard, on a coin row whose `GetLootSlotLink` is nil. Typed as `String`, this raised
// — which the loot window only survived while we owned the file and could add a guard the
// reference does not have. It bit the moment `LootFrame.xml` came off the player's chain
// (1751), and it would bite any addon writing the same unguarded line.
//
// A NUMBER still inserts its digits: `lua_tostring` converts one in place, and mlua's
// `Option<String>` coercion follows it.
m.set(
"Insert",
lua.create_function(|lua, (this, s): (Table, String)| {
lua.create_function(|lua, (this, s): (Table, Option<String>)| {
let h = frame_handle_of(lua, &this)?;
insert(lua, h, &s, true);
if let Some(s) = s {
insert(lua, h, &s, true);
}
Ok(())
})?,
)?;