Files
benilla-octo/crates/benilla-app/assets/ui/QuestFrame.xml
T
SamandClaude Fable 5.1 b41acbf98a Linux and Windows get their sound back, and emotes refuse while moving
- every build off macOS has been silent since the 09-02 snapshot (B356): the non-macOS device layer was a stub that opened nothing, and nothing had ever compiled the client for another target; that layer is now the same output stream over cpal — one seam, not two stacks: the ring, the render thread and the rebuild-on-device-change loop are one code on every platform, only the device file differs; the audio thread never allocates, a lost or changed default device is noticed at kira's own cadence, and realtime is `SCHED_FIFO` on unix and MMCSS "Pro Audio" on Windows, either refusable, the ring turning a refusal into a degradation; and the two instruments this bug says were missing exist — a Linux and Windows cross-compile under `clippy -D warnings`, and a live output test that opens the real device and asserts the ring never ran dry, because compiling is not sounding (1920)
- an emote that requires standing still now refuses on the move: `CheckEmoteEligible`'s `0x4000` arm writes an out-param that `DoEmote` turns into a red "You can't do that while moving!" and aborts; 20 emotes of 78 can raise the line, read off the shipped `Emotes.dbc`; SWIMMING is not in the mask, so a swimmer holding still emotes fine, and `IsSelfControlled` is TRUE for an ordinary player, so a feared or confused player emotes away happily (1904); and the cast path gains its equipped-item rung — the button greyed and pressing it still sent: the reference short-circuits to "fits" on four gates, not one, the miss being a zero `EquippedItemSubClassMask`, which means no item required at all, and the rung sits above the crowd-control validator, so a player both stunned and missing the weapon is told about the weapon; that ladder has six arms, not three — CHARMED, FLEEING and CONFUSED join (1925)
- the quest reward's money is the reference's own widget: the tail of the shared `QuestFrameItems_Update` drives `questState.."MoneyFrame"` through `MoneyFrame_Update`, and the two hand-built coin trios become one `MoneyFrameTemplate` instance each at the reference's anchors, which our coins already used; the progress panel stays on coins, being the required-money flow, not the reward one (1928); and the flight master's `!` joins the questgiver sweep, because the green `!` and the gold `!` are the SAME slot, `unit+0xb2c`, torn down once before both queries re-issue; the mouseover re-ask is refuted — the only taxi-status sender has one live caller — and the two queries are not symmetric: the questgiver's goes out on every sweep, the taxi one only when the unit's key moves or on a full sweep, as the wire shows at Dungar Longdrink: 68 questgiver status queries against 1 taxi-node one (1918)
- `GetSpellTabInfo` out of range answers `nil, nil, 0, 0` — the reference returns four values on every path that returns (`0x4b3ce0`), and ours answered a single nil copied from another binding's shape; it matters because stock `SpellBookFrame_OnLoad` calls it unconditionally and then does `id > (offset + numSpells)`, which a nil raises on and (0+0) makes true, hiding every button on the page — the behaviour to match; the argument raises the reference's usage string when absent, and the index truncates toward zero THEN decrements (1931); the perf probe gains a `WOW_PROBE_UNCAP=vsync` arm, because the uncapped present mode is a request, not a schedule — measured, it taxes the number it was meant to pin (11.80 `cpu_ms` uncapped against 14.65 under it), so the default stays uncapped; and the last three test temp paths are keyed on the pid, after concurrent runs raced on one fixed shared file

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 20:52:22 +07:00

