Files
benilla-octo/crates/benilla-app/assets/ui/QuestTimerFrame.xml
T
SamandClaude Fable 5 7b4bd8af4a quest timers, honest interiors, and snow rebuilt from the bytes
- timed quests get their countdown — a Quest Timers box under the minimap and a Time Remaining line in the quest log, kept against the server's own clock
- interiors go honest: footsteps stop crunching the terrain's snow through a building's floor, and tavern voices are authored dry, so the fake cave echo is gone
- snow is the reference's again — point-sprite flakes at the era's pixel size, and a spawn slab that leans into your run so a runner no longer outruns the field
- Stormwind stops drawing Elwynn through its walls, a looted quest crate's lid settles, cold zones puff visible breath, and the collect-quest toast fires only on a real advance

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

256 lines
15 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 replaced with select("#", ...)/select(i, ...) —
LUA_COMPAT_VARARG isn't shipped (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: it is the ref's raise-on-click behaviour for movable
dialogs, which this engine has no verb for and this frame never uses (it is not movable
and its click opens the quest log rather than raising anything).
- 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("BenillaQuestLogFrame"))
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>