Files
benilla-octo/crates/benilla-app/assets/ui/QuestTimerFrame.xml
T
SamandClaude Fable 5 b5bc569156 the addon arc: the AddOns folder opens
- benilla loads real 1.12 addons from Interface/AddOns — TOC, XML and Lua through one loader, with per-addon saved variables, Bindings.xml and the lifecycle events addons expect
- both AddOns screens are in: the character-select list with per-addon enable state, and the ESC menu's in-game list
- the API surface is measured, not guessed — a corpus harness runs hundreds of real 1.12 addons and ranks exactly which globals, templates and verbs they demand
- the Lua dialect is the client's own, read off the binary — 5.0 semantics with the 1.12 additions, and UI source files are bytes, not UTF-8

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:18:31 +07:00

260 lines
16 KiB
XML

<Ui>
<!--
The quest-timer window — the small dialog under the minimap that counts a timed quest down
(decision 1150, B234). Geometry, art, layout and behaviour are QUOTED from the real 1.12
QuestTimerFrame (extracted Interface\FrameXML\QuestTimerFrame.xml + .lua): a 160x72
UI-DialogBox backdrop with the 256x64 header art and the QUEST_TIMERS title on it, twenty
140x16 rows stacked flush from TOP -30, and a height of 45 + 16*n for n live timers.
It is a LIST OF TIMES, not a bar. The report this closes guessed a mirror-timer-style bar,
and the reference file settles it: one `SecondsToTime` line per timed quest ("14 Mins 20
Secs"), clickable to open the quest log at that quest, hovering to name it. The mirror
timers (MirrorTimer.xml, 0874) are a different mechanism entirely — those are server-pushed
values with a rate to integrate, this reads the quest log's own deadlines.
The feed is the engine's GetQuestTimers()/GetQuestIndexForTimer(), which subtract the
server's wall clock (SMSG_QUERY_TIME_RESPONSE) from each quest-log slot's absolute deadline
per call — the reference's own division of labour, and why the OnUpdate below can repaint a
ticking number without the quest-log snapshot changing under it. No timed quests → the
bindings return no values at all → the frame hides, which is the ref's own signal.
Departures, each named:
- BARE frame names (no `Benilla` prefix), like DurabilityFrame and GroupLootFrame1 before
it (decision 0591 §3): UIParent_ManageFramePositions resolves its stack through a literal
getglobal(name), and the ref's own Lua below reaches its rows by
getglobal("QuestTimer"..i.."Text"). Prefixing would silently unseat the frame and break
the row lookup at once.
- Lua 5.0 `arg`/`arg.n` varargs written as select("#", ...)/select(i, ...), the 5.1-native
spelling and this tree's house style (TalentFrame.xml / WorldMapFrame.xml's precedent).
- The ref's XML anchor (TOPRIGHT of MinimapCluster +10,0) is transcribed and, like every
managed HUD frame, DEAD ON ARRIVAL: UIParent_ManageFramePositions re-seats it at
-CONTAINER_OFFSET_X on every pass. Kept verbatim anyway so the file reads as the ref's.
- `toplevel="true"` is dropped, and the reason given here was half wrong: the raise's real
triggers are **Show**, drag-start and mouse-down, not "raise-on-click for movable dialogs"
(wow-re `ui/scratch/toplevel-raise.md`). The verb now exists (`script::object::toplevel`),
so the standing reason is only the second one — this frame is a managed HUD strip that
UIParent_ManageFramePositions re-seats and that overlaps nothing in its own stratum, so
the occlusion gate would decline every raise anyway. Re-adding it is one word if that
stops being true.
- QuestTimerFrame_OnShow/OnHide call the manage pass, as the ref does — that is what lets
the durability guy and the quest tracker drop below this window when a timer appears.
- The ref's own commented-out QuestTimerFrame_UpdatePosition (its pre-manage-pass
positioning) is left out rather than transcribed as dead code.
NOT a departure, though it looks like one: the row buttons are laid out flush (0,0 between
them) while each is 16 tall and the frame grows by exactly 16 per timer. That is the ref's
own arithmetic (45 + 16*n against a 72-tall empty frame), not a rounding of ours.
-->
<Script><![CDATA[
-- ref GlobalStrings.lua:3241 — the window's title, under its real key so the ref's own
-- `text="QUEST_TIMERS"` below resolves through the loader's global-string lookup verbatim
-- (loader/mod.rs `resolve_text`). Declared here because we load no GlobalStrings.lua;
-- guarded, in case a later file ships one.
QUEST_TIMERS = QUEST_TIMERS or "Quest Timers"
-- ref QuestLogFrame.lua:2. Declared here too because the ref's hide loop below runs to it
-- and this file may load before the quest log's own copy.
MAX_QUESTS = MAX_QUESTS or 20
-- ref QuestTimerFrame_OnLoad (QuestTimerFrame.lua:1-6).
function QuestTimerFrame_OnLoad(frame)
frame:RegisterEvent("QUEST_LOG_UPDATE")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
QuestTimerFrameNumTimers = 0
QuestTimerFrameUpdating = nil
end
-- ref QuestTimerFrame_OnEvent (:8-14). The `updating` latch is the ref's own re-entrancy
-- guard: QuestTimerFrame_Update's Show() fires OnShow → the manage pass, and a
-- QUEST_LOG_UPDATE arriving inside that would re-enter this.
function QuestTimerFrame_OnEvent()
if not QuestTimerFrameUpdating then
QuestTimerFrame_Update(GetQuestTimers())
end
end
-- ref QuestTimerFrame_Update (:16-32), with select() for the 5.0 vararg table.
function QuestTimerFrame_Update(...)
QuestTimerFrameUpdating = 1
local n = select("#", ...)
for i = 1, n do
-- `select(i, ...)` expands to EVERY value from i on, and in a call's last argument
-- position they all get passed — so an inlined `SecondsToTime(select(i, ...))`
-- hands timer i+1 to SecondsToTime's `noSeconds` and silently drops the seconds
-- off every row but the last ("14 Mins" instead of "14 Mins 20 Secs"). The local
-- takes the first value only. The ref's `arg[i]` had no such hazard; this is the
-- 5.0→5.1 vararg swap's own trap, caught by the test that expected the seconds.
local seconds = select(i, ...)
getglobal("QuestTimer" .. i .. "Text"):SetText(SecondsToTime(seconds))
getglobal("QuestTimer" .. i):Show()
end
for i = n + 1, MAX_QUESTS do
getglobal("QuestTimer" .. i):Hide()
end
QuestTimerFrameNumTimers = n
local frame = getglobal("QuestTimerFrame")
if n > 0 then
frame:SetHeight(45 + (16 * n))
frame:Show()
else
frame:Hide()
end
QuestTimerFrameUpdating = nil
end
-- ref QuestTimerFrame_OnUpdate (:34-38): re-read every frame while any timer is live —
-- that, not an event, is what makes the number tick. Costs nothing while hidden (the
-- engine's OnUpdate gate is effective-visibility) and nothing while empty (numTimers 0).
function QuestTimerFrame_OnUpdate()
if QuestTimerFrameNumTimers > 0 then
QuestTimerFrame_Update(GetQuestTimers())
end
end
-- ref QuestTimerButton_OnClick (:40-44): open the log at that quest.
function QuestTimerButton_OnClick(id)
local index = GetQuestIndexForTimer(id)
if not index then
return
end
ShowUIPanel(getglobal("QuestLogFrame"))
SelectQuestLogEntry(index)
BenillaQuestLogFrame_Update()
end
]]></Script>
<Button name="QuestTimerTemplate" virtual="true">
<Size>
<AbsDimension x="140" y="16"/>
</Size>
<Layers>
<Layer level="BACKGROUND">
<FontString name="$parentText" inherits="GameFontHighlightSmall">
<Anchors>
<Anchor point="TOP"/>
</Anchors>
</FontString>
</Layer>
</Layers>
<Scripts>
<!-- ref QuestTimerFrame.xml:18-27. The tooltip names the quest the row belongs to;
GetQuestLogTitle's FIRST return only (the rest of its 6-tuple is level/tag/state). -->
<OnEnter>
local index = GetQuestIndexForTimer(self:GetID())
if index then
GameTooltip:SetOwner(self)
GameTooltip:SetText(GetQuestLogTitle(index), 1.0, 1.0, 1.0)
GameTooltip:Show()
end
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
<OnClick>QuestTimerButton_OnClick(self:GetID())</OnClick>
</Scripts>
</Button>
<Frame name="QuestTimerFrame" enableMouse="true" frameStrata="BACKGROUND" hidden="true">
<Size>
<AbsDimension x="160" y="72"/>
</Size>
<Anchors>
<Anchor point="TOPRIGHT" relativeTo="MinimapCluster" relativePoint="BOTTOMRIGHT">
<Offset>
<AbsDimension x="10" y="0"/>
</Offset>
</Anchor>
</Anchors>
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
<BackgroundInsets><AbsInset left="11" right="12" top="12" bottom="11"/></BackgroundInsets>
<TileSize><AbsValue val="32"/></TileSize>
<EdgeSize><AbsValue val="32"/></EdgeSize>
</Backdrop>
<Layers>
<Layer level="ARTWORK">
<Texture name="QuestTimerHeader" file="Interface\DialogFrame\UI-DialogBox-Header">
<Size><AbsDimension x="256" y="64"/></Size>
<Anchors>
<Anchor point="TOP"><Offset><AbsDimension x="1" y="12"/></Offset></Anchor>
</Anchors>
</Texture>
<FontString inherits="GameFontNormal" text="QUEST_TIMERS">
<Anchors>
<Anchor point="TOP" relativeTo="QuestTimerHeader"><Offset><AbsDimension x="0" y="-13"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The ref's twenty rows (MAX_QUESTS), first at TOP -30 and the rest flush beneath it.
Named/id'd 1..20 because both the ref's row lookup and its timer→quest mapping are
by ordinal. -->
<Button name="QuestTimer1" inherits="QuestTimerTemplate" hidden="true" id="1">
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="-30"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer2" inherits="QuestTimerTemplate" hidden="true" id="2">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer1" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer3" inherits="QuestTimerTemplate" hidden="true" id="3">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer2" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer4" inherits="QuestTimerTemplate" hidden="true" id="4">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer3" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer5" inherits="QuestTimerTemplate" hidden="true" id="5">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer4" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer6" inherits="QuestTimerTemplate" hidden="true" id="6">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer5" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer7" inherits="QuestTimerTemplate" hidden="true" id="7">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer6" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer8" inherits="QuestTimerTemplate" hidden="true" id="8">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer7" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer9" inherits="QuestTimerTemplate" hidden="true" id="9">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer8" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer10" inherits="QuestTimerTemplate" hidden="true" id="10">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer9" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer11" inherits="QuestTimerTemplate" hidden="true" id="11">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer10" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer12" inherits="QuestTimerTemplate" hidden="true" id="12">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer11" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer13" inherits="QuestTimerTemplate" hidden="true" id="13">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer12" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer14" inherits="QuestTimerTemplate" hidden="true" id="14">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer13" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer15" inherits="QuestTimerTemplate" hidden="true" id="15">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer14" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer16" inherits="QuestTimerTemplate" hidden="true" id="16">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer15" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer17" inherits="QuestTimerTemplate" hidden="true" id="17">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer16" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer18" inherits="QuestTimerTemplate" hidden="true" id="18">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer17" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer19" inherits="QuestTimerTemplate" hidden="true" id="19">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer18" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="QuestTimer20" inherits="QuestTimerTemplate" hidden="true" id="20">
<Anchors><Anchor point="TOP" relativeTo="QuestTimer19" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Button>
</Frames>
<Scripts>
<OnLoad>QuestTimerFrame_OnLoad(self)</OnLoad>
<OnUpdate>QuestTimerFrame_OnUpdate()</OnUpdate>
<OnEvent>QuestTimerFrame_OnEvent()</OnEvent>
<!-- ref :46-52 — the stack under the minimap re-flows when this window appears or goes. -->
<OnShow>UIParent_ManageFramePositions()</OnShow>
<OnHide>UIParent_ManageFramePositions()</OnHide>
</Scripts>
</Frame>
</Ui>