1360 lines
97 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<Ui>
<!--
benilla's questgiver window (decision 0088; this geometry pass is the arc 0112 named as "the
giver windows still carry invented offsets... proposed as the immediate next arc" — see that
decision for the resolver-fixpoint/ScrollFrame engine work this pass builds on; its own decision
record, if any, is claimed at land time per `method.md`). Four REAL child panels now (greeting /
detail / progress / reward), mirroring the
real 1.12 `QuestFrame.xml`/`QuestFrameTemplates.xml` (scratchpad `ref-framexml/QuestFrame.*`;
`file:line` cites below are into those extracted files) structurally, not just its numbers:
QUEST_GREETING → QuestFrameGreetingPanel (SMSG_QUESTGIVER_QUEST_LIST): greeting text +
active/available quest title buttons (SelectActiveQuest/SelectAvailableQuest).
QUEST_DETAIL → QuestFrameDetailPanel (SMSG_QUESTGIVER_QUEST_DETAILS): title/description/
objectives + rewards on offer, Accept/Decline.
QUEST_PROGRESS → QuestFrameProgressPanel (SMSG_QUESTGIVER_REQUEST_ITEMS): title, request
text, required items/money, Continue (enabled iff completable)/Goodbye.
QUEST_COMPLETE → QuestFrameRewardPanel (SMSG_QUESTGIVER_OFFER_REWARD): title, reward text,
choose reward + fixed rewards + money, Complete Quest/Cancel.
QUEST_ITEM_UPDATE → in-place refresh of the visible panel's rows (async name landing).
QUEST_FINISHED → hide the window (turn-in result / interaction end).
WHY FOUR CHILD FRAMES NOW (reversing 0088 §2's "FLAT layout" finding): that finding — "a
child frame shown at runtime does not draw its own <Layers>/button-text regions" — was an
engine BUG (a bounded anchor-resolver budget silently dropping deep chains), not a real engine
constraint; the resolver now runs to a true change-driven fixpoint and a dedicated test
(`child_frame_layers_regions_render_after_the_fixpoint`, `benilla-ui`) pins that a shown child
frame's own Layers/buttons render correctly. So the ref's actual frame tree — one always-shown
`QuestFrame` chrome (portrait/name/close) hosting four independent child panels, each
shown/hidden by its own event, each carrying its own art + a real ScrollFrame — is expressible
again, and is what ships here. The reward/choice item grids still use a RUNTIME `SetPoint` chain
(not a static XML relativeTo chain) for the SAME reason the quest log's rewards do
(`QuestLogFrame.xml`'s `BenillaQuestLogRewards_Update`) — that IS the ref's own mechanism
(`QuestFrame.lua`'s shared `QuestFrameItems_Update`), now safe to port line-for-line because a
Button anchored to a chained region resolves correctly (the second half of 0112's fixpoint
finding — pinned by `long_region_chain_resolves_and_a_frame_binds_to_its_tail`).
Each panel's text pane sits on a REAL ScrollFrame (`benilla-ui` `script/scrollframe.rs`,
decision 0112's last structural gap): `SetScrollChild`/`SetVerticalScroll` pin + pan the child,
`GetVerticalScrollRange` is live off the resolved rects, and every quad in the child's subtree
clips to the ScrollFrame's rect (real "content clipped to the panel", not an overflow-and-hope).
Wheel-driven only (no scrollbar thumb widget exists in this engine yet — `Slider` is a named
FrameKind with no behavior behind it, same gap the quest log's own faux-scroll stood in for);
`<ScrollFrame>` takes the wheel by construction, same as `ScrollingMessageFrame`
(`widget/mod.rs`). The scroll child's `<Size>` is a GENEROUS FIXED budget (700px), not the
ref's own measured-to-content height (`QuestFrame_SetAsLastShown`'s real mechanism — moving a
spacer frame to the last-shown element and sizing the child off it): this engine has no
`GetBottom`/`GetTop` to read a chain's true extent back out, so a tight fit remains a documented
follow-up (the same class of gap 0088/0112 already named as "a measured layout reflow... is a
v1 dressing follow-up"); the budget only means the scroll RANGE is generous, never that content
is cut off — everything still resolves, scrolls, and clips correctly at whatever size it is.
Item row visuals (`QuestItemTemplate`) are UNCHANGED from before this pass — they were
already a capture-verified port of the quest log's own item row shape (ring-slot art), not the
ref's plainer inline `QuestItemTemplate` (`QuestFrameTemplates.xml` l.85-166) — kept for visual
consistency across the quest windows, not a fidelity regression introduced here.
Docks the left UIPanel slot (`pushable=0`, `UiPanels.xml`) exactly as before — untouched by this
pass. The window-sound convention (decision 0090/0095/0096) and the Lua seam
(`benilla-ui` `script/quest.rs`'s `GetTitleText`/`AcceptQuest`/`GetQuestReward`/… — untouched by
this pass, see that module's doc) live on the OUTER `QuestFrame` only, matching the ref's
own `QuestFrame_OnShow`/`OnHide` (l.284-295) — a panel switch never re-plays a kit, only the
window opening/closing does.
Deviations (each cited again at its own widget below):
- `QUEST_FADING_DISABLE` (the ref's "Instant Quest Text" option) is no longer a deviation:
1804 put it back on the reference's "0", so quest text writes on and the panels fade in
out of the box. The write-on machinery (0132) is ref-verbatim, and since decision 1136
the player can flip the flag on the Interface page.
- The material swap is BUILT but always Parchment: `QuestFramePanelTemplate` carries the
ref's four `$parentMaterial*` quadrants and `QuestFrame_SetMaterial` is the ref's own
body, but `GetQuestBackgroundMaterial()` is not a surface this engine has, so
`QuestFrame_GetMaterial` can only answer the ref's own nil-fallback "Parchment" — whose
arm hides all four. Same window as before; the four regions exist because they are the
ref's, and four corpus lookups address them by name.
- Reward-spell rows out of scope (`GetRewardSpell()` returns `nil`, decision 0088).
- Greeting active/available split is quest-log membership derived (`ui_quest.rs`'s
`is_active_quest`), not the wire icon — resolved already, unaffected by this pass.
-->
<Script><![CDATA[
-- The ref's OWN caps and the ref's OWN names (QuestFrame.lua l.2-3). This used to be a
-- private `BENILLA_QUEST_MAX_ITEMS = 6`, halving both — which also forced the detail and
-- reward panels to keep TWO pools of 6 (Choice*/Reward*) where the ref packs choices,
-- the spell row and fixed rewards into ONE 10-deep pool. The pools are merged below.
MAX_NUM_ITEMS = 10 -- detail/reward rows per panel (ref QuestFrame.lua:2)
MAX_REQUIRED_ITEMS = 6 -- progress-panel rows (ref QuestFrame.lua:3)
BENILLA_QUEST_MAX_TITLES = 10 -- greeting title buttons (active + available combined; ref
-- MAX_NUM_QUESTS=32, halved-and-then-some, an existing v1 cap)
BENILLA_QUEST_SCROLLCHILD_H = 700 -- the generous scroll-child budget, see header comment
-- The detail write-on (ref QuestFrame.lua:4-6): the reveal edge moves at CPS characters
-- per second, fading over LENGTH characters; the objectives/rewards block fades in over
-- QUESTINFO_FADE_IN seconds once the text has fully written.
QUEST_DESCRIPTION_GRADIENT_LENGTH = 30
QUEST_DESCRIPTION_GRADIENT_CPS = 40
QUESTINFO_FADE_IN = 1
-- "Instant Quest Text" (ref UIOptionsFrame.lua l.20/110 — uvar QUEST_FADING_DISABLE,
-- both the checkbox table's `default` and `UIOptionsFrame_Init`'s assignment are "0").
-- "0" is what we ship too, since 1804: the fade-in and the write-on are the reference's
-- out-of-box quest text, and this was pinned "1" by direction on 2026-07-17 — a taste
-- call made while the window was being built, never re-weighed as a *default*.
--
-- Flipped ON ("1"), text lands whole and the greeting/progress/reward fade-ins skip — and
-- the quill still scratches ONCE, because that is what the ref's instant mode actually
-- does (see the OnShow arm below): it jams the reveal edge past the text rather than
-- skipping the write-on, so the first OnUpdate tick plays one WriteQuest before the
-- gradient runs off. Instant is one scribble, not silence.
--
-- The ref home for this uvar is the options window, and that is where the row lives: the
-- Interface page's "Instant Quest Text" row drives it (decision 1136), and this assignment
-- is the only place the default is written down — the row CAPTURES it here rather than
-- restating one, so the Defaults button now walks the page back to the reference's value
-- because this line IS the reference's value. Registered for save on the line below
-- (1128's load order: this assignment, then the saved chunk, then consumers).
QUEST_FADING_DISABLE = "0"
RegisterForSave("QUEST_FADING_DISABLE")
-- One quest-item row (kind = "choice"/"reward"/"required", 1-based `index`) — unchanged from
-- before this pass; every panel's rows share it via `GetQuestItemInfo`.
function BenillaQuestItem_Set(button, kind, index)
local name, texture, count, quality, usable, itemId = GetQuestItemInfo(kind, index)
button.kind = kind
button.index = index
button:SetID(index) -- ref l.383/487 `questItem:SetID(i)` — the row's 1-based index
-- within its KIND, which is what GetID() answers on a real client.
button.itemId = itemId
-- ref l.388/490 ("-- For the tooltip"): every laid-out reward/choice row is stamped
-- rewardType = "item"; the ONE row that ever reads "spell" is the reward-spell row
-- (l.445), which v1 doesn't build (GetRewardSpell() is nil, decision 0088). The click
-- arms below guard on it exactly as the ref does, so the guard is live the day that
-- row lands. (Deviation, harmless: the ref leaves required rows' rewardType unset —
-- l.197-200 stamps only `.type` — which reads the same `~= "spell"` either way.)
button.rewardType = "item"
button.quality = quality
button.icon:SetTexture(texture or BENILLA_FALLBACK_ICON)
button.name:SetText(name or "...")
if (count or 1) > 1 then button.count:SetText(count) else button.count:SetText("") end
local r, g, b = 1, 1, 1
if quality and BenillaTooltip_QualityColor then r, g, b = BenillaTooltip_QualityColor(quality) end
button.name:SetTextColor(r, g, b)
button:Show()
end
-- ── The giver MATERIAL (ref QuestFrame.lua l.580-604), verbatim ─────────────────────────
-- Every quest panel is drawn on a material: Parchment (the bare corner art) or one of
-- Bronze/Marble/Silver/Stone, whose four quadrants overlay it. `SetMaterial` is the ref's
-- own body, byte-for-byte in shape; `GetMaterial` is the ref's with its one engine call
-- NAMED rather than faked.
--
-- **`GetQuestBackgroundMaterial()` IS NOT BOUND** — this engine has no such surface, so
-- `QuestFrame_GetMaterial` can only answer the ref's own fallback, "Parchment". That is
-- also the ref's answer whenever the getter returns nil, so the code path below is real and
-- reached, not a stub: the Parchment arm hides all four quadrants, which is the window we
-- ship. Written as a named absence (1134 §4) instead of a nil-returning fake binding —
-- a getter that always says nil would read like a working feature on a giver that has one.
function QuestFrame_GetMaterial()
return "Parchment"
end
function QuestFrame_SetMaterial(frame, material)
local n = frame:GetName()
local corners = { "MaterialTopLeft", "MaterialTopRight", "MaterialBotLeft", "MaterialBotRight" }
if material == "Parchment" then
for i = 1, 4 do getglobal(n .. corners[i]):Hide() end
else
for i = 1, 4 do
local t = getglobal(n .. corners[i])
t:SetTexture("Interface\\ItemTextFrame\\ItemText-" .. material .. "-" .. string.sub(corners[i], 9))
t:Show()
end
end
end
-- ── The panel scroll kit ─────────────────────────────────────────────────────────────
-- There isn't one any more: the four panels wear the ref's OWN
-- `UIPanelScrollFrameTemplate` (ref l.107/326/508/754), so the wheel, the arrows, the
-- draggable Slider thumb and the range/arrow bookkeeping all come from the template's own
-- scripts. What stood here was a hand-rolled no-drag bar plus `BenillaQuestScroll_Step`/
-- `_OnMouseWheel`/`_SyncBar`, derived by name off "<scroll>Bar" — a private-name kit that
-- published four `Quest<panel>ScrollBar*` families the reference does not have, and that
-- pfUI's `skins/blizzard/gossipquest.lua` l.100 fell through: it skins
-- `_G[scroll:GetName().."ScrollBar"]`, which only exists on a real template instance.
--
-- TWO BEHAVIOUR CHANGES, both toward the reference: the thumb DRAGS now, and the bar STAYS
-- (greyed) on a panel with nothing to scroll instead of vanishing — the ref's quest panels
-- set no `scrollBarHideable`, so `ScrollFrame_OnScrollRangeChanged` takes its
-- disable-and-show arm.
-- Size the ACTIVE panel's scroll child to its measured content — the shared real-scroll
-- kit's `BenillaScroll_ResizeChild` (ScrollTemplates.xml), which owns the widget-bottom sum,
-- the change-guard, and the UpdateScrollChildRect. The bar needs no nudge from here: the
-- resize's own `UpdateScrollChildRect` fires OnScrollRangeChanged, and the template's
-- handler seats the arrows and the thumb.
function BenillaQuestPanel_Resize(scrollName, widgets)
BenillaScroll_ResizeChild(scrollName, widgets)
end
-- Per-panel resize candidates: every element that can be the LOWEST visible thing.
BENILLA_QUEST_PANEL_WIDGETS = {
["QuestGreetingScrollFrame"] = (function()
local t = { "GreetingText", "CurrentQuestsText", "AvailableQuestsText" }
for i = 1, BENILLA_QUEST_MAX_TITLES do table.insert(t, "QuestTitleButton" .. i) end
return t
end)(),
["QuestDetailScrollFrame"] = (function()
local t = { "QuestDescription", "QuestObjectiveText", "QuestDetailRewardTitleText",
"QuestDetailItemChooseText", "QuestDetailItemReceiveText" }
for i = 1, MAX_NUM_ITEMS do table.insert(t, "QuestDetailItem" .. i) end
table.insert(t, "QuestDetailMoneyFrame")
return t
end)(),
["QuestProgressScrollFrame"] = (function()
local t = { "QuestProgressText", "QuestProgressRequiredItemsText",
"QuestProgressRequiredMoneyText", "QuestProgressMoneyCoin1",
"QuestProgressMoneyCoin2", "QuestProgressMoneyCoin3" }
for i = 1, MAX_REQUIRED_ITEMS do table.insert(t, "QuestProgressItem" .. i) end
return t
end)(),
["QuestRewardScrollFrame"] = (function()
local t = { "QuestRewardText", "QuestRewardRewardTitleText",
"QuestRewardItemChooseText", "QuestRewardItemReceiveText" }
for i = 1, MAX_NUM_ITEMS do table.insert(t, "QuestRewardItem" .. i) end
table.insert(t, "QuestRewardMoneyFrame")
return t
end)(),
}
BENILLA_QUEST_PANEL_SCROLLS = {
greeting = "QuestGreetingScrollFrame", detail = "QuestDetailScrollFrame",
progress = "QuestProgressScrollFrame", reward = "QuestRewardScrollFrame",
}
-- The convergence driver: while the window shows, keep re-measuring the ACTIVE panel
-- (cheap — the resize itself no-ops once the sum stabilizes; the log's own OnUpdate idiom).
-- The greeting panel's title rows fit FIRST, so the panel sum below already sees their
-- final bottoms in this same pass.
function BenillaQuestFrame_OnUpdate()
local frame = getglobal("QuestFrame")
if not frame:IsVisible() or not frame.panel then return end
if frame.panel == "greeting" then
for i = 1, BENILLA_QUEST_MAX_TITLES do
local button = getglobal("QuestTitleButton" .. i)
if button:IsShown() then
BenillaQuestTitleButton_Resize(button)
end
end
end
local scroll = BENILLA_QUEST_PANEL_SCROLLS[frame.panel]
if scroll then
BenillaQuestPanel_Resize(scroll, BENILLA_QUEST_PANEL_WIDGETS[scroll])
end
end
-- ── Greeting panel (ref QuestFrameGreetingPanel_OnShow, QuestFrame.lua:219-282) ──────────────
function QuestTitleButton_OnClick()
local button = this
if button.isActive then SelectActiveQuest(button.titleIndex) else SelectAvailableQuest(button.titleIndex) end
PlaySound("igQuestListSelect") -- ref QuestTitleButton_OnClick, QuestFrame.lua:303
end
-- A title row is as tall as its own wrapped title, +2 (ref QuestFrameGreetingPanel_OnShow's
-- `SetHeight(GetTextHeight() + 2)`, QuestFrame.lua:243/272 — the same law GossipFrame.xml's
-- `BenillaGossipRow_Resize` runs on the gossip rows). Titles wrap at the ButtonText's 275 px,
-- and the rows hang off each other, so a row left at the template's 16px while its title
-- draws two lines prints the next title through it. Our GetStringHeight lands a frame after
-- the SetText (the measure round-trip), hence the guard + the OnUpdate settle above.
function BenillaQuestTitleButton_Resize(button)
local text = button:GetFontString()
local h = text and text:GetHeight() or 0
if h <= 0 or button.benillaFitHeight == h then return end
button.benillaFitHeight = h
button:SetHeight(h + 2)
end
function BenillaQuestGreetingPanel_OnShow()
getglobal("QuestFrameDetailPanel"):Hide()
getglobal("QuestFrameProgressPanel"):Hide()
getglobal("QuestFrameRewardPanel"):Hide()
local greetingText = getglobal("GreetingText")
greetingText:SetText(GetGreetingText() or "")
local activeHeader = getglobal("CurrentQuestsText")
local availableHeader = getglobal("AvailableQuestsText")
local hbreak = getglobal("QuestGreetingFrameHorizontalBreak")
local numActive = math.min(GetNumActiveQuests(), BENILLA_QUEST_MAX_TITLES)
local numAvailable = math.min(GetNumAvailableQuests(), BENILLA_QUEST_MAX_TITLES - numActive)
-- Current Quests + its title-button run (l.235-253).
if numActive == 0 then
activeHeader:SetText("")
else
activeHeader:SetText("Current Quests")
activeHeader:SetPoint("TOPLEFT", greetingText, "BOTTOMLEFT", 0, -10)
for i = 1, numActive do
local button = getglobal("QuestTitleButton" .. i)
button.isActive = true
button.titleIndex = i
button:SetText(GetActiveTitle(i))
BenillaQuestTitleButton_Resize(button)
button:Show()
if i == 1 then
button:SetPoint("TOPLEFT", activeHeader, "BOTTOMLEFT", -10, -5)
else
button:SetPoint("TOPLEFT", getglobal("QuestTitleButton" .. (i - 1)), "BOTTOMLEFT", 0, 0)
end
end
end
-- The horizontal break + Available Quests + ITS title-button run (l.254-278) — the break
-- shows only when BOTH lists are non-empty (l.256-260).
if numActive > 0 and numAvailable > 0 then
hbreak:SetTexture("Interface\\QuestFrame\\UI-HorizontalBreak")
hbreak:SetPoint("TOPLEFT", getglobal("QuestTitleButton" .. numActive), "BOTTOMLEFT", 22, -10)
else
hbreak:SetTexture(nil)
end
if numAvailable == 0 then
availableHeader:SetText("")
else
availableHeader:SetText("Available Quests")
if numActive > 0 then
availableHeader:SetPoint("TOPLEFT", hbreak, "BOTTOMLEFT", -12, -10)
else
availableHeader:SetPoint("TOPLEFT", greetingText, "BOTTOMLEFT", 0, -10)
end
for i = numActive + 1, numActive + numAvailable do
local button = getglobal("QuestTitleButton" .. i)
button.isActive = false
button.titleIndex = i - numActive
button:SetText(GetAvailableTitle(i - numActive))
BenillaQuestTitleButton_Resize(button)
button:Show()
if i == numActive + 1 then
button:SetPoint("TOPLEFT", availableHeader, "BOTTOMLEFT", -10, -5)
else
button:SetPoint("TOPLEFT", getglobal("QuestTitleButton" .. (i - 1)), "BOTTOMLEFT", 0, 0)
end
end
end
for i = numActive + numAvailable + 1, BENILLA_QUEST_MAX_TITLES do
getglobal("QuestTitleButton" .. i):Hide()
end
getglobal("QuestGreetingScrollFrame"):SetVerticalScroll(0)
-- The action button inherits `hidden="true"` from `BenillaQuestPanelButtonTemplate`
-- (a virtual template — never itself drawn — declared hidden so a bare `inherits=` alone
-- doesn't imply "visible"); every panel must :Show() its own buttons explicitly, the same
-- way BenillaQuestItem_Set already does for item/title rows.
getglobal("QuestFrameGreetingGoodbyeButton"):Show()
if QUEST_FADING_DISABLE == "0" then -- ref l.223-226: instant text skips the fade-in
UIFrameFadeIn(getglobal("QuestGreetingScrollChildFrame"), QUESTINFO_FADE_IN)
end
end
function BenillaQuestFrameGreetingGoodbyeButton_OnClick()
-- ref l.749-752: no DeclineQuest() call here — the greeting has no in-progress panel to
-- decline yet. The window's own OnHide (CloseQuest()) still ends the giver session.
HideUIPanel(getglobal("QuestFrame"))
end
-- The reference's own `QuestMoneyFrame_OnLoad` (QuestFrame.lua:306-309), which lives in
-- THIS file in the reference too — so it is home, not a squatter. Its whole body is these
-- two calls. Needed because 1928 put the two reward panels on `MoneyFrameTemplate`, whose
-- instances call it from their OnLoad exactly as the reference's do. Both callees are our
-- `MoneyFrame.xml`'s, which the manifest loads at entry 141 — far above this file.
function QuestMoneyFrame_OnLoad()
MoneyFrame_OnLoad()
MoneyFrame_SetType("STATIC")
end
-- ── The shared reward grid — `QuestFrameItems_Update(questState)`, the REFERENCE'S OWN
-- NAME and the reference's own algorithm (QuestFrame.lua:311-522), ported line-for-line.
--
-- `questState` is "QuestDetail" or "QuestReward"; both panels read the SAME giver getters
-- (`GetNumQuestChoices`/`GetQuestItemInfo`/`GetRewardMoney` are giver-global, not
-- panel-specific — `benilla-ui`'s `quest.rs`), exactly as the ref's own one function feeds
-- its own two panels.
--
-- ONE POOL. This used to be `BenillaQuestItems_Update` over two static 6-deep pools
-- (`<panel>Choice1..6` and `<panel>Reward1..6`). The ref packs choices, then the
-- reward-spell row, then fixed rewards into a SINGLE `<panel>Item1..10` run with a running
-- `rewardsCount`, positioning each at runtime — which is why the ref's rows carry a bare
-- TOPLEFT anchor and why the split could not reproduce the function. pfUI is the report
-- (`skins/blizzard/gossipquest.lua` l.38-42 walks `QuestProgressItem`/`QuestDetailItem`/
-- `QuestRewardItem` 1..6 and indexes `:GetWidth()` unguarded), and it `hooksecurefunc`es
-- this function and `QuestFrameProgressItems_Update` by name.
--
-- The SPELL-REWARD arm is transcribed as the ref writes it and is dead on a 1.12 server:
-- `GetRewardSpell()` answers nil (decision 0088). Kept rather than dropped because it is
-- the arm that advances `rewardsCount` between the choices and the fixed rewards — deleting
-- it would silently change every index below it the day the row lands.
function QuestFrameItems_Update(questState)
local numChoices = GetNumQuestChoices()
local numRewards = GetNumQuestRewards()
local numSpellRewards = GetRewardSpell() and 1 or 0
local money = GetRewardMoney()
local itemName = questState .. "Item"
local totalRewards = numChoices + numRewards + numSpellRewards
-- ref l.341-349: the "Rewards" header shows iff there is anything at all to show.
local header = getglobal(questState .. "RewardTitleText")
header:SetText((totalRewards > 0 or (money and money > 0)) and "Rewards" or "")
-- ref l.358-361: hide the tail of the pool BEFORE laying any of it out.
for i = totalRewards + 1, MAX_NUM_ITEMS do
getglobal(itemName .. i):Hide()
end
local rewardsCount = 0
-- ref l.366-411: choosable rewards, 2-per-row — odd index starts a new row under the
-- row two back, even index sits beside the previous.
local chooseText = getglobal(questState .. "ItemChooseText")
chooseText:SetText(numChoices > 0 and "You may choose one of these rewards:" or "")
local lastChoice = math.min(numChoices, MAX_NUM_ITEMS)
for i = 1, lastChoice do
local index = i + rewardsCount
local item = getglobal(itemName .. index)
BenillaQuestItem_Set(item, "choice", i)
if i == 1 then
item:SetPoint("TOPLEFT", chooseText, "BOTTOMLEFT", -3, -5)
elseif i % 2 == 1 then
item:SetPoint("TOPLEFT", getglobal(itemName .. (index - 2)), "BOTTOMLEFT", 0, -2)
else
item:SetPoint("TOPLEFT", getglobal(itemName .. (index - 1)), "TOPRIGHT", 1, 0)
end
end
rewardsCount = rewardsCount + lastChoice
-- ref l.414-451: the reward-spell row. Dead here (GetRewardSpell() is nil), and the
-- `else` arm is the one that runs — it blanks the label exactly as the ref hides it.
local learnSpellText = getglobal(questState .. "SpellLearnText")
if learnSpellText then learnSpellText:SetText("") end
-- ref l.454-471: "You will receive:" / "You will also receive:" — shown for a fixed
-- reward OR money, even with zero fixed-reward rows (a money-only quest).
local receiveText = getglobal(questState .. "ItemReceiveText")
if numRewards > 0 or (money and money > 0) then
if numChoices > 0 then
receiveText:SetText("You will also receive:")
local index = lastChoice
if index % 2 == 0 then index = index - 1 end
receiveText:SetPoint("TOPLEFT", getglobal(itemName .. index), "BOTTOMLEFT", 3, -5)
else
receiveText:SetText("You will receive:")
receiveText:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 3, -5)
end
else
receiveText:SetText("")
end
-- ref l.474-513: fixed rewards, the SAME 2-per-row walk, continuing the pool.
local lastReward = math.min(numRewards, MAX_NUM_ITEMS - rewardsCount)
for i = 1, lastReward do
local index = i + rewardsCount
local item = getglobal(itemName .. index)
BenillaQuestItem_Set(item, "reward", i)
if i == 1 then
item:SetPoint("TOPLEFT", receiveText, "BOTTOMLEFT", -3, -5)
elseif i % 2 == 1 then
item:SetPoint("TOPLEFT", getglobal(itemName .. (index - 2)), "BOTTOMLEFT", 0, -2)
else
item:SetPoint("TOPLEFT", getglobal(itemName .. (index - 1)), "TOPRIGHT", 1, 0)
end
end
-- The reference's own tail (QuestFrame.lua:350-356), on the reference's own widget.
-- `QuestFrame_SetAsLastShown` is deliberately NOT called: it anchors a SPACER FRAME
-- that this client does not have — our panels size their scroll child from
-- `BENILLA_QUEST_PANEL_WIDGETS` instead (see 1928). The money half converges here; the
-- sizing model stays ours until this window migrates.
local moneyFrame = getglobal(questState .. "MoneyFrame")
if money and money > 0 then
moneyFrame:Show()
MoneyFrame_Update(questState .. "MoneyFrame", money)
else
moneyFrame:Hide()
end
end
-- ── Detail panel (ref QuestFrameDetailPanel_OnShow, QuestFrame.lua:524-552) ──────────────────
function BenillaQuestDetailPanel_OnShow()
getglobal("QuestFrameGreetingPanel"):Hide()
getglobal("QuestFrameProgressPanel"):Hide()
getglobal("QuestFrameRewardPanel"):Hide()
getglobal("QuestTitleText"):SetText(GetTitleText() or "")
getglobal("QuestDescription"):SetText(GetQuestText() or "")
getglobal("QuestDetailObjectiveTitleText"):SetText("Quest Objectives")
getglobal("QuestObjectiveText"):SetText(GetObjectiveText() or "")
QuestFrameItems_Update("QuestDetail")
getglobal("QuestDetailScrollFrame"):SetVerticalScroll(0)
getglobal("QuestFrameAcceptButton"):Show()
getglobal("QuestFrameDeclineButton"):Show()
-- Arm the write-on (ref l.538-551): objectives/rewards invisible and Accept dead
-- until the description has finished writing itself onto the parchment.
getglobal("TextAlphaDependentFrame"):SetAlpha(0)
getglobal("QuestFrameAcceptButton"):Disable()
local panel = getglobal("QuestFrameDetailPanel")
panel.fading = 1
panel.fadingProgress = 0
getglobal("QuestDescription"):SetAlphaGradient(0, QUEST_DESCRIPTION_GRADIENT_LENGTH)
-- Instant text (ref l.549-551): the write-on still arms, but the reveal edge starts
-- past any quest text's end — the first OnUpdate tick scratches the quill once, runs
-- the gradient off, and wakes everything on the spot.
if QUEST_FADING_DISABLE == "1" then
panel.fadingProgress = 1024
end
end
-- ref QuestFrameDetailPanel_OnUpdate (QuestFrame.lua:555-570): advance the reveal edge,
-- scratching the WriteQuest quill each tick — the kit's no-duplicates flag (0x20) keeps
-- the per-frame PlaySound from stacking (a new scribble only starts when the previous
-- wav ends, which IS the continuous writing sound). When the gradient runs off the end
-- of the text, fade the objectives/rewards block in (or snap it on, instant mode) and
-- wake the Accept button. With instant text the arm above started the edge at 1024, so
-- all of this happens on the first tick: one scratch, then the wake.
function BenillaQuestDetailPanel_OnUpdate(panel, elapsed)
if panel.fading then
panel.fadingProgress = panel.fadingProgress + (elapsed * QUEST_DESCRIPTION_GRADIENT_CPS)
PlaySound("WriteQuest")
if not getglobal("QuestDescription"):SetAlphaGradient(panel.fadingProgress, QUEST_DESCRIPTION_GRADIENT_LENGTH) then
panel.fading = nil
if QUEST_FADING_DISABLE == "0" then
UIFrameFadeIn(getglobal("TextAlphaDependentFrame"), QUESTINFO_FADE_IN)
else
-- instant: the block snaps on, no fade (ref l.561-564)
getglobal("TextAlphaDependentFrame"):SetAlpha(1)
end
getglobal("QuestFrameAcceptButton"):Enable()
end
end
end
function QuestFrameAcceptButton_OnClick()
AcceptQuest()
end
function QuestFrameDeclineButton_OnClick()
-- DeclineQuest() DOES send (decision 1738, byte-verified — this comment used to claim
-- the opposite for years): the app's session-end routine re-opens the giver's list
-- (CMSG_QUESTGIVER_HELLO / CMSG_GOSSIP_HELLO), which is what puts you back in an NPC's
-- menu after a decline, or answers a party member's share. Either way the panel is
-- hidden here immediately rather than waiting on a packet — the same convention every
-- NPC window's close button already uses (e.g. BenillaMerchantCloseButton_OnClick).
DeclineQuest()
PlaySound("igQuestCancel") -- ref QuestFrameDeclineButton_OnClick, QuestFrame.lua:577
HideUIPanel(getglobal("QuestFrame"))
end
-- ── Progress panel (ref QuestFrameProgressPanel_OnShow/Items_Update, QuestFrame.lua:143-217)
-- ITS OWN grid — NOT the choose/reward one above: item1 is the only Lua-repositioned row (below
-- the required-money line when money is required, else below the header directly, l.186-192);
-- items 2-6 are a STATIC XML 2-column×3-row chain off item1/item3/item5 (ref l.416-460) that
-- the resolver's fixpoint keeps correctly re-propagated as item1 moves — no Lua needed for
-- them at all. ─────────────────────────────────────────────────────────────────────────────
-- The progress panel's own grid, under the REFERENCE'S OWN NAME (ref
-- `QuestFrameProgressItems_Update`, QuestFrame.lua:165-217) — pfUI `hooksecurefunc`es it by
-- that name (`skins/blizzard/gossipquest.lua` l.131), which is why it is a function rather
-- than a block inside the OnShow it used to live in.
--
-- NOT the choose/reward walk above: `QuestProgressItem1` is the only Lua-repositioned row
-- (below the required-money line when money is required, else below the header directly,
-- ref l.186-192); items 2-6 are a STATIC XML 2-column x 3-row chain off items 1/3/5 (ref
-- l.416-460) that the resolver's fixpoint keeps re-propagated as item1 moves.
function QuestFrameProgressItems_Update()
local numItems = math.min(GetNumQuestItems(), MAX_REQUIRED_ITEMS)
local money = GetQuestMoneyToGet()
local reqText = getglobal("QuestProgressRequiredItemsText")
local moneyText = getglobal("QuestProgressRequiredMoneyText")
local item1 = getglobal("QuestProgressItem1")
if numItems > 0 or (money and money > 0) then
reqText:SetText("Items Needed:")
if money and money > 0 then
moneyText:SetText("Required Money:")
BenillaMoney_Set("QuestProgressMoney", money, true)
item1:SetPoint("TOPLEFT", moneyText, "BOTTOMLEFT", 0, -10)
else
moneyText:SetText("")
BenillaMoney_Clear("QuestProgressMoney")
item1:SetPoint("TOPLEFT", reqText, "BOTTOMLEFT", -3, -5)
end
else
reqText:SetText("")
moneyText:SetText("")
BenillaMoney_Clear("QuestProgressMoney")
end
for i = 1, MAX_REQUIRED_ITEMS do
if i <= numItems then
BenillaQuestItem_Set(getglobal("QuestProgressItem" .. i), "required", i)
else
getglobal("QuestProgressItem" .. i):Hide()
end
end
end
function BenillaQuestProgressPanel_OnShow()
getglobal("QuestFrameGreetingPanel"):Hide()
getglobal("QuestFrameDetailPanel"):Hide()
getglobal("QuestFrameRewardPanel"):Hide()
getglobal("QuestProgressTitleText"):SetText(GetTitleText() or "")
getglobal("QuestProgressText"):SetText(GetProgressText() or "")
QuestFrameProgressItems_Update()
local continueButton = getglobal("QuestFrameCompleteButton")
if IsQuestCompletable() then continueButton:Enable() else continueButton:Disable() end
continueButton:Show()
getglobal("QuestFrameGoodbyeButton"):Show()
getglobal("QuestProgressScrollFrame"):SetVerticalScroll(0)
if QUEST_FADING_DISABLE == "0" then -- ref l.159-162: instant text skips the fade-in
UIFrameFadeIn(getglobal("QuestProgressScrollChildFrame"), QUESTINFO_FADE_IN)
end
end
function BenillaQuestFrameCompleteButton_OnClick()
CompleteQuest() -- the progress panel's "Continue" — requests the reward offer
end
function BenillaQuestFrameGoodbyeButton_OnClick()
DeclineQuest()
PlaySound("igQuestCancel") -- ref QuestGoodbyeButton_OnClick, QuestFrame.lua:112
HideUIPanel(getglobal("QuestFrame"))
end
-- ── Reward panel (ref QuestFrameRewardPanel_OnShow + reward-item clicks, QuestFrame.lua:72-141)
function BenillaQuestRewardPanel_OnShow()
getglobal("QuestFrameGreetingPanel"):Hide()
getglobal("QuestFrameDetailPanel"):Hide()
getglobal("QuestFrameProgressPanel"):Hide()
getglobal("QuestRewardTitleText"):SetText(GetTitleText() or "")
getglobal("QuestRewardText"):SetText(GetRewardText() or "")
getglobal("QuestFrame").itemChoice = 0
getglobal("QuestRewardItemHighlight"):Hide()
QuestFrameItems_Update("QuestReward")
getglobal("QuestRewardScrollFrame"):SetVerticalScroll(0)
getglobal("QuestFrameCompleteQuestButton"):Show()
getglobal("QuestFrameCancelButton"):Show()
-- ref l.85-88: the whole parchment content fades in — unless the text is instant.
if QUEST_FADING_DISABLE == "0" then
getglobal("QuestRewardScrollChildFrame"):SetAlpha(0)
UIFrameFadeIn(getglobal("QuestRewardScrollChildFrame"), QUESTINFO_FADE_IN)
end
end
-- ── The item-row modifier fork, shared by every quest-item row in this window ─────────────
-- ref QuestItem_OnClick (QuestFrame.lua:115-125), the OnClick QuestItemTemplate itself
-- carries (QuestFrameTemplates.xml:162-164) — so it covers the DETAIL panel's choice/reward
-- rows, the PROGRESS panel's required rows, and the reward panel's FIXED reward rows.
-- CTRL previews the item in the dressing room (decision 1060), SHIFT posts its link into an
-- open chat edit box (decision 1059); a plain click does nothing here, which is why these
-- rows carried no OnClick before this arc.
--
-- Two guards the ref's own code has, transcribed: the ctrl arm is gated on
-- `this.rewardType ~= "spell"` (l.117 — a spell reward has no item to wear), and the shift
-- arm additionally requires the edit box to be VISIBLE (l.121; the `and rewardType ~=
-- "spell"` rides the same line there). Ours adds `ChatFrameEditBox and` — ChatFrame.xml is
-- a separate file a window test need not load, exactly as BenillaPaperDollSlot_OnClick does.
--
-- One benilla-only guard: `link` is nil until the ask-once item template lands (a state the
-- reference's synchronous item cache never has), and EditBox:Insert takes a string. The ref
-- already carries the twin of this guard inside DressUpItemLink (ref DressUpFrame.lua:11-13).
function QuestItem_OnClick()
local button = this
if IsControlKeyDown() then
if button.rewardType ~= "spell" then
DressUpItemLink(GetQuestItemLink(button.kind, button.index))
end
elseif IsShiftKeyDown() then
-- The ref's spell guard rides this arm (l.121); the "edit box up" and "link not
-- nil" pair is the shared helper's (UIParent.xml — one law, one place).
if button.rewardType ~= "spell" then
BenillaChatEdit_InsertLink(GetQuestItemLink(button.kind, button.index))
end
end
end
-- ref QuestRewardItem_OnClick (l.127-141), the OnClick the REWARD panel's choice rows carry
-- through QuestRewardItemTemplate (QuestFrameTemplates.xml:169-171). The first two arms are
-- QuestItem_OnClick's verbatim (only the shift arm's spell guard differs — the ref drops it
-- here, l.133); the choice SELECT is the THIRD arm, so a ctrl- or shift-click must NOT also
-- pick the reward. That ordering is the whole point of the fork.
function QuestRewardItem_OnClick()
local button = this
if IsControlKeyDown() then
if button.rewardType ~= "spell" then
DressUpItemLink(GetQuestItemLink(button.kind, button.index))
end
elseif IsShiftKeyDown() then
-- No spell guard on this arm — the ref drops it here (l.133). The visible/non-nil
-- pair is the shared helper's (UIParent.xml).
BenillaChatEdit_InsertLink(GetQuestItemLink(button.kind, button.index))
elseif button.kind == "choice" then
-- ref l.136's own `this.type == "choice"` test. The ref needs it because its reward
-- panel packs choices AND fixed rewards into ONE QuestRewardItem1..10 pool; ours
-- keeps two pools and hangs this template only on QuestRewardChoice*, so the
-- test is belt-and-braces — kept because it is the ref's, and because it is what
-- makes the template safe to reuse if the pools are ever merged.
BenillaQuestRewardItem_Select(button)
end
end
-- The third arm alone (ref l.136-139): only a REWARD-panel choice row is selectable — the
-- detail panel's rows and both panels' FIXED reward rows ride the plain
-- `QuestItemTemplate`, whose OnClick has no select arm at all, matching the ref's
-- separate QuestRewardItemTemplate vs QuestItemTemplate.
function BenillaQuestRewardItem_Select(button)
getglobal("QuestFrame").itemChoice = button.index
-- Ref QuestRewardItem_OnClick l.137-138 verbatim: the glow frame seats at (-8,7) on
-- the clicked row's TOPLEFT.
local hl = getglobal("QuestRewardItemHighlight")
hl:SetPoint("TOPLEFT", button, "TOPLEFT", -8, 7)
hl:Show()
end
function QuestFrameCompleteQuestButton_OnClick()
local choice = getglobal("QuestFrame").itemChoice or 0
if GetNumQuestChoices() > 0 and choice == 0 then return end -- must pick a choice first
GetQuestReward(math.max(choice - 1, 0)) -- 1-based row -> 0-based wire index
PlaySound("igQuestListComplete") -- ref QuestFrameCompleteQuestButton_OnClick, QuestFrame.lua:101
end
function QuestFrameCancelButton_OnClick()
DeclineQuest()
PlaySound("igQuestCancel") -- ref QuestFrameCancelButton_OnClick, QuestFrame.lua:93
HideUIPanel(getglobal("QuestFrame"))
end
-- ── Window lifecycle (ref QuestFrame_OnLoad/OnEvent, QuestFrame.lua:8-70) ────────────────────
function QuestFrame_OnLoad()
local frame = this
-- The NPC portrait: the "npc" unit token → the questgiver's live model bake
-- (crates/benilla/src/portrait, InteractNpc) — the same binding MerchantFrame/GossipFrame use.
SetPortraitTexture(getglobal(frame:GetName() .. "Portrait"), "npc")
frame:RegisterEvent("QUEST_GREETING")
frame:RegisterEvent("QUEST_DETAIL")
frame:RegisterEvent("QUEST_PROGRESS")
frame:RegisterEvent("QUEST_COMPLETE")
frame:RegisterEvent("QUEST_ITEM_UPDATE")
frame:RegisterEvent("QUEST_FINISHED")
frame.itemChoice = 0
frame.panel = nil
end
-- Re-run whichever panel is currently live (QUEST_ITEM_UPDATE — an in-place row refresh, e.g.
-- an item name landing async; ref l.45-58 calls the item-grid function directly rather than
-- re-showing the panel — re-running the panel's own OnShow here is equivalent AND simpler: it
-- reads the same live QuestState, so title/body come out unchanged and only the rows actually
-- differ).
function BenillaQuestFrame_Repaint(panel)
if panel == "greeting" then BenillaQuestGreetingPanel_OnShow()
elseif panel == "detail" then BenillaQuestDetailPanel_OnShow()
elseif panel == "progress" then BenillaQuestProgressPanel_OnShow()
elseif panel == "reward" then BenillaQuestRewardPanel_OnShow() end
end
function QuestFrame_OnEvent(event)
local frame = this
if event == "QUEST_FINISHED" then
HideUIPanel(frame)
frame.panel = nil
return
end
if event == "QUEST_ITEM_UPDATE" then
-- The in-place refresh also carries the NPC name (arg1) — in live play the ask-once
-- name often lands one frame AFTER the panel opened, arriving exactly here; dropping
-- it left the title bar blank (caught reviewing the ui-quest capture).
if arg1 and arg1 ~= "" then getglobal("QuestFrameNpcNameText"):SetText(arg1) end
if frame:IsVisible() and frame.panel then BenillaQuestFrame_Repaint(frame.panel) end
return
end
getglobal("QuestFrameNpcNameText"):SetText(arg1 or "")
if event == "QUEST_GREETING" then frame.panel = "greeting"
elseif event == "QUEST_DETAIL" then frame.panel = "detail"
elseif event == "QUEST_PROGRESS" then frame.panel = "progress"
elseif event == "QUEST_COMPLETE" then frame.panel = "reward" end
ShowUIPanel(frame)
-- ref l.33-44: Hide() then Show() the target CHILD panel — forces its own OnShow to run
-- (which hides the other three siblings + repaints itself), even on a same-panel refresh
-- (e.g. re-QUERY_QUEST on the same NPC while Detail is already open).
local names = {
greeting = "QuestFrameGreetingPanel", detail = "QuestFrameDetailPanel",
progress = "QuestFrameProgressPanel", reward = "QuestFrameRewardPanel",
}
local target = getglobal(names[frame.panel])
target:Hide()
target:Show()
end
function BenillaQuestFrameCloseButton_OnClick()
CloseQuest()
HideUIPanel(getglobal("QuestFrame"))
end
]]></Script>
<!--
One quest-item row, ref-verbatim (QuestItemTemplate, QuestFrameTemplates.xml:85-138): a bare
39×39 icon at TOPLEFT (the ref draws NO slot-well behind it — the earlier `UI-EmptySlot` here
was our own addition and read as a heavy black box on the parchment), the name plate
overlapping the icon's right edge by 10, the name in GameFontHighlight (12px, not Small),
and the count riding the icon's BOTTOMRIGHT. Kept in sync by hand with the quest log's
`QuestLogRewardItemTemplate` (per-window test-isolation duplication, that template's own
comment).
-->
<Button name="QuestItemTemplate" virtual="true" hidden="true">
<Size><AbsDimension x="147" y="41"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentIconTexture">
<Size><AbsDimension x="39" y="39"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
</Texture>
<Texture name="$parentNameFrame" file="Interface\QuestFrame\UI-QuestItemNameFrame">
<Size><AbsDimension x="128" y="64"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="$parentIconTexture" relativePoint="RIGHT"><Offset><AbsDimension x="-10" y="0"/></Offset></Anchor></Anchors>
</Texture>
<FontString name="$parentName" inherits="GameFontHighlight" justifyH="LEFT">
<Size><AbsDimension x="90" y="36"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="$parentNameFrame"><Offset><AbsDimension x="15" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
<Layer level="ARTWORK">
<FontString name="$parentCount" inherits="NumberFontNormal" justifyH="RIGHT">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentIconTexture"><Offset><AbsDimension x="-4" y="1"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<!-- NO hover art: the ref QuestItemTemplate carries no HighlightTexture — reward rows
don't glow on hover; only the reward panel's CHOICE click seats the selection glow
(QuestRewardItemHighlight). The old ButtonHilight-Square here was our own
addition (the director's img-18). -->
<Scripts>
<OnLoad>
local n = self:GetName()
self.icon = getglobal(n .. "IconTexture")
self.name = getglobal(n .. "Name")
self.count = getglobal(n .. "Count")
</OnLoad>
<!-- The shared id-keyed item tooltip (ref QuestItemTemplate OnEnter →
GameTooltip:SetQuestItem — the same C++ renderer every item hover uses). -->
<OnEnter>
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetItemById(self.itemId, self.name:GetText(), self.quality)
GameTooltip:Show()
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
<!-- ref QuestItemTemplate's own OnClick (QuestFrameTemplates.xml:162-164): the CTRL
dressing-room preview + the SHIFT chat-link post, and nothing else — a plain click
on one of these rows is inert (decisions 1059/1060). -->
<OnClick>QuestItem_OnClick()</OnClick>
</Scripts>
</Button>
<!-- A REWARD-panel choice row: the same visual, plus the ref's `QuestRewardItemTemplate` click
(QuestFrameTemplates.xml:167-173) — the ctrl/shift arms shared with the base template, then
the choose highlight as the THIRD arm. The detail panel's rows and both panels' FIXED reward
rows stay on plain `QuestItemTemplate`, whose OnClick has no select arm (ref: only
reward-panel CHOICE rows select). -->
<Button name="QuestRewardItemTemplate" inherits="QuestItemTemplate" virtual="true" hidden="true">
<Scripts><OnClick>QuestRewardItem_OnClick()</OnClick></Scripts>
</Button>
<!-- **The reference's own name**, since 1751's thirteenth window. It was
`BenillaQuestTitleButtonTemplate` while both this file and its only consumers were ours;
the sourced `Interface\FrameXML\GuildRegistrarFrame.xml` inherits the reference's name
for its two service rows, and an unknown template is a loader WARNING — the frame builds
and silently has no art — so the rename is what keeps that window skinned.
A greeting quest-title button (ref QuestTitleButtonTemplate, QuestFrameTemplates.xml l.174-213):
a bullet + the quest title. -->
<Button name="QuestTitleButtonTemplate" virtual="true">
<Size><AbsDimension x="300" y="16"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\QuestFrame\UI-Quest-BulletPoint">
<Size><AbsDimension x="16" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
</Texture>
</Layer>
</Layers>
<HighlightTexture file="Interface\QuestFrame\UI-QuestTitleHighlight" alphaMode="ADD"/>
<Scripts><OnClick>QuestTitleButton_OnClick()</OnClick></Scripts>
<!-- QuestFont (13px BLACK on parchment), LEFT, x+20 beside the bullet. We anchor LEFT (the
row's vertical centre), NOT the reference's TOPLEFT: the round UI-Quest-BulletPoint is
cell-centred, so a top-anchored title hangs its centreline ABOVE the bullet and reads
"too high" (director's falsifier). LEFT-anchoring drops the title onto the row centre
where the bullet sits — a deliberate deviation (the reference top-anchors both and lives
with the low bullet). -->
<ButtonText>
<Size><AbsDimension x="275" y="0"/></Size>
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="20" y="0"/></Offset></Anchor></Anchors>
</ButtonText>
<NormalFont inherits="QuestFont" justifyH="LEFT"/>
</Button>
<!-- A panel action button (Accept/Decline/…): the UIPanelButtonTemplate face, label via text=. -->
<Button name="BenillaQuestPanelButtonTemplate" virtual="true" hidden="true">
<Size><AbsDimension x="80" y="22"/></Size>
<NormalFont inherits="GameFontNormal"/>
<HighlightFont inherits="GameFontHighlight"/>
<DisabledFont inherits="GameFontDisable"/>
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
<DisabledTexture file="Interface\Buttons\UI-Panel-Button-Disabled"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></DisabledTexture>
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
</Button>
<!-- One money coin slot (shared money display; BenillaMoney_Set fills three, high-denom first,
shrinking each to its number + 13px coin — the XML width is just the pre-fill state). -->
<Frame name="BenillaQuestMoneyCoinTemplate" virtual="true">
<Size><AbsDimension x="13" y="13"/></Size>
<Layers><Layer level="OVERLAY">
<Texture name="$parentIcon" file="Interface\MoneyFrame\UI-MoneyIcons"><Size><AbsDimension x="13" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"/></Anchors></Texture>
<FontString name="$parentNum" inherits="NumberFontNormal" justifyH="RIGHT"><Size><AbsDimension x="20" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor></Anchors></FontString>
</Layer></Layers>
</Frame>
<!-- The shared sub-panel chrome (ref `QuestFramePanelTemplate`, QuestFrameTemplates.xml l.3-84):
the four corner parchment quadrants every one of the four panels draws independently (the
ref's own choice — each real sub-panel repeats this art rather than the window compositing it
once). The ref's material BORDER textures (the Parchment/Book skin swap,
`QuestFrame_SetMaterial`) are omitted — no such Lua surface exists in this engine (this
window's header comment). Fills the whole window (384×512, TOPLEFT the parent panel's own
TOPLEFT — each concrete panel below inherits this and is itself anchored to fill
`QuestFrame`). -->
<!-- ref QuestFrameTemplates.xml l.20-84. Was `QuestFramePanelTemplate`, a name the reference
does not have. -->
<Frame name="QuestFramePanelTemplate" virtual="true" hidden="true">
<Size><AbsDimension x="384" y="512"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\QuestFrame\UI-QuestGreeting-TopLeft"><Size><AbsDimension x="256" y="256"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors></Texture>
<Texture file="Interface\QuestFrame\UI-QuestGreeting-TopRight"><Size><AbsDimension x="128" y="256"/></Size><Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"/></Anchors></Texture>
<Texture file="Interface\QuestFrame\UI-QuestGreeting-BotLeft"><Size><AbsDimension x="256" y="256"/></Size><Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"/></Anchors></Texture>
<Texture file="Interface\QuestFrame\UI-QuestGreeting-BotRight"><Size><AbsDimension x="128" y="256"/></Size><Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"/></Anchors></Texture>
</Layer>
<!-- The MATERIAL quadrants (ref l.45-82), fileless and unshown: `QuestFrame_SetMaterial`
paints and shows them for a Bronze/Marble/Silver/Stone giver and HIDES all four for
a Parchment one. Parchment is the branch this client always takes
(`QuestFrame_GetMaterial` below), so they draw nothing today and the window looks
exactly as it did — they are here because they are four real regions of the ref's
template that four corpus lookups address by name, not to make a picture. -->
<Layer level="BORDER">
<Texture name="$parentMaterialTopLeft" hidden="true">
<Size><AbsDimension x="239" y="241"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="21" y="-75"/></Offset></Anchor></Anchors>
</Texture>
<Texture name="$parentMaterialTopRight" hidden="true">
<Size><AbsDimension x="64" y="241"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentMaterialTopLeft" relativePoint="TOPRIGHT"/></Anchors>
</Texture>
<Texture name="$parentMaterialBotLeft" hidden="true">
<Size><AbsDimension x="239" y="128"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentMaterialTopLeft" relativePoint="BOTTOMLEFT"/></Anchors>
</Texture>
<Texture name="$parentMaterialBotRight" hidden="true">
<Size><AbsDimension x="64" y="128"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentMaterialTopLeft" relativePoint="BOTTOMRIGHT"/></Anchors>
</Texture>
</Layer>
</Layers>
</Frame>
<Frame name="QuestFrame" enableMouse="true" toplevel="true" parent="UIParent" hidden="true">
<Size><AbsDimension x="384" y="512"/></Size>
<Layers>
<!-- NPC portrait 60×60 TOPLEFT (7,-6) (ref QuestFramePortrait l.20-33). Drawn on the OUTER
frame, which is created/inserted BEFORE its child panels — the panel-level chrome
above therefore paints AFTER (and so visually "above") this portrait in the same
stratum (`order.rs`'s frame-level → insertion-order tie-break), letting the corner
art's baked-in circular cutout frame the portrait exactly like the flat layout did. -->
<Layer level="ARTWORK">
<Texture name="$parentPortrait">
<Size><AbsDimension x="60" y="60"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- NPC name — the ref's OWN stacking solution transcribed (ref l.36-61): a dedicated
QuestNpcNameFrame child raised +2 frame levels in its OnLoad, so the name draws
ABOVE the four sub-panels' book art (a bare FontString on the outer frame sits at
level 0 and the level-1 panels paint over it — the blank-title-bar bug the
ui-quest capture caught; the engine z-order honors frame level exactly like the
client's). frameLevel="3" is the static equivalent of the ref's GetFrameLevel()+2
(outer 0, panels 1). -->
<Frame name="QuestNpcNameFrame" frameLevel="3">
<Size><AbsDimension x="300" y="14"/></Size>
<Anchors><Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="-23"/></Offset></Anchor></Anchors>
<Layers><Layer level="BACKGROUND">
<FontString name="QuestFrameNpcNameText" inherits="GameFontHighlight" justifyH="CENTER">
<Size><AbsDimension x="235" y="20"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</FontString>
</Layer></Layers>
</Frame>
<!-- Close (ref l.62-70: CENTER on the window's TOPRIGHT, -42,-31 — the SAME position
GossipFrame.xml's own close button uses, since quest and gossip are the same "list"
window family in the real client) on the minimize-button art this engine's windows all
substitute for the real UIPanelCloseButton (that art rides the tooltip/dialog arc). -->
<Button name="QuestFrameCloseButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-42" y="-31"/></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>BenillaQuestFrameCloseButton_OnClick()</OnClick></Scripts>
</Button>
<!-- ══════════════════════════ Greeting panel (ref l.719-1002) ══════════════════════════ -->
<Frame name="QuestFrameGreetingPanel" inherits="QuestFramePanelTemplate" hidden="true">
<Layers>
<Layer level="ARTWORK">
<!-- Ref QuestFrame.xml:722-733: the greeting layout has no bottom-LEFT
button, but the shared UI-QuestGreeting-BotLeft art has the button
recess BAKED IN — the ref covers it with this patch (the director's
"bottom left hole"). -->
<Texture file="Interface\QuestFrame\UI-Quest-BotLeftPatch">
<Size><AbsDimension x="128" y="64"/></Size>
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="22" y="68"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<Button name="QuestFrameGreetingGoodbyeButton" inherits="BenillaQuestPanelButtonTemplate" text="Goodbye">
<Size><AbsDimension x="78" y="22"/></Size>
<Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-39" y="73"/></Offset></Anchor></Anchors>
<Scripts><OnClick>BenillaQuestFrameGreetingGoodbyeButton_OnClick()</OnClick></Scripts>
</Button>
<ScrollFrame name="QuestGreetingScrollFrame" inherits="UIPanelScrollFrameTemplate">
<Size><AbsDimension x="300" y="334"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="23" y="-81"/></Offset></Anchor></Anchors>
<ScrollChild>
<Frame name="QuestGreetingScrollChildFrame">
<Size><AbsDimension x="300" y="700"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
<Layers>
<Layer level="ARTWORK">
<FontString name="GreetingText" inherits="QuestFont" justifyH="LEFT">
<Size><AbsDimension x="270" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="10" y="-10"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="CurrentQuestsText" inherits="QuestTitleFont" justifyH="LEFT">
<Size><AbsDimension x="300" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="GreetingText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-10"/></Offset></Anchor></Anchors>
</FontString>
<Texture name="QuestGreetingFrameHorizontalBreak" file="Interface\QuestFrame\UI-HorizontalBreak">
<Size><AbsDimension x="256" y="32"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="GreetingText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="12" y="-10"/></Offset></Anchor></Anchors>
</Texture>
<FontString name="AvailableQuestsText" inherits="QuestTitleFont" justifyH="LEFT">
<Size><AbsDimension x="300" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="GreetingText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-10"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Button name="QuestTitleButton1" inherits="QuestTitleButtonTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CurrentQuestsText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-10" y="-5"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTitleButton2" inherits="QuestTitleButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="QuestTitleButton1" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="QuestTitleButton3" inherits="QuestTitleButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="QuestTitleButton2" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="QuestTitleButton4" inherits="QuestTitleButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="QuestTitleButton3" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="QuestTitleButton5" inherits="QuestTitleButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="QuestTitleButton4" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="QuestTitleButton6" inherits="QuestTitleButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="QuestTitleButton5" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="QuestTitleButton7" inherits="QuestTitleButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="QuestTitleButton6" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="QuestTitleButton8" inherits="QuestTitleButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="QuestTitleButton7" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="QuestTitleButton9" inherits="QuestTitleButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="QuestTitleButton8" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="QuestTitleButton10" inherits="QuestTitleButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="QuestTitleButton9" relativePoint="BOTTOMLEFT"/></Anchors></Button>
</Frames>
</Frame>
</ScrollChild>
</ScrollFrame>
</Frames>
<Scripts><OnShow>BenillaQuestGreetingPanel_OnShow()</OnShow></Scripts>
</Frame>
<!-- ══════════════════════════ Detail panel (ref l.472-718) ═════════════════════════════ -->
<Frame name="QuestFrameDetailPanel" inherits="QuestFramePanelTemplate" hidden="true">
<Frames>
<Button name="QuestFrameAcceptButton" inherits="BenillaQuestPanelButtonTemplate" text="Accept">
<Size><AbsDimension x="77" y="22"/></Size>
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="23" y="72"/></Offset></Anchor></Anchors>
<Scripts><OnClick>QuestFrameAcceptButton_OnClick()</OnClick></Scripts>
</Button>
<Button name="QuestFrameDeclineButton" inherits="BenillaQuestPanelButtonTemplate" text="Decline">
<Size><AbsDimension x="78" y="22"/></Size>
<Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-39" y="72"/></Offset></Anchor></Anchors>
<Scripts><OnClick>QuestFrameDeclineButton_OnClick()</OnClick></Scripts>
</Button>
<ScrollFrame name="QuestDetailScrollFrame" inherits="UIPanelScrollFrameTemplate">
<Size><AbsDimension x="300" y="334"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="23" y="-81"/></Offset></Anchor></Anchors>
<ScrollChild>
<Frame name="QuestDetailScrollChildFrame">
<Size><AbsDimension x="300" y="700"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
<Layers>
<Layer level="ARTWORK">
<FontString name="QuestTitleText" inherits="QuestTitleFont" justifyH="LEFT">
<Size><AbsDimension x="285" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="5" y="-10"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestDescription" inherits="QuestFont" justifyH="LEFT">
<Size><AbsDimension x="270" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestTitleText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- Everything below the description rides ONE frame whose alpha
the write-on machinery drives (ref TextAlphaDependentFrame,
QuestFrame.xml l.553): 1.12 SetAlpha overwrite-cascades into
the child item buttons/coins (wow-re propagation.md,
CORRECTED), so the whole block hides while the text writes
and fades in as one.
**The reference's own bare name.** This was spelled
`QuestDetailTextAlphaFrame` until decision 1757, with the
citation above already naming what it should have been —
the slip 1189 describes and `BlackoutWorld` paid for: a
published global spelled to our taste is a global the
reference has and we do not, and the reference's own
QuestFrame.lua indexes this one by hand (l.543/562/564). -->
<Frame name="TextAlphaDependentFrame">
<Size><AbsDimension x="1" y="1"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestDescription" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
<Layers>
<Layer level="ARTWORK">
<FontString name="QuestDetailObjectiveTitleText" inherits="QuestTitleFont" justifyH="LEFT">
<Size><AbsDimension x="285" y="18"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestDescription" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-15"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestObjectiveText" inherits="QuestFont" justifyH="LEFT">
<Size><AbsDimension x="270" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestDetailObjectiveTitleText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestDetailRewardTitleText" inherits="QuestTitleFont" justifyH="LEFT">
<Size><AbsDimension x="285" y="18"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestObjectiveText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-15"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestDetailItemChooseText" inherits="QuestFont" justifyH="LEFT">
<Size><AbsDimension x="295" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestDetailRewardTitleText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestDetailItemReceiveText" inherits="QuestFont" justifyH="LEFT">
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestDetailRewardTitleText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="3" y="-5"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- ONE pool of 10, the ref's own shape (ref l.190-239 / l.627-676): the panel packs
CHOICES, then the reward-spell row, then FIXED rewards into a single numbered
run, and positions every one of them at runtime — so each carries the ref's own
bare TOPLEFT anchor and nothing else. This replaced two static 6-deep pools
(QuestDetailChoice*/QuestDetailReward*), whose names the reference does not have and
whose split QuestFrameItems_Update could not reproduce. -->
<Button name="QuestDetailItem1" inherits="QuestItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestDetailItem2" inherits="QuestItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestDetailItem3" inherits="QuestItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestDetailItem4" inherits="QuestItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestDetailItem5" inherits="QuestItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestDetailItem6" inherits="QuestItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestDetailItem7" inherits="QuestItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestDetailItem8" inherits="QuestItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestDetailItem9" inherits="QuestItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestDetailItem10" inherits="QuestItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Frame name="QuestDetailMoneyFrame" inherits="MoneyFrameTemplate"><Anchors><Anchor point="LEFT" relativeTo="QuestDetailItemReceiveText" relativePoint="RIGHT"><Offset><AbsDimension x="15" y="0"/></Offset></Anchor></Anchors><Scripts><OnLoad>QuestMoneyFrame_OnLoad()</OnLoad></Scripts></Frame>
</Frames>
</Frame>
</Frames>
</Frame>
</ScrollChild>
</ScrollFrame>
</Frames>
<Scripts>
<OnShow>BenillaQuestDetailPanel_OnShow()</OnShow>
<OnUpdate>BenillaQuestDetailPanel_OnUpdate(this, arg1)</OnUpdate>
</Scripts>
</Frame>
<!-- ══════════════════════════ Progress panel (ref l.290-471) ═══════════════════════════ -->
<Frame name="QuestFrameProgressPanel" inherits="QuestFramePanelTemplate" hidden="true">
<Frames>
<Button name="QuestFrameCompleteButton" inherits="BenillaQuestPanelButtonTemplate" text="Continue">
<Size><AbsDimension x="120" y="22"/></Size>
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="22" y="72"/></Offset></Anchor></Anchors>
<Scripts><OnClick>BenillaQuestFrameCompleteButton_OnClick()</OnClick></Scripts>
</Button>
<Button name="QuestFrameGoodbyeButton" inherits="BenillaQuestPanelButtonTemplate" text="Goodbye">
<Size><AbsDimension x="78" y="22"/></Size>
<Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-39" y="73"/></Offset></Anchor></Anchors>
<Scripts><OnClick>BenillaQuestFrameGoodbyeButton_OnClick()</OnClick></Scripts>
</Button>
<ScrollFrame name="QuestProgressScrollFrame" inherits="UIPanelScrollFrameTemplate">
<Size><AbsDimension x="300" y="334"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="23" y="-81"/></Offset></Anchor></Anchors>
<ScrollChild>
<Frame name="QuestProgressScrollChildFrame">
<Size><AbsDimension x="300" y="700"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
<Layers>
<Layer level="ARTWORK">
<FontString name="QuestProgressTitleText" inherits="QuestTitleFont" justifyH="LEFT">
<Size><AbsDimension x="285" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="5" y="-10"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestProgressText" inherits="QuestFont" justifyH="LEFT">
<Size><AbsDimension x="270" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestProgressTitleText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestProgressRequiredItemsText" inherits="QuestTitleFont" justifyH="LEFT">
<Size><AbsDimension x="295" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestProgressText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-10"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestProgressRequiredMoneyText" inherits="QuestFontNormalSmall" justifyH="LEFT">
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestProgressRequiredItemsText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-10"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Frame name="QuestProgressMoneyCoin1" inherits="BenillaQuestMoneyCoinTemplate"><Anchors><Anchor point="LEFT" relativeTo="QuestProgressRequiredMoneyText" relativePoint="RIGHT"><Offset><AbsDimension x="10" y="0"/></Offset></Anchor></Anchors></Frame>
<Frame name="QuestProgressMoneyCoin2" inherits="BenillaQuestMoneyCoinTemplate"><Anchors><Anchor point="LEFT" relativeTo="QuestProgressMoneyCoin1" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors></Frame>
<Frame name="QuestProgressMoneyCoin3" inherits="BenillaQuestMoneyCoinTemplate"><Anchors><Anchor point="LEFT" relativeTo="QuestProgressMoneyCoin2" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors></Frame>
<!-- The 2-col×3-row grid (ref l.407-460): item1 is Lua-repositioned
(money-line vs header-line); items 2-6 are a STATIC chain off
1/3/5 the resolver's fixpoint re-propagates automatically. -->
<Button name="QuestProgressItem1" inherits="QuestItemTemplate" id="1"><Anchors><Anchor point="TOPLEFT" relativeTo="QuestProgressRequiredItemsText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-3" y="-5"/></Offset></Anchor></Anchors></Button>
<Button name="QuestProgressItem2" inherits="QuestItemTemplate" id="2"><Anchors><Anchor point="LEFT" relativeTo="QuestProgressItem1" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors></Button>
<Button name="QuestProgressItem3" inherits="QuestItemTemplate" id="3"><Anchors><Anchor point="TOP" relativeTo="QuestProgressItem1" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></Button>
<Button name="QuestProgressItem4" inherits="QuestItemTemplate" id="4"><Anchors><Anchor point="LEFT" relativeTo="QuestProgressItem3" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors></Button>
<Button name="QuestProgressItem5" inherits="QuestItemTemplate" id="5"><Anchors><Anchor point="TOP" relativeTo="QuestProgressItem3" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></Button>
<Button name="QuestProgressItem6" inherits="QuestItemTemplate" id="6"><Anchors><Anchor point="LEFT" relativeTo="QuestProgressItem5" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors></Button>
</Frames>
</Frame>
</ScrollChild>
</ScrollFrame>
</Frames>
<Scripts><OnShow>BenillaQuestProgressPanel_OnShow()</OnShow></Scripts>
</Frame>
<!-- ══════════════════════════ Reward panel (ref l.71-289) ══════════════════════════════ -->
<Frame name="QuestFrameRewardPanel" inherits="QuestFramePanelTemplate" hidden="true">
<Frames>
<Button name="QuestFrameCompleteQuestButton" inherits="BenillaQuestPanelButtonTemplate" text="Complete Quest">
<Size><AbsDimension x="120" y="22"/></Size>
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="22" y="72"/></Offset></Anchor></Anchors>
<Scripts><OnClick>QuestFrameCompleteQuestButton_OnClick()</OnClick></Scripts>
</Button>
<Button name="QuestFrameCancelButton" inherits="BenillaQuestPanelButtonTemplate" text="Cancel">
<Size><AbsDimension x="78" y="22"/></Size>
<Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-39" y="73"/></Offset></Anchor></Anchors>
<Scripts><OnClick>QuestFrameCancelButton_OnClick()</OnClick></Scripts>
</Button>
<ScrollFrame name="QuestRewardScrollFrame" inherits="UIPanelScrollFrameTemplate">
<Size><AbsDimension x="300" y="334"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="23" y="-81"/></Offset></Anchor></Anchors>
<ScrollChild>
<Frame name="QuestRewardScrollChildFrame">
<Size><AbsDimension x="300" y="700"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
<Layers>
<Layer level="ARTWORK">
<FontString name="QuestRewardTitleText" inherits="QuestTitleFont" justifyH="LEFT">
<Size><AbsDimension x="285" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="5" y="-10"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestRewardText" inherits="QuestFont" justifyH="LEFT">
<Size><AbsDimension x="275" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestRewardTitleText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestRewardRewardTitleText" inherits="QuestTitleFont" justifyH="LEFT">
<Size><AbsDimension x="285" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestRewardText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-10"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestRewardItemChooseText" inherits="QuestFont" justifyH="LEFT">
<Size><AbsDimension x="295" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestRewardRewardTitleText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="QuestRewardItemReceiveText" inherits="QuestFont" justifyH="LEFT">
<Anchors><Anchor point="TOPLEFT" relativeTo="QuestRewardRewardTitleText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- ONE pool of 10, the ref's own shape (ref l.190-239 / l.627-676): the panel packs
CHOICES, then the reward-spell row, then FIXED rewards into a single numbered
run, and positions every one of them at runtime — so each carries the ref's own
bare TOPLEFT anchor and nothing else. This replaced two static 6-deep pools
(QuestRewardChoice*/QuestRewardReward*), whose names the reference does not have and
whose split QuestFrameItems_Update could not reproduce. -->
<Button name="QuestRewardItem1" inherits="QuestRewardItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestRewardItem2" inherits="QuestRewardItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestRewardItem3" inherits="QuestRewardItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestRewardItem4" inherits="QuestRewardItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestRewardItem5" inherits="QuestRewardItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestRewardItem6" inherits="QuestRewardItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestRewardItem7" inherits="QuestRewardItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestRewardItem8" inherits="QuestRewardItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestRewardItem9" inherits="QuestRewardItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Button name="QuestRewardItem10" inherits="QuestRewardItemTemplate"><Anchors><Anchor point="TOPLEFT"/></Anchors></Button>
<Frame name="QuestRewardMoneyFrame" inherits="MoneyFrameTemplate"><Anchors><Anchor point="LEFT" relativeTo="QuestRewardItemReceiveText" relativePoint="RIGHT"><Offset><AbsDimension x="15" y="0"/></Offset></Anchor></Anchors><Scripts><OnLoad>QuestMoneyFrame_OnLoad()</OnLoad></Scripts></Frame>
<!-- The choice-selection glow (moved onto the clicked choice
row; ref QuestRewardItemHighlight, QuestFrame.xml:254-278
VERBATIM: the 256x64 UI-QuestItemHighlight ADD card, one
frame level above the rows). -->
<Frame name="QuestRewardItemHighlight" hidden="true" frameLevel="3">
<Size><AbsDimension x="256" y="64"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<Layers><Layer level="BACKGROUND">
<Texture file="Interface\QuestFrame\UI-QuestItemHighlight" alphaMode="ADD">
<Size><AbsDimension x="256" y="64"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
</Texture>
</Layer></Layers>
</Frame>
</Frames>
</Frame>
</ScrollChild>
</ScrollFrame>
</Frames>
<Scripts><OnShow>BenillaQuestRewardPanel_OnShow()</OnShow></Scripts>
</Frame>
</Frames>
<Scripts>
<OnLoad>QuestFrame_OnLoad()</OnLoad>
<OnEvent>QuestFrame_OnEvent(event)</OnEvent>
<OnUpdate>BenillaQuestFrame_OnUpdate()</OnUpdate>
<!-- OnShow/OnHide, quoted from the real QuestFrame.lua QuestFrame_OnShow/OnHide (kits
igQuestListOpen l.285 / igQuestListClose l.294, wired via QuestFrame.xml l.1012/1015).
Same pair as gossip — the questgiver and gossip panels are the same "list" surface.
Load-bearing: CloseQuest() on hide (the real QuestFrame_OnHide) ends the giver session
whenever the frame hides, INCLUDING when another NPC window displaces it at the UIPanel
slot (SetLeftFrame → oldFrame:Hide() → OnHide); without it the QuestGiver resource
stayed set after a vendor/gossip window replaced the panel (decision 0095). No-packet
client-side clear; idempotent with the close/decline buttons, which also call it.
Never fires at load (hidden="true"). Also hides all four child panels (ref l.289-292)
so a re-open always starts from a clean panel state. -->
<OnShow>PlaySound("igQuestListOpen")</OnShow>
<OnHide>
getglobal("QuestFrameGreetingPanel"):Hide()
getglobal("QuestFrameDetailPanel"):Hide()
getglobal("QuestFrameProgressPanel"):Hide()
getglobal("QuestFrameRewardPanel"):Hide()
CloseQuest()
PlaySound("igQuestListClose")
</OnHide>
</Scripts>
</Frame>
</Ui>