- the stable master: `MSG_LIST_STABLED_PETS` both ways, the four mutations answered by one result byte, two slots priced from `StableSlotPrices.dbc` (500 and 50000 copper — the cap falls out of the data, corroborated by vmangos's `MAX_PET_STABLES` and the reference's `NUM_PET_STABLE_SLOTS`), rows seated by slot never by position (a petless hunter's list starts at a stabled row), slot 0 read from the wire because a dismissed pet has a row and no live unit, a re-list after every success because no result carries one, and `GetPetIcon` reading `CreatureFamily.dbc`'s icon column — which ships `Ability_Druid_CatForm` for both warlock families, asserted as shipped - master loot: `CMSG_LOOT_MASTER_GIVE` (the slot is a byte here where the roll family's is a u32) and `SMSG_LOOT_MASTER_LIST`; the click gate is the wire's slot-type byte and nothing else — the reference never asks who the master looter is — so a MASTER row fires `OPEN_MASTER_LOOT_LIST` instead of a take and auto-loot skips every non-ALLOW row; the candidate array is bucketed by raid subgroup into blocks of five (a dense list would have filed every candidate under the wrong "Group N"), an unresolved name is nil so `UPDATE_MASTER_LOOT_LIST` has something to repaint, and a give re-checks the row is still MASTER before sending; found on the way: a master looter in another raid subgroup lit the crown on your own portrait - `SetLootMethod` takes its third (threshold) argument; the reference's silent reset to Uncommon when it is absent is flagged and not adopted; the stable's model pane draws nothing yet — a stabled pet is a database row with no world entity, and a booth that renders from a bare display id is the next arc Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
675 lines
40 KiB
XML
675 lines
40 KiB
XML
<Ui>
|
||
<!--
|
||
benilla's loot window (decision 0084), RE-SKINNED to the real 1.12 loot window: the geometry
|
||
and art below are QUOTED from the real `LootFrame.xml` (scratchpad `LootFrame.xml`; line
|
||
citations inline), turned onto our own feeds/intents. The name `LootFrame`, its panel
|
||
registration (UiPanels.xml area="left", pushable=7), the loot feeds, the 4-rows-per-page +
|
||
pager logic, and the intent functions are UNCHANGED — only the window's dressing is real now.
|
||
|
||
A hidden window that shows on LOOT_OPENED and hides on LOOT_CLOSED. It wears the real loot art:
|
||
the single UI-LootPanel slab filling the real 256×256 window (ref l.59-60, 88); each row is one
|
||
wide click button (our whole-row model) carrying the item icon and, behind the name, the real
|
||
UI-QuestItemNameFrame parchment plate (ref LootButtonTemplate, l.4-32); the pager arrows and the
|
||
minimize-button close at their real spots.
|
||
|
||
ENGINE-SLICE CAVEAT (this branch predates the dressing arc's engine slice — named `<Font>`
|
||
objects are not implemented here yet, loader.rs "named Font objects are not implemented in v1").
|
||
The real window's `inherits="GameFontNormal"` FontStrings are transcribed faithfully (they are
|
||
part of the quoted geometry), but until the font-object slice + our Fonts.xml land on this line
|
||
the loader warns "unknown template 'GameFontNormal'" and the strings render in the default font
|
||
— no worse than before. They light up when the slice merges. No <TexCoords> is used: the loot
|
||
panel is a single whole-texture slab, so this window needs none of it.
|
||
|
||
The dead-target skull sits in the panel's portrait ring: LootFramePortraitOverlay
|
||
(Interface\TargetingFrame\TargetDead), quoted from ref l.73-85 (BORDER layer, 58×58 at the
|
||
window TOPLEFT +(10,-8)). It is STATIC here — the real client swaps in FishingLoot-Icon for a
|
||
fishing loot (LootFrame.lua l.134/139), but we ship no fishing feed, so only the corpse skull
|
||
renders (and always, in the ring). Being BORDER (below the ARTWORK panel), it shows through the
|
||
panel art's ring cut-out exactly as in the real frame.
|
||
|
||
Real-window features we OMIT (no feed/model, or unchanged pager logic): the PREV/NEXT text
|
||
labels beside the pager (ref l.98-115 — our arrow buttons carry the affordance and our Update()
|
||
shows/hides only those; adding synced labels would touch the unchanged pager logic).
|
||
|
||
The Era loot API this drives (all app-fed via the loot seam, benilla-ui `script/loot.rs`):
|
||
GetNumLootItems(), GetLootSlotInfo(slot) -> texture,item,quantity,quality, LootSlotIsItem/
|
||
LootSlotIsCoin(slot), GetLootSlotLink(slot) (the row's item link, read by the click's
|
||
ctrl/shift arms — decision 1059), LootSlot(slot) (the row click's action), CloseLoot() (from
|
||
OnHide). The synthesized coin pile is row 1 when the loot carries gold; a row click sends the
|
||
money or the item's wire slot (the app maps it).
|
||
|
||
Paging (LootFrame.lua l.27-31,70-73,105-118): 4 rows/page; > 4 items spends one slot on the
|
||
pager, showing 3 rows + Up/Down. We keep the current page across content-change repaints
|
||
(LOOT_UPDATE) and reset it to 1 only on a fresh open (LOOT_OPENED) — the merchant SHOW/UPDATE
|
||
split. (The per-button LOOT_SLOT_CLEARED event is replaced by a full re-snapshot repaint; the
|
||
app fires LOOT_UPDATE on any content change, incl. an async item name/quality landing and a
|
||
looted/coin-cleared row. The OBSERVABLE matches the ref exactly: the slot list is fixed while
|
||
the window is open — GetNumLootItems counts cleared slots, which answer neither IsItem nor
|
||
IsCoin, so a looted row's button hides IN PLACE and the rows below never shift up — and a
|
||
page left with every button hidden advances to the next page when one exists, the ref's
|
||
LOOT_SLOT_CLEARED tail, l.38-50.)
|
||
|
||
UIPanel slot system (decision 0084 §2): registered in UiPanels.xml as area="left",
|
||
pushable=7 — so it carries NO hardcoded <Anchors>; ShowUIPanel SetPoints it (and, when a
|
||
pushable=0 window like the merchant also wants the left slot, promotes loot to the center
|
||
slot rather than replacing it). Every show is ShowUIPanel, every hide HideUIPanel.
|
||
-->
|
||
<Script><![CDATA[
|
||
BENILLA_LOOT_NUMBUTTONS = 4
|
||
|
||
-- The 1.12 item-quality palette — now the shared ITEM_QUALITY_COLORS global (Fonts.xml,
|
||
-- its home since the unit popup became a second customer); the local name kept for this
|
||
-- file's existing readers. Indexed 0..6; an unknown/nil quality falls back to common.
|
||
BENILLA_LOOT_QUALITY_COLORS = ITEM_QUALITY_COLORS
|
||
|
||
local function loot_color(quality)
|
||
return BENILLA_LOOT_QUALITY_COLORS[quality] or BENILLA_LOOT_QUALITY_COLORS[1]
|
||
end
|
||
|
||
-- ── Master loot (decision 1675) ───────────────────────────────────────────────────────
|
||
-- The quality at or above which handing an item out asks for confirmation first. Ref
|
||
-- LootFrame.lua l.3 — and the misspelling is Blizzard's own, kept because the corpus
|
||
-- reads THIS name: `MASTER_LOOT_THRESHOLD` (with the S) exists nowhere in 1.12.
|
||
MASTER_LOOT_THREHOLD = 4
|
||
|
||
GIVE_LOOT = GIVE_LOOT or "Give Loot To:" -- l.2006
|
||
GROUP = GROUP or "Group" -- l.2029
|
||
-- The party bound the dropdown's party arm sweeps to (ref PartyMemberFrame.lua l.1; ours
|
||
-- is declared in PartyFrame.xml, which the manifest loads far ahead of this file). Stated
|
||
-- through the same first-writer-wins idiom as the strings above rather than assumed: the
|
||
-- dropdown's init function RUNS at this file's load (UIDropDownMenu_Initialize calls it
|
||
-- immediately), so a missing constant is an arithmetic error at load, not at first use.
|
||
MAX_PARTY_MEMBERS = MAX_PARTY_MEMBERS or 4
|
||
-- The raid arm's two bounds, same idiom and same reason (declared in PartyFrame.xml /
|
||
-- RaidFrame.xml, both loaded first). The reference writes them inline as literal `40` and
|
||
-- `5` (l.194/:195); naming them is ours, so the names have to be guaranteed here.
|
||
MAX_RAID_MEMBERS = MAX_RAID_MEMBERS or 40
|
||
MEMBERS_PER_RAID_GROUP = MEMBERS_PER_RAID_GROUP or 5
|
||
CONFIRM_LOOT_DISTRIBUTION = CONFIRM_LOOT_DISTRIBUTION
|
||
or "You wish to assign %s to %s. Is this correct?" -- l.848
|
||
-- The dialog below captures these two by VALUE at load, so a load-order slip would bake a
|
||
-- nil into its buttons rather than fail loudly. FriendsFrame.xml declares them and the
|
||
-- manifest loads it first; stated again here so this file's own dialog cannot depend on
|
||
-- that staying true.
|
||
YES = YES or "Yes" -- l.5463
|
||
NO = NO or "No" -- l.2794
|
||
|
||
-- The confirmation the dropdown raises for an at-or-above-threshold item (ref
|
||
-- StaticPopup.lua l.85-94). `data` is the candidate index the dropdown stashed; the SLOT
|
||
-- is re-read live from LootFrame.selectedSlot at accept time, exactly as the ref does —
|
||
-- which is precisely why the row click below hides this popup first, so an accept can
|
||
-- never land on a row the player has since clicked away from.
|
||
StaticPopupDialogs["CONFIRM_LOOT_DISTRIBUTION"] = {
|
||
text = CONFIRM_LOOT_DISTRIBUTION,
|
||
button1 = YES,
|
||
button2 = NO,
|
||
OnAccept = function(data)
|
||
GiveMasterLoot(LootFrame.selectedSlot, data)
|
||
end,
|
||
timeout = 0,
|
||
hideOnEscape = 1,
|
||
}
|
||
|
||
function BenillaLootItem_OnLoad(button, index)
|
||
button.lootButton = index
|
||
button.icon = getglobal(button:GetName() .. "Icon")
|
||
button.name = getglobal(button:GetName() .. "Name")
|
||
button.count = getglobal(button:GetName() .. "Count")
|
||
-- Left-click loots the row (the C LootButton behaviour, LootFrame.lua l.94/147).
|
||
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||
end
|
||
|
||
-- ref LootFrameItem_OnClick (LootFrame.lua l.147-162): CTRL previews the row's item in the
|
||
-- dressing room (decision 1060), SHIFT posts its link into an open chat edit box (decision
|
||
-- 1059) — both through GetLootSlotLink, the row-link binding this arc added (loot.rs).
|
||
--
|
||
-- WHERE THE TAKE LIVES, and why the gate below reads three modifiers where FrameXML names
|
||
-- two (VERIFIED at the 1.12.1 bytes — wow-re `ui/scratch/loot-click-modifier-gate.md`,
|
||
-- decision 1067). The reference's Lua handler suppresses NOTHING, because the take is the C
|
||
-- `CLootButton`'s own click behaviour (`LootFrame.lua:94`'s `button:SetSlot(slot)` hands the
|
||
-- widget the slot). `CLootButton::OnClick 0x4c1820` runs the Lua `<OnClick>` FIRST and
|
||
-- unconditionally, then gates itself on `0x41f8f0(0/1/2)` — byte-identically the predicates
|
||
-- behind `IsShiftKeyDown`/`IsControlKeyDown`/**`IsAltKeyDown`** — and only then sends
|
||
-- (`0x4c2790` → `0x5e1ad0` → `push 0x108`, CMSG_AUTOSTORE_LOOT_ITEM).
|
||
--
|
||
-- Three consequences, all of them this handler's shape:
|
||
-- · **ALT suppresses the take too**, though no FrameXML file mentions alt. Its gate is C.
|
||
-- · The gate is on ANY modifier being HELD, not on "the arm that handled it": a shift-click
|
||
-- with the chat box closed does nothing in Lua and still must not loot.
|
||
-- · The button code is never read (`0x4c1820` doesn't look at it), so a RIGHT-click loots
|
||
-- exactly like a left one — which is why this handler takes no `mouseButton`.
|
||
--
|
||
-- benilla folds that C action into this Lua handler (`LootSlot(button.slot)`, this file's
|
||
-- model since 0084), so the gate has to be an early return here. Named divergence, not a
|
||
-- transcription slip: 1.12 exposes no plain "take slot N" binding at all — the real
|
||
-- `LootSlot 0x4c2e70` is the LOOT_BIND *confirmation continuation* (flag 1, gated on the
|
||
-- stashed slot at `[0x847cec]`; its only FrameXML caller is `StaticPopup.lua:606`). Moving
|
||
-- the take host-side onto the widget, and shrinking `LootSlot()` to that continuation, is
|
||
-- 1067's named follow-up.
|
||
--
|
||
-- The reference's tail (StaticPopup_Hide("CONFIRM_LOOT_DISTRIBUTION") and the four
|
||
-- LootFrame.selected* fields, l.155-161) is the MASTER-LOOT dropdown's bookkeeping —
|
||
-- GroupLootDropDown reads exactly those four (l.164+). benilla ships no master loot
|
||
-- (benilla-protocol's loot module: CMSG_LOOT_MASTER_GIVE/SMSG_LOOT_MASTER_LIST out of
|
||
-- scope), so returning early skips nothing that exists.
|
||
--
|
||
-- The coin row has no link (it is synthesized, with no item behind it — ui_loot.rs), so a
|
||
-- modified click on it finds nil: DressUpItemLink returns on a nil link (DressUpFrame.lua
|
||
-- l.10-16) and the insert is skipped. It does not fall through to looting the money either,
|
||
-- which is the same law the item rows follow.
|
||
function BenillaLootItem_OnClick(button)
|
||
-- The row remembers its resolved loot slot from the last Update; act on it (LootFrame.lua
|
||
-- l.94-95 stores button.slot, the click calls LootSlot on it).
|
||
if not button.slot then return end
|
||
local modified = false
|
||
if IsControlKeyDown() then
|
||
DressUpItemLink(GetLootSlotLink(button.slot)) -- ref l.148-149
|
||
modified = true
|
||
elseif IsShiftKeyDown() then
|
||
-- ref l.150-153, through the shared helper (UIParent.xml): it carries the ref's own
|
||
-- "only when the edit box is up" guard AND our nil-link one, which this row needs
|
||
-- more than most — the coin row never has a link, and an item row has none until
|
||
-- its template lands. A shift-click with chat closed does nothing here: a loot row
|
||
-- has no stack to split, unlike a bag slot.
|
||
BenillaChatEdit_InsertLink(GetLootSlotLink(button.slot))
|
||
modified = true
|
||
elseif IsAltKeyDown() then
|
||
-- The C gate's third modifier (`0x41f8f0(2)` @ `0x4c1841`). Nothing is bound to
|
||
-- alt in FrameXML, so this arm does nothing at all — its entire job is to NOT
|
||
-- loot, which is what the real client does and what we did not.
|
||
modified = true
|
||
end
|
||
|
||
-- The ref's tail (l.155-161), and it runs on EVERY click — modified ones included,
|
||
-- because in the reference this bookkeeping is the Lua <OnClick> and the take is the
|
||
-- C handler that runs after it; the modifier gate lives in the C half, so it never
|
||
-- skipped these four. Ours folds the take into this function (1067), so the tail is
|
||
-- lifted above the early return rather than left below it — same observable.
|
||
--
|
||
-- What reads them: GroupLootDropDown, which the app opens by firing
|
||
-- OPEN_MASTER_LOOT_LIST when the clicked row's wire slot_type is MASTER. That event
|
||
-- is raised from the pick drain, i.e. AFTER this handler returns, so the four fields
|
||
-- must already be stashed by then — they are the dropdown's only knowledge of WHICH
|
||
-- row is being handed out, and its anchor.
|
||
StaticPopup_Hide("CONFIRM_LOOT_DISTRIBUTION")
|
||
LootFrame.selectedLootButton = button:GetName()
|
||
LootFrame.selectedSlot = button.slot
|
||
LootFrame.selectedQuality = button.quality
|
||
LootFrame.selectedItemName = button.name:GetText()
|
||
|
||
if modified then return end
|
||
LootSlot(button.slot)
|
||
end
|
||
|
||
-- Hover: the plain engine item tooltip, ITEM rows only — the ref's own gate (its
|
||
-- LootButtonTemplate <OnEnter> runs SetLootItem only `if LootSlotIsItem(slot)`, so the
|
||
-- coin pile shows no tooltip at all). No hand-added lines: the real tooltip never shows a
|
||
-- stack size (the 0274 P1 'Stack Size' drop retired the same line from the bag hovers;
|
||
-- this file's copy predated it and survived).
|
||
function BenillaLootItem_OnEnter(button)
|
||
if not button.slot then return end
|
||
if not LootSlotIsItem(button.slot) then return end
|
||
local texture, item, quantity, quality, itemId = GetLootSlotInfo(button.slot)
|
||
button.itemId = itemId
|
||
if not item then return end
|
||
-- Snap beside the ICON, not the 160-wide row's far edge (the vendor rows' own idiom —
|
||
-- MerchantFrame.xml walks ANCHOR_RIGHT back by a negative x): 37px icon on a 160 row
|
||
-- → -123 lands the tip at the icon's right edge.
|
||
GameTooltip:SetOwner(button, "ANCHOR_RIGHT", -123, 0)
|
||
-- SetLootItem, like the ref's own LootButtonTemplate: the loot ROW is the source, not
|
||
-- just its item id, so the tooltip sees the drop's random-suffix roll. Through
|
||
-- SetItemById (a template source) a "… of the Monkey" showed `<Random enchantment>`
|
||
-- until the item reached a bag — decision 1547.
|
||
GameTooltip:SetLootItem(button.slot)
|
||
GameTooltip:Show()
|
||
end
|
||
|
||
function BenillaLootItem_OnLeave(button)
|
||
GameTooltip:Hide()
|
||
end
|
||
|
||
-- LootFrame_Update (LootFrame.lua l.67-119): fill the visible rows for the current page.
|
||
function LootFrame_Update()
|
||
local frame = getglobal("LootFrame")
|
||
local numItems = GetNumLootItems()
|
||
-- Rows per page: 4, or 3 when there's a pager to make room for (l.70-73).
|
||
local perPage = BENILLA_LOOT_NUMBUTTONS
|
||
if numItems > BENILLA_LOOT_NUMBUTTONS then
|
||
perPage = perPage - 1
|
||
end
|
||
-- Clamp the page. numItems is constant while a window is open (cleared slots stay in
|
||
-- the count — the fixed slot layout), so this only guards a stray page value.
|
||
local maxPage = 1
|
||
if numItems > 0 then
|
||
maxPage = math.ceil(numItems / perPage)
|
||
end
|
||
if frame.page > maxPage then
|
||
frame.page = maxPage
|
||
end
|
||
if frame.page < 1 then
|
||
frame.page = 1
|
||
end
|
||
|
||
for index = 1, BENILLA_LOOT_NUMBUTTONS do
|
||
local button = getglobal("LootButton" .. index)
|
||
local slot = (perPage * (frame.page - 1)) + index
|
||
if slot <= numItems and index <= perPage
|
||
and (LootSlotIsItem(slot) or LootSlotIsCoin(slot)) then
|
||
local texture, item, quantity, quality, itemId = GetLootSlotInfo(slot)
|
||
button.icon:SetTexture(texture or BENILLA_FALLBACK_ICON)
|
||
button.name:SetText(item or "...")
|
||
local color = loot_color(quality)
|
||
button.name:SetVertexColor(color.r, color.g, color.b)
|
||
-- "xN" stack count only when more than one drops (LootFrame.lua l.87-93).
|
||
if (quantity or 1) > 1 then
|
||
button.count:SetText(quantity)
|
||
else
|
||
button.count:SetText("")
|
||
end
|
||
button.slot = slot
|
||
button.itemId = itemId
|
||
-- Ref l.96. Read back by the row click into LootFrame.selectedQuality, which
|
||
-- is what decides whether handing this row out asks for confirmation first.
|
||
button.quality = quality
|
||
button:Show()
|
||
else
|
||
button:Hide()
|
||
end
|
||
end
|
||
|
||
-- Pager visibility (LootFrame.lua l.105-118): Up when past page 1, Down when more remain.
|
||
if frame.page > 1 then
|
||
getglobal("LootUpButton"):Show()
|
||
else
|
||
getglobal("LootUpButton"):Hide()
|
||
end
|
||
if frame.page < maxPage then
|
||
getglobal("LootDownButton"):Show()
|
||
else
|
||
getglobal("LootDownButton"):Hide()
|
||
end
|
||
end
|
||
|
||
function LootFrame_PageDown()
|
||
local frame = getglobal("LootFrame")
|
||
frame.page = frame.page + 1
|
||
LootFrame_Update()
|
||
end
|
||
|
||
function LootFrame_PageUp()
|
||
local frame = getglobal("LootFrame")
|
||
frame.page = frame.page - 1
|
||
LootFrame_Update()
|
||
end
|
||
|
||
function LootFrame_OnLoad()
|
||
local frame = this
|
||
frame.page = 1
|
||
frame:RegisterEvent("LOOT_OPENED")
|
||
frame:RegisterEvent("LOOT_UPDATE")
|
||
frame:RegisterEvent("LOOT_CLOSED")
|
||
-- Master loot (ref l.9-10). Neither event carries an argument: the candidate list is
|
||
-- read back through GetMasterLootCandidate at menu-build time, not pushed.
|
||
frame:RegisterEvent("OPEN_MASTER_LOOT_LIST")
|
||
frame:RegisterEvent("UPDATE_MASTER_LOOT_LIST")
|
||
end
|
||
|
||
function LootFrame_OnEvent(event)
|
||
local frame = this
|
||
if event == "LOOT_OPENED" then
|
||
-- A fresh loot: reset to page 1, paint, show (LootFrame.lua l.14-16).
|
||
frame.page = 1
|
||
LootFrame_Update()
|
||
ShowUIPanel(frame)
|
||
elseif event == "LOOT_UPDATE" then
|
||
-- A content change while open (name/quality landed, a row/coin removed): repaint,
|
||
-- keeping the current page. A looted slot's button hides IN PLACE (the slot stays
|
||
-- in the layout as a gap — the ref's LOOT_SLOT_CLEARED, l.22-37).
|
||
LootFrame_Update()
|
||
-- The ref's LOOT_SLOT_CLEARED tail (l.38-50): a page left with every button hidden
|
||
-- moves down to the next page when one exists.
|
||
local allHidden = true
|
||
for index = 1, BENILLA_LOOT_NUMBUTTONS do
|
||
if getglobal("LootButton" .. index):IsVisible() then
|
||
allHidden = false
|
||
end
|
||
end
|
||
if allHidden and getglobal("LootDownButton"):IsVisible() then
|
||
LootFrame_PageDown()
|
||
end
|
||
elseif event == "LOOT_CLOSED" then
|
||
HideUIPanel(frame)
|
||
elseif event == "OPEN_MASTER_LOOT_LIST" then
|
||
-- Ref l.58-61. Anchored to the ROW that was clicked, not to the frame — the name
|
||
-- the click stashed a moment ago.
|
||
ToggleDropDownMenu(1, nil, GroupLootDropDown, LootFrame.selectedLootButton, 0, 0)
|
||
elseif event == "UPDATE_MASTER_LOOT_LIST" then
|
||
-- Ref l.62-64: refresh IN PLACE, never re-toggle — re-toggling an open menu would
|
||
-- close it under the player's cursor.
|
||
UIDropDownMenu_Refresh(GroupLootDropDown)
|
||
end
|
||
end
|
||
|
||
function LootFrame_OnShow()
|
||
-- Open sound — the window-sound convention (decision 0090), wrapper form (this handler
|
||
-- has real logic beyond the sound, so it can't be a bare inline PlaySound).
|
||
-- (LootFrame.lua LootFrame_OnShow, l.131-141). ONLY the empty-roll fork and the fishing
|
||
-- fork are Lua-side: an empty loot plays LOOTWINDOWOPENEMPTY (l.135-136); a fishing loot
|
||
-- (IsFishingLoot(), decision 1086) plays "FISHING REEL IN" and swaps the portrait ring's
|
||
-- skull for the FishingLoot-Icon (l.137-140) — reset to the skull first, exactly as the
|
||
-- ref does (l.133), so a corpse loot after a fishing one gets its skull back. The normal
|
||
-- (non-empty, non-fishing) loot-open sound is C-side (the server-driven loot handler),
|
||
-- so we stay silent for it — there is no kit to guess. (DR 0086: vmangos masks the
|
||
-- lootable flag for empty rolls, so a live empty window is rare — but the branch must
|
||
-- exist.)
|
||
getglobal("LootPortraitOverlay"):SetTexture("Interface\\TargetingFrame\\TargetDead")
|
||
if GetNumLootItems() == 0 then
|
||
PlaySound("LOOTWINDOWOPENEMPTY")
|
||
elseif IsFishingLoot() then
|
||
PlaySound("FISHING REEL IN")
|
||
getglobal("LootPortraitOverlay"):SetTexture("Interface\\LootFrame\\FishingLoot-Icon")
|
||
end
|
||
end
|
||
|
||
function LootFrame_OnHide()
|
||
-- Closing the window releases the loot (LootFrame.lua l.143-145). The app only sends the
|
||
-- release when a loot is actually open, so a hide after the state already cleared is a
|
||
-- harmless no-op. No close sound here: LootFrame_OnHide plays none (the loot-close kit is
|
||
-- C-side), so we stay silent — no kit to guess.
|
||
CloseLoot()
|
||
end
|
||
|
||
function BenillaLootCloseButton_OnClick()
|
||
-- Hide immediately (client-authoritative); OnHide's CloseLoot() sends the release.
|
||
HideUIPanel(getglobal("LootFrame"))
|
||
end
|
||
|
||
-- ── GroupLootDropDown — the master looter's "Give Loot To:" menu ──────────────────────
|
||
-- Ref LootFrame.lua l.164-244, transcribed. The frame keeps the reference's BARE name for
|
||
-- the same mechanical reason the four GroupLootFrames do (decision 1254/0591 §3):
|
||
-- LootFrame_OnEvent resolves it by literal global lookup, and a real 1.12 addon that
|
||
-- hooks the master-loot menu expects to find it under this name.
|
||
|
||
function GroupLootDropDown_OnLoad()
|
||
-- "MENU" style: a bare context menu, no capsule chrome — it is opened at the loot row
|
||
-- by ToggleDropDownMenu, never drawn in place. Passed by name rather than the ref's
|
||
-- bare `this`, the house calling convention (TradeSkillFrame/TrainerFrame precedent).
|
||
UIDropDownMenu_Initialize(GroupLootDropDown, GroupLootDropDown_Initialize, "MENU")
|
||
end
|
||
|
||
function GroupLootDropDown_Initialize()
|
||
local candidate, info
|
||
|
||
-- Level 2: one raid group's five candidate slots (ref l.170-184). The parent row
|
||
-- stashed the index its probe found, and this walks the five starting THERE — so a
|
||
-- group whose first present candidate is not its first slot yields a window offset by
|
||
-- the same amount. That is the reference's own arithmetic; kept as-is.
|
||
if UIDROPDOWNMENU_MENU_LEVEL == 2 then
|
||
local lastIndex = UIDROPDOWNMENU_MENU_VALUE + 5 - 1
|
||
for i = UIDROPDOWNMENU_MENU_VALUE, lastIndex do
|
||
candidate = GetMasterLootCandidate(i)
|
||
if candidate then
|
||
info = {}
|
||
info.text = candidate
|
||
info.textHeight = 12
|
||
info.value = i
|
||
info.notCheckable = 1
|
||
info.func = GroupLootDropDown_GiveLoot
|
||
UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL)
|
||
end
|
||
end
|
||
return
|
||
end
|
||
|
||
if GetNumRaidMembers() > 0 then
|
||
-- In a raid: a title, then one nested row per group that has any candidate at all
|
||
-- (ref l.186-212). The probe is "does any of this block of five answer non-nil".
|
||
info = {}
|
||
info.text = GIVE_LOOT
|
||
info.textHeight = 12
|
||
info.notCheckable = 1
|
||
info.isTitle = 1
|
||
UIDropDownMenu_AddButton(info)
|
||
for i = 1, MAX_RAID_MEMBERS, MEMBERS_PER_RAID_GROUP do
|
||
for j = i, i + MEMBERS_PER_RAID_GROUP - 1 do
|
||
candidate = GetMasterLootCandidate(j)
|
||
if candidate then
|
||
info = {}
|
||
-- The label counts from the loop step `i`, the value from the found
|
||
-- slot `j` — the ref uses the two differently on purpose (l.196/:200).
|
||
info.text = GROUP .. " " .. math.ceil(i / MEMBERS_PER_RAID_GROUP)
|
||
info.textHeight = 12
|
||
info.hasArrow = 1
|
||
info.notCheckable = 1
|
||
info.value = j
|
||
info.func = nil
|
||
UIDropDownMenu_AddButton(info)
|
||
break
|
||
end
|
||
end
|
||
end
|
||
else
|
||
-- In a party: a flat list, no title row and no nesting (ref l.214-232). The sweep
|
||
-- is MAX_PARTY_MEMBERS+1 because the master looter can hand loot to THEMSELVES —
|
||
-- the server includes us in the candidate list too (Group::MasterLoot walks the
|
||
-- whole group).
|
||
for i = 1, MAX_PARTY_MEMBERS + 1, 1 do
|
||
candidate = GetMasterLootCandidate(i)
|
||
if candidate then
|
||
info = {}
|
||
info.text = candidate
|
||
info.textHeight = 12
|
||
info.value = i
|
||
info.notCheckable = 1
|
||
info.func = GroupLootDropDown_GiveLoot
|
||
UIDropDownMenu_AddButton(info)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function GroupLootDropDown_GiveLoot()
|
||
-- Ref l.234-244. At or above the threshold the assignment is confirmed first; below
|
||
-- it, it goes straight out. The popup's message takes the quality-coloured item name
|
||
-- and the candidate's own row text.
|
||
--
|
||
-- The `selectedQuality and` guard is ours, and it is required rather than defensive:
|
||
-- benilla resolves an item template asynchronously, so GetLootSlotInfo answers a nil
|
||
-- quality (and a nil name) for a row whose template is still in flight — a state the
|
||
-- real client's C-side loot record never has. Unguarded, that comparison raises. A nil
|
||
-- quality takes the below-threshold arm, which is the safe direction: the assignment
|
||
-- still happens, it just doesn't ask first.
|
||
if LootFrame.selectedQuality and LootFrame.selectedQuality >= MASTER_LOOT_THREHOLD then
|
||
local color = ITEM_QUALITY_COLORS[LootFrame.selectedQuality]
|
||
local dialog = StaticPopup_Show("CONFIRM_LOOT_DISTRIBUTION",
|
||
color.hex .. (LootFrame.selectedItemName or "") .. FONT_COLOR_CODE_CLOSE,
|
||
this:GetText())
|
||
if dialog then
|
||
dialog.data = this.value
|
||
end
|
||
else
|
||
GiveMasterLoot(LootFrame.selectedSlot, this.value)
|
||
end
|
||
CloseDropDownMenus()
|
||
end
|
||
]]></Script>
|
||
|
||
<!--
|
||
One loot row (ref LootButtonTemplate, l.4-57). The real template inherits ItemButtonTemplate
|
||
(the 37px item icon + its "xN" count) and adds, to the right of the icon, the UI-QuestItemNameFrame
|
||
parchment plate (130×62 at the button's LEFT +30; ref l.7-18) with the item name over it
|
||
(GameFontNormal, LEFT, 93×38; ref l.19-30). The real button is just the 37px icon and the name
|
||
rides off its RIGHT edge; our whole-row click model keeps the same art but sizes the button to
|
||
span icon + plate (37 icon + 8 gap → name at LEFT +45; plate at LEFT +30, 130 wide → 160 total).
|
||
-->
|
||
<Button name="BenillaLootButtonTemplate" virtual="true">
|
||
<Size><AbsDimension x="160" y="37"/></Size>
|
||
<Layers>
|
||
<!-- The item icon at the row-left (ItemButtonTemplate's 37px $parentIconTexture, ref l.4). -->
|
||
<Layer level="BORDER">
|
||
<Texture name="$parentIcon">
|
||
<Size><AbsDimension x="37" y="37"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Texture>
|
||
</Layer>
|
||
<Layer level="ARTWORK">
|
||
<!-- The parchment name plate behind the name (ref l.7-18): 130×62 at the icon's right,
|
||
the button's LEFT +30. -->
|
||
<Texture file="Interface\QuestFrame\UI-QuestItemNameFrame">
|
||
<Size><AbsDimension x="130" y="62"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="30" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Texture>
|
||
<!-- The item name (GameFontNormal, LEFT; ref l.19-30), off the icon's right edge: the
|
||
button LEFT +45 (icon 37 + the real 8px gap). -->
|
||
<FontString name="$parentName" inherits="GameFontNormal" justifyH="LEFT">
|
||
<Size><AbsDimension x="93" y="38"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="45" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
<Layer level="OVERLAY">
|
||
<!-- "xN" stack count INSIDE the icon's bottom-right (ItemButtonTemplate
|
||
$parentCount: NumberFontNormal, BOTTOMRIGHT -5,2 — the ref's button IS the
|
||
37px icon, ours spans the whole row, so the same inset re-bases off the
|
||
button's BOTTOMLEFT: 37-5=32. The bag slots' count is this exact shape). -->
|
||
<FontString name="$parentCount" inherits="NumberFontNormal" justifyH="RIGHT">
|
||
<Anchors>
|
||
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="32" y="2"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
</Layers>
|
||
<!-- Hover art over the ICON only (the ref's real button IS the 37px icon — our whole-row
|
||
click model keeps the row hit-rect but must not glow the name plate too; director's
|
||
loot-window note). -->
|
||
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD">
|
||
<Size><AbsDimension x="37" y="37"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativePoint="LEFT"/></Anchors>
|
||
</HighlightTexture>
|
||
<Scripts>
|
||
<OnClick>BenillaLootItem_OnClick(self)</OnClick>
|
||
<OnEnter>BenillaLootItem_OnEnter(self)</OnEnter>
|
||
<OnLeave>BenillaLootItem_OnLeave(self)</OnLeave>
|
||
</Scripts>
|
||
</Button>
|
||
|
||
<Frame name="LootFrame" hidden="true">
|
||
<!-- The real 256×256 window (ref l.59-60). No <Anchors> here — ShowUIPanel SetPoints this to
|
||
its slot. -->
|
||
<Size><AbsDimension x="256" y="256"/></Size>
|
||
<Layers>
|
||
<!-- The dead-target skull in the panel's portrait ring (ref LootFramePortraitOverlay,
|
||
l.73-85): Interface\TargetingFrame\TargetDead, 58×58 at the window TOPLEFT
|
||
+(10,-8). BORDER (below the ARTWORK panel) so it shows through the panel's ring cut-out.
|
||
OnShow resets it to the skull and swaps a fishing loot to FishingLoot-Icon
|
||
(decision 1086), exactly as the ref's OnShow does (l.133-140). -->
|
||
<Layer level="BORDER">
|
||
<Texture name="LootPortraitOverlay" file="Interface\TargetingFrame\TargetDead">
|
||
<Size><AbsDimension x="58" y="58"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="10" y="-8"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Texture>
|
||
</Layer>
|
||
<!-- The single UI-LootPanel slab that IS the window art (ref l.88): no size/anchors → fills
|
||
the 256×256 owner. No <TexCoords>. The solid-colour fill is gone (the panel is opaque). -->
|
||
<Layer level="ARTWORK">
|
||
<Texture file="Interface\LootFrame\UI-LootPanel"/>
|
||
<!-- Title: the real ITEMS label (ref l.89-97) — GameFontNormal at CENTER +(-12,102). -->
|
||
<FontString name="LootTitle" inherits="GameFontNormal" text="Items">
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="-12" y="102"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
</Layers>
|
||
<Frames>
|
||
<!-- The real row stack (ref l.139-174): row 1 at the window TOPLEFT +(24,-80), each next
|
||
row centred on the prior row's BOTTOM -4. -->
|
||
<Button name="LootButton1" inherits="BenillaLootButtonTemplate">
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="24" y="-80"/></Offset></Anchor></Anchors>
|
||
<Scripts><OnLoad>BenillaLootItem_OnLoad(self, 1)</OnLoad></Scripts>
|
||
</Button>
|
||
<Button name="LootButton2" inherits="BenillaLootButtonTemplate">
|
||
<Anchors><Anchor point="TOP" relativeTo="LootButton1" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||
<Scripts><OnLoad>BenillaLootItem_OnLoad(self, 2)</OnLoad></Scripts>
|
||
</Button>
|
||
<Button name="LootButton3" inherits="BenillaLootButtonTemplate">
|
||
<Anchors><Anchor point="TOP" relativeTo="LootButton2" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||
<Scripts><OnLoad>BenillaLootItem_OnLoad(self, 3)</OnLoad></Scripts>
|
||
</Button>
|
||
<Button name="LootButton4" inherits="BenillaLootButtonTemplate">
|
||
<Anchors><Anchor point="TOP" relativeTo="LootButton3" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||
<Scripts><OnLoad>BenillaLootItem_OnLoad(self, 4)</OnLoad></Scripts>
|
||
</Button>
|
||
|
||
<!-- Pager (ref l.175-216): Up (page back) at the window BOTTOMLEFT +(25,16), Down (page
|
||
forward) at the Up button's RIGHT +86 — both in the bottom-left of the panel. The
|
||
ChatIcon scroll arrows are the real art (ref l.191-193, 212-214). -->
|
||
<Button name="LootUpButton" hidden="true">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="25" y="16"/></Offset></Anchor>
|
||
</Anchors>
|
||
<NormalTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollUp-Up"/>
|
||
<PushedTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollUp-Down"/>
|
||
<DisabledTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollUp-Disabled"/>
|
||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||
<Scripts><OnClick>LootFrame_PageUp()</OnClick></Scripts>
|
||
</Button>
|
||
<Button name="LootDownButton" hidden="true">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativeTo="LootUpButton" relativePoint="RIGHT"><Offset><AbsDimension x="86" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
<NormalTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Up"/>
|
||
<PushedTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Down"/>
|
||
<DisabledTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Disabled"/>
|
||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||
<Scripts><OnClick>LootFrame_PageDown()</OnClick></Scripts>
|
||
</Button>
|
||
|
||
<!-- Close: the real LootCloseButton position (CENTER on the window's TOPRIGHT, -81,-26;
|
||
ref l.119-129), on the minimize-button art it already used (the real LootCloseButton
|
||
uses this very art, ref l.135-137). -->
|
||
<Button name="LootCloseButton">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-81" y="-26"/></Offset></Anchor>
|
||
</Anchors>
|
||
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
|
||
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
|
||
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
|
||
<Scripts><OnClick>BenillaLootCloseButton_OnClick()</OnClick></Scripts>
|
||
</Button>
|
||
</Frames>
|
||
<Scripts>
|
||
<OnLoad>LootFrame_OnLoad()</OnLoad>
|
||
<OnEvent>LootFrame_OnEvent(event)</OnEvent>
|
||
<OnShow>LootFrame_OnShow()</OnShow>
|
||
<OnHide>LootFrame_OnHide()</OnHide>
|
||
</Scripts>
|
||
</Frame>
|
||
|
||
<!--
|
||
The master looter's candidate menu (ref LootFrame.xml l.233-242). Bare reference name, and
|
||
the reason is mechanical rather than stylistic — the same one decision 1254 recorded for
|
||
GroupLootFrame1..4: LootFrame_OnEvent hands ToggleDropDownMenu this frame by literal global
|
||
lookup, and a 1.12 addon that wants to extend the master-loot menu expects to find it here.
|
||
Hidden and unanchored: ToggleDropDownMenu positions it at the clicked loot row.
|
||
-->
|
||
<Frame name="GroupLootDropDown" parent="UIParent" inherits="UIDropDownMenuTemplate" id="1" hidden="true">
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT"/>
|
||
</Anchors>
|
||
<Scripts>
|
||
<OnLoad>GroupLootDropDown_OnLoad()</OnLoad>
|
||
</Scripts>
|
||
</Frame>
|
||
</Ui>
|