- the bag windows are the reference's own `ContainerFrame.xml` running off the player's patch chain — decision 1751 makes the stock 1.12 UI off the install the end state and `assets/ui` scaffolding that retires file-by-file (86 files against the 99-file stock corpus, 72% of the 1128-function global surface already built) — via one ordered manifest, `benilla.toc`, where a bare name is a file we ship and an `Interface\FrameXML\` path is the player's own file; the windows on screen are `ContainerFrame1..12`, the bank's six copy-pasted popouts fold into them at ids 5..10, net −750 lines, and the swap forced real engine verbs into being rather than stubs (`SetBagPortaitTexture` with its id-0 no-texture arm, `GameTooltip:AppendText`, `SetButtonPulse`, and a `<Model>` whose file is the cooldown indicator materializing as the native sweep, which three corpus addons inherit) - the 1.12 walk toggle lands: `TOGGLERUN` latches one bit with `ToggleRun`'s own silent refusal chain (dead, rooted, on a live server spline), and the walk animation was never missing at all — the flag reaches it through exactly one channel, the speed (`0x5fd100` picks Walk over Run on `speed > 2 × walkSpeed`); pinning it caught our remote extrapolator with the two min rungs backwards — `min(walk, run)` runs BEFORE the backward min, so a walking backpedaller moves at 2.5 yd/s where we would have played them the Run clip at 4.5 — and one shared `current_speed` now keeps the local controller and the extrapolator from ever disagreeing about it - the instance and raid lockout messages exist — six packets that were dropped as unknown opcodes now land, four of them as the CHAT_MSG_SYSTEM lines the client composes itself, which is why no Lua handler ever surfaced them — plus `IsInInstance`, `ResetInstances`, and the unit popup's "Reset all instances" row behind the reference's own predicate; where the binary contradicts vmangos: the ownership flag means "any instance bind" but vmangos only ever names raid binds, so the row was dead for every character without a raid lockout — term 1 is now also satisfied by our own eyes, walking out of a party dungeon the server itself bound us to - opening the world map showed 3D world at the sheet's margins: `BlackoutWorld` was parented to the very `UIParent` a fullscreen panel hides, and three more frames had crossed the same boundary — the shared dropdown lists (so the map's continent and zone pickers answered a click with nothing at all), the master-looter menu, and the screenshot confirmation, which could never appear during a cinematic; `frame_flag_gate` now checks the parent seat against the reference both ways a frame acquires one (the attribute, template-inherited included, and `<Frames>` nesting), and its first harvest took 30 divergences down to three accepted ones Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
728 lines
43 KiB
XML
728 lines
43 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), BenillaTakeLootSlot(slot) (the row click's action, which
|
||
in 1.12 is the C LootButton's and no Lua binding at all — decision 1744), LootSlot(slot) (the
|
||
LOOT_BIND confirmation continuation, its only 1.12 meaning), 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) — unless the row binds on pickup, where it raises the
|
||
soulbind confirm instead and the send waits for the OK.
|
||
|
||
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, so the gate has to be an early return
|
||
-- here. The verb it calls is `BenillaTakeLootSlot`, not `LootSlot`, and that is 1067's
|
||
-- named follow-up landed (decision 1744): 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`, the entry below). Ours keeps exactly that meaning, so an addon
|
||
-- calling `LootSlot(n)` on an ordinary row does what it does on the real client: nothing.
|
||
--
|
||
-- 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
|
||
BenillaTakeLootSlot(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
|
||
|
||
-- ── The soulbind confirm ──────────────────────────────────────────────────────────────
|
||
-- ref GlobalStrings.lua l.2612 / l.2818 / l.465, fallback form (the player's own localized
|
||
-- file runs off the patch chain first).
|
||
LOOT_NO_DROP = LOOT_NO_DROP or "Looting this item will bind it to you."
|
||
OKAY = OKAY or "Okay"
|
||
CANCEL = CANCEL or "Cancel"
|
||
|
||
-- ref StaticPopup.lua l.601-611, verbatim. `OnAccept`'s argument is `dialog.data`, which
|
||
-- the driver below sets to the event's row — and `LootSlot` is the continuation verb, so
|
||
-- this line is the ONLY thing in the client that ever completes a bind-deferred take.
|
||
--
|
||
-- Nothing else is needed: no OnCancel (declining just leaves the row in the window, and the
|
||
-- app drops the pending stash when the window turns over) and no OnUpdate poll, because the
|
||
-- window's own LOOT_CLOSED hides it (below, ref LootFrame.lua l.53-54).
|
||
StaticPopupDialogs["LOOT_BIND"] = {
|
||
text = LOOT_NO_DROP,
|
||
button1 = OKAY,
|
||
button2 = CANCEL,
|
||
OnAccept = function(slot)
|
||
LootSlot(slot)
|
||
end,
|
||
timeout = 0,
|
||
exclusive = 1,
|
||
hideOnEscape = 1,
|
||
}
|
||
|
||
-- ref UIParent.lua l.317-323. The row rides on `dialog.data` rather than as a text arg —
|
||
-- LOOT_NO_DROP has no format specifier, so the dialog shows the same sentence whatever
|
||
-- binds; the number is only there to be handed back to LootSlot.
|
||
--
|
||
-- The reference registers this on UIParent, which owns every bind confirm; benilla hangs it
|
||
-- on LootFrame, whose LOOT_CLOSED already has to hide the dialog. Same two moments, one
|
||
-- listener, and the arc stays in one file.
|
||
function BenillaLootBindConfirm(slot)
|
||
local dialog = StaticPopup_Show("LOOT_BIND")
|
||
if dialog then
|
||
dialog.data = slot
|
||
end
|
||
end
|
||
|
||
function LootFrame_OnLoad()
|
||
local frame = this
|
||
frame.page = 1
|
||
frame:RegisterEvent("LOOT_OPENED")
|
||
frame:RegisterEvent("LOOT_UPDATE")
|
||
frame:RegisterEvent("LOOT_CLOSED")
|
||
frame:RegisterEvent("LOOT_BIND_CONFIRM")
|
||
-- 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
|
||
-- ref l.53-54: the confirm dies with the window it addressed. Its row number would
|
||
-- name a slot in the NEXT corpse otherwise.
|
||
StaticPopup_Hide("LOOT_BIND")
|
||
HideUIPanel(frame)
|
||
elseif event == "LOOT_BIND_CONFIRM" then
|
||
BenillaLootBindConfirm(arg1)
|
||
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" enableMouse="true" toplevel="true" parent="UIParent" 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.
|
||
|
||
No `parent=`, which is the reference's own declaration (l.233, a top-level sibling of
|
||
LootFrame) and the dropdown family's rule since 1757: a menu host under `UIParent` goes
|
||
invisible with it under an `area = "full"` panel, and a hidden host seats a hidden list.
|
||
-->
|
||
<Frame name="GroupLootDropDown" inherits="UIDropDownMenuTemplate" id="1" hidden="true">
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT"/>
|
||
</Anchors>
|
||
<Scripts>
|
||
<OnLoad>GroupLootDropDown_OnLoad()</OnLoad>
|
||
</Scripts>
|
||
</Frame>
|
||
</Ui>
|