the help window is the reference's own, and a ding carries nine gains
- the help window is the reference's own `HelpFrame.xml`, and ours was less than half its size, so this is mostly arrival: the category list, the per-category GM and harassment pages, the stuck page, the bug report, the PvP policy line; four things we had built go with the swap — one-click filing from Home, the Home unstick button, the downed-queue refusal message, and ticket abandonment, a capability loss rather than a look change, since `AbandonTicket` exists in neither the binding table nor the 1.12 FrameXML corpus and any return of it goes on top of the stock window; `GMSURVEY_DISPLAY` arrives with no producer, a real 1.12 event (`SignalEvent` `0x5e797b`) whose whole UI is a LoadOnDemand addon not on our chain; and the stock file needs two orderings ours did not, the micro-button row before its GM status ask and the buff frame before its repoll timer (1889, superseding 1687)
- the cast bar, the action-button regions and the font registry are the reference's own files too: our `CastingBar.xml` was `CastingBarFrame.xml` under another name, with the literal "Failed" where the reference reads the `FAILED` GlobalString (1886); `ActionButtonTemplate.xml` seats above our `ActionBar.xml` because the later-registered template wins, and it declares zero scripts, so the regions migrate while the handlers stay ours until `ActionButton.lua`'s verbs do (1887); `Fonts.xml` is manifest entry 0, all 51 fonts match by name, and its one real difference, `ITEM_QUALITY_COLORS`, moves to UIParent where the reference declares it (1888); the harnesses were the real cost — those reading our files off disk could not name a chain file and now read through the manifest's own rule — and the event-producer gate, having cried wolf at the cast bar's `SPELLCAST_INTERRUPTED` fired through a variable, now matches the fired tuple's shape
- of the 189 events benilla fires, three are not in the client — `BAG_UPDATE_DELAYED` (a Classic Era name), `LOOT_UPDATE` and `UPDATE_LOOT_ROLL` (invented here, no listener anywhere) — found against the reference binary's own string table, and a gate now fails any fire of a name the client cannot dispatch unless it wears a `BENILLA_` prefix (1883); two payloads the reference gives that we did not: `UNIT_HAPPINESS` carries its unit token, without which no `UNIT_*` consumer reads it, and `CHARACTER_POINTS_CHANGED` carries the point deltas `ChatFrame.lua` compares unguarded, so nil raised (1884); and `PLAYER_LEVEL_UP` carries all nine — level, health and mana gain, talent points (not on the wire: the client derives `(newLevel >= 10) ? 1 : 0` at `0x5e407c`) and the five stat gains — matched to the ding by level, a demotion's absent gains sent as zeros since a nil raises where a zero reads as no line
- the chain gap report could not run — one of its six probes had no player seated where a stock addon formats `UnitName("player")` at load, the other five each carrying an identical copy-pasted seed — and the repaired instrument prices each window's cost: 13 stock files load clean with nothing missing (the action bars, the cast bar, the fonts, the help, friends, game-menu and sound-options windows among them) and the near ones name their missing verb, with "loads clean" still not "swapped" (1885); and `UpdateNameplates`, recorded as the missing engine verb blocking the UIParent keystone, is FrameXML — defined in `UIOptionsFrame.lua`, absent from the binding table, its whole body four nameplate verbs that are already ours — so it is twelve lines transcribed into our options frame, the reference's own Show-then-Hide no-op included (1890 corrects 1878)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1482,9 +1482,12 @@
|
||||
does (decision 1636).
|
||||
-->
|
||||
<!-- **The reference's own two-level split, which ours used to conflate into one template.**
|
||||
`ActionButtonTemplate` (ref ActionButtonTemplate.xml:3) is REGIONS ONLY and carries no
|
||||
scripts at all; `ActionBarButtonTemplate` (ref ActionBarFrame.xml:4) inherits it and adds
|
||||
the handler set. Addons rely on both halves being separately inheritable: `zBar.xml:7` is
|
||||
`ActionButtonTemplate` is REGIONS ONLY and carries no scripts at all — and since 1887 it
|
||||
is the reference's own, off the chain (`Interface\FrameXML\ActionButtonTemplate.xml`,
|
||||
seated immediately above this file in benilla.toc, which the order REQUIRES: a template is
|
||||
registered by name and the later line wins). `ActionBarButtonTemplate` below is the other
|
||||
half (ref ActionBarFrame.xml:4); it inherits that one and adds the handler set, and stays
|
||||
ours until `ActionButton.lua`'s verbs migrate. Addons rely on both halves being separately inheritable: `zBar.xml:7` is
|
||||
`<CheckButton name="zBarButtonTemplate" inherits="ActionBarButtonTemplate">` and then
|
||||
wires its OWN OnLoad, and its per-button code reads
|
||||
`getglobal(button:GetName().."NormalTexture")` — a derived name it only gets by inheriting
|
||||
@@ -1493,66 +1496,6 @@
|
||||
published they got a bare frame, no art, no regions, and no error (1203's silent shape).
|
||||
`BenillaActionButtonTemplate` survives below as a pure alias so every one of our own
|
||||
`inherits=` sites is untouched. -->
|
||||
<CheckButton name="ActionButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="36" y="36"/></Size>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentIcon"/>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="$parentFlash" file="Interface\Buttons\UI-QuickslotRed" hidden="true"/>
|
||||
<FontString name="$parentHotKey" inherits="NumberFontNormalSmallGray" justifyH="RIGHT">
|
||||
<Size><AbsDimension x="36" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="-2" y="-2"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parentCount" inherits="NumberFontNormal" justifyH="RIGHT">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-2" y="2"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<!-- ref ActionButtonTemplate.xml's `$parentName` — the macro-name line along the
|
||||
button's bottom edge. `BenillaActionButton_Update` writes it from
|
||||
`GetActionText` on every update (a MACRO slot's macro name, nil — cleared —
|
||||
for any other kind); it is also part of the template's published contract, so
|
||||
an addon indexing `<button>Name` gets the region. -->
|
||||
<FontString name="$parentName" inherits="GameFontHighlightSmallOutline">
|
||||
<Size><AbsDimension x="36" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="2"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="$parentBorder" file="Interface\Buttons\UI-ActionButton-Border" hidden="true" alphaMode="ADD">
|
||||
<Size><AbsDimension x="62" y="62"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Cooldown name="$parentCooldown" hidden="true">
|
||||
<Size><AbsDimension x="36" y="36"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Cooldown>
|
||||
</Frames>
|
||||
<NormalTexture name="$parentNormalTexture" file="Interface\Buttons\UI-Quickslot2">
|
||||
<Size><AbsDimension x="66" y="66"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</NormalTexture>
|
||||
<PushedTexture file="Interface\Buttons\UI-Quickslot-Depress"/>
|
||||
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
|
||||
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
|
||||
</CheckButton>
|
||||
|
||||
<!-- The bar half: the reference's `ActionBarButtonTemplate`, regions inherited, handlers added. -->
|
||||
<CheckButton name="ActionBarButtonTemplate" inherits="ActionButtonTemplate" virtual="true">
|
||||
<Scripts>
|
||||
<OnEvent>ActionButton_OnEvent()</OnEvent>
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
<Ui>
|
||||
<!--
|
||||
The cast bar — benilla's own implementation of the 1.12 CastingBarFrame contract. The
|
||||
extracted reference (Interface\FrameXML\CastingBarFrame.xml/.lua, mpqx; decision 0137
|
||||
phase 1) is the SPECIFICATION: geometry, textures, colors, and every timing constant are
|
||||
read off it, and the observable lifecycle below is the one it defines. The code is ours.
|
||||
Driven by the SPELLCAST_* events crate::ui_cast fires off the wire (START name,ms / STOP /
|
||||
FAILED / INTERRUPTED / DELAYED / CHANNEL_START ms,name / CHANNEL_UPDATE ms / CHANNEL_STOP).
|
||||
|
||||
The reference's globals (CastingBarText/Spark/Flash) are named regions, so they publish as
|
||||
globals here exactly as they do there; `this` is the firing frame (RF-0025), and every
|
||||
handler below reads it once into a named local rather than threading the global through a
|
||||
dozen method calls. Deliberate departures from the specification's own .lua: its dead code
|
||||
(the `if not this:IsVisible() then this:Hide() end` no-op guard at the head of the STOP
|
||||
branch; the two write-only `this.mode` stores nothing reads), and the flash/fade steps
|
||||
normalized to the reference tick rate (the CASTING_BAR_REF_TICK comment below —
|
||||
decision 0454).
|
||||
|
||||
SPELLCAST_DELAYED is live: SMSG_SPELL_DELAYED (pushback on taking a hit) is decoded and fed,
|
||||
so a hit slides the bar's window out instead of letting it finish early (decision 0256).
|
||||
|
||||
POSITION: the XML anchor y=55 below is only the pre-manage default, exactly as in the
|
||||
reference — UIParent.lua overrides it on every bottom-stack layout pass
|
||||
(UIPARENT_MANAGED_FRAME_POSITIONS["CastingBarFrame"]: baseY 60, +40 per either bottom
|
||||
multibar, +40 per pet/stance bar, +9 per rep bar). Our transcription of that mechanism
|
||||
lives in UIParent.xml (decision 0272); with the always-on multibars the bar sits at
|
||||
y=100, and y=140 while the stance bar shows.
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
CASTING_BAR_ALPHA_STEP = 0.05
|
||||
CASTING_BAR_FLASH_STEP = 0.2
|
||||
CASTING_BAR_HOLD_TIME = 1
|
||||
|
||||
-- The ref applies the two steps PER OnUpdate TICK (no arg1 scaling) — the flash/fade
|
||||
-- tail is frame-rate dependent by the reference's own design: ~25 ticks from hold-expiry
|
||||
-- to hidden, i.e. 0.83 s at the reference bottle's configured cap (WoW/WTF/Config.wtf
|
||||
-- `SET maxfps "30"`), 0.42 s at an era-typical 60 fps, a blink at 120. We render fast,
|
||||
-- so the step lines below are normalized to the reference tick rate — the bar's
|
||||
-- hold+fade then matches the reference window at any render rate (decision 0454).
|
||||
CASTING_BAR_REF_TICK = 30
|
||||
|
||||
-- The 1.12 GlobalStrings values (no GlobalStrings.lua is loaded yet).
|
||||
FAILED = FAILED or "Failed"
|
||||
INTERRUPTED = INTERRUPTED or "Interrupted"
|
||||
|
||||
-- THE BAR'S WHOLE VOCABULARY, one entry per wire event, each taking the frame. OnLoad
|
||||
-- registers this table's keys and OnEvent looks the incoming event up in it, so the set
|
||||
-- the bar LISTENS for and the set it can ANSWER are the same set by construction — there
|
||||
-- is no second list to fall out of step with. (The reference spells both halves out: eight
|
||||
-- RegisterEvent calls, then a sixty-line if-chain testing the same eight strings back.)
|
||||
local castingBarEvent = {}
|
||||
|
||||
-- Seat the fill at `value` and ride the spark on its leading edge. The bar is 195px wide,
|
||||
-- so a fraction of `span` scales straight to a pixel offset from the frame's LEFT. A cast
|
||||
-- and a channel differ only in which value and which span they hand in.
|
||||
local function castingBarTrack(bar, value, span)
|
||||
bar:SetValue(value)
|
||||
CastingBarFlash:Hide()
|
||||
local sparkPosition = ((value - bar.startTime) / span) * 195
|
||||
-- Pushback slides startTime PAST the wall clock, so a cast really can sit before its
|
||||
-- own window's start for a frame or two. (A channel cannot — its value counts down
|
||||
-- from the far end and stops at startTime — so here the clamp is dead weight rather
|
||||
-- than a change of behaviour.)
|
||||
if sparkPosition < 0 then
|
||||
sparkPosition = 0
|
||||
end
|
||||
CastingBarSpark:SetPoint("CENTER", CastingBarFrame, "LEFT", sparkPosition, 2)
|
||||
end
|
||||
|
||||
-- A cast opens: orange, a full window anchored on the clock, the spark at the left edge.
|
||||
castingBarEvent.SPELLCAST_START = function(bar)
|
||||
bar:SetStatusBarColor(1.0, 0.7, 0.0)
|
||||
CastingBarSpark:Show()
|
||||
bar.startTime = GetTime()
|
||||
bar.maxValue = bar.startTime + (arg2 / 1000)
|
||||
bar:SetMinMaxValues(bar.startTime, bar.maxValue)
|
||||
bar:SetValue(bar.startTime)
|
||||
CastingBarText:SetText(arg1)
|
||||
bar:SetAlpha(1.0)
|
||||
bar.holdTime = 0
|
||||
bar.casting = 1
|
||||
bar.fadeOut = nil
|
||||
bar:Show()
|
||||
end
|
||||
|
||||
-- A channel opens. Its args are START's the other way round — milliseconds first, then the
|
||||
-- name — and its fill DRAINS, so the bar is seated at the far end and walks back.
|
||||
castingBarEvent.SPELLCAST_CHANNEL_START = function(bar)
|
||||
bar:SetStatusBarColor(1.0, 0.7, 0.0)
|
||||
CastingBarSpark:Show()
|
||||
bar.maxValue = 1
|
||||
bar.startTime = GetTime()
|
||||
bar.endTime = bar.startTime + (arg1 / 1000)
|
||||
bar.duration = arg1 / 1000
|
||||
bar:SetMinMaxValues(bar.startTime, bar.endTime)
|
||||
bar:SetValue(bar.endTime)
|
||||
CastingBarText:SetText(arg2)
|
||||
bar:SetAlpha(1.0)
|
||||
bar.holdTime = 0
|
||||
bar.casting = nil
|
||||
bar.channeling = 1
|
||||
bar.fadeOut = nil
|
||||
bar:Show()
|
||||
end
|
||||
|
||||
-- The server's mid-channel correction: the window keeps its LENGTH and is re-anchored to
|
||||
-- end arg1 ms from now, so a pushback eats into what is left rather than extending it.
|
||||
castingBarEvent.SPELLCAST_CHANNEL_UPDATE = function(bar)
|
||||
if not bar:IsShown() then
|
||||
return
|
||||
end
|
||||
local span = bar.endTime - bar.startTime
|
||||
bar.endTime = GetTime() + (arg1 / 1000)
|
||||
bar.startTime = bar.endTime - span
|
||||
bar:SetMinMaxValues(bar.startTime, bar.endTime)
|
||||
end
|
||||
|
||||
-- Pushback on taking a hit (SMSG_SPELL_DELAYED, decision 0256): BOTH ends of the window
|
||||
-- slide out by arg1 ms, so the cast has that much longer to run and the spark jumps back.
|
||||
-- A hit never cancels the bar.
|
||||
castingBarEvent.SPELLCAST_DELAYED = function(bar)
|
||||
if not bar:IsShown() then
|
||||
return
|
||||
end
|
||||
local pushback = arg1 / 1000
|
||||
bar.startTime = bar.startTime + pushback
|
||||
bar.maxValue = bar.maxValue + pushback
|
||||
bar:SetMinMaxValues(bar.startTime, bar.maxValue)
|
||||
end
|
||||
|
||||
-- The cast landed, or a channel ran its course: green, full, and the completion bloom
|
||||
-- armed at alpha 0 for OnUpdate to ramp. `flag` names the one field the event clears,
|
||||
-- which is the only thing that separates the two of them.
|
||||
local function castingBarFinished(bar, flag)
|
||||
if not bar:IsShown() then
|
||||
return
|
||||
end
|
||||
bar:SetValue(bar.maxValue)
|
||||
bar:SetStatusBarColor(0.0, 1.0, 0.0)
|
||||
CastingBarSpark:Hide()
|
||||
CastingBarFlash:SetAlpha(0.0)
|
||||
CastingBarFlash:Show()
|
||||
bar[flag] = nil
|
||||
bar.flash = 1
|
||||
bar.fadeOut = 1
|
||||
end
|
||||
castingBarEvent.SPELLCAST_STOP = function(bar) castingBarFinished(bar, "casting") end
|
||||
castingBarEvent.SPELLCAST_CHANNEL_STOP = function(bar) castingBarFinished(bar, "channeling") end
|
||||
|
||||
-- The cast did NOT land: red, the reason where the spell's name was, and held opaque for
|
||||
-- CASTING_BAR_HOLD_TIME before the fade starts — no completion bloom. A channel is never
|
||||
-- ended this way (the server closes one with CHANNEL_STOP), so a live channel ignores both.
|
||||
local function castingBarAborted(bar, reason)
|
||||
if not bar:IsShown() or bar.channeling then
|
||||
return
|
||||
end
|
||||
bar:SetValue(bar.maxValue)
|
||||
bar:SetStatusBarColor(1.0, 0.0, 0.0)
|
||||
CastingBarSpark:Hide()
|
||||
CastingBarText:SetText(reason)
|
||||
bar.casting = nil
|
||||
bar.fadeOut = 1
|
||||
bar.holdTime = GetTime() + CASTING_BAR_HOLD_TIME
|
||||
end
|
||||
castingBarEvent.SPELLCAST_FAILED = function(bar) castingBarAborted(bar, FAILED) end
|
||||
castingBarEvent.SPELLCAST_INTERRUPTED = function(bar) castingBarAborted(bar, INTERRUPTED) end
|
||||
|
||||
function CastingBarFrame_OnLoad()
|
||||
local bar = this
|
||||
for spellcast in pairs(castingBarEvent) do
|
||||
bar:RegisterEvent(spellcast)
|
||||
end
|
||||
bar.casting = nil
|
||||
bar.holdTime = 0
|
||||
-- The alias global the reference publishes at the end of its own OnLoad
|
||||
-- (`CastingBarFrame.lua:16`). `CastingBarFrame` IS the StatusBar in 1.12 — there is no
|
||||
-- separate child frame — so the name an addon reaches for has to resolve to the frame
|
||||
-- itself.
|
||||
--
|
||||
-- 11 corpus addons index `CastingBarFrameStatusBar`, and it was the largest single
|
||||
-- name in the whole session-start `attempt to index global` row. It went missing here
|
||||
-- once and nothing warned: a line dropped from INSIDE a function is invisible to every
|
||||
-- instrument we have. `framexml-transcription-diff.py` compares elements and handlers,
|
||||
-- not function bodies, and would not even pair these two files (ours is CastingBar.xml,
|
||||
-- the reference's is CastingBarFrame.xml).
|
||||
CastingBarFrameStatusBar = bar
|
||||
bar:Hide()
|
||||
end
|
||||
|
||||
function CastingBarFrame_OnEvent()
|
||||
local handle = castingBarEvent[event]
|
||||
if handle then
|
||||
handle(this)
|
||||
end
|
||||
end
|
||||
|
||||
function CastingBarFrame_OnUpdate()
|
||||
local bar = this
|
||||
if bar.casting then
|
||||
-- The fill IS the wall clock, pinned at the window's end once the cast is spent.
|
||||
local now = GetTime()
|
||||
if now > bar.maxValue then
|
||||
now = bar.maxValue
|
||||
end
|
||||
castingBarTrack(bar, now, bar.maxValue - bar.startTime)
|
||||
elseif bar.channeling then
|
||||
local now = GetTime()
|
||||
if now >= bar.endTime then
|
||||
bar.channeling = nil
|
||||
bar.fadeOut = 1
|
||||
return
|
||||
end
|
||||
-- A channel drains: the value is the window's start plus what is LEFT of it, so
|
||||
-- the fill empties from full instead of filling from empty.
|
||||
castingBarTrack(bar, bar.startTime + (bar.endTime - now), bar.endTime - bar.startTime)
|
||||
elseif GetTime() < bar.holdTime then
|
||||
return
|
||||
elseif bar.flash then
|
||||
local alpha = CastingBarFlash:GetAlpha() + CASTING_BAR_FLASH_STEP * (arg1 * CASTING_BAR_REF_TICK)
|
||||
if alpha < 1 then
|
||||
CastingBarFlash:SetAlpha(alpha)
|
||||
else
|
||||
CastingBarFlash:SetAlpha(1.0)
|
||||
bar.flash = nil
|
||||
end
|
||||
elseif bar.fadeOut then
|
||||
local alpha = bar:GetAlpha() - CASTING_BAR_ALPHA_STEP * (arg1 * CASTING_BAR_REF_TICK)
|
||||
if alpha > 0 then
|
||||
bar:SetAlpha(alpha)
|
||||
else
|
||||
bar.fadeOut = nil
|
||||
bar:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<StatusBar name="CastingBarFrame" drawLayer="BORDER" toplevel="true" parent="UIParent" hidden="true">
|
||||
<Size><AbsDimension x="195" y="13"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM">
|
||||
<Offset><AbsDimension x="0" y="55"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture setAllPoints="true">
|
||||
<Color r="0" g="0" b="0" a="0.5"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="CastingBarText" inherits="GameFontHighlight">
|
||||
<Size><AbsDimension x="185" y="16"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP">
|
||||
<Offset><AbsDimension x="0" y="5"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<Texture name="CastingBarBorder" file="Interface\CastingBar\UI-CastingBar-Border">
|
||||
<Size><AbsDimension x="256" y="64"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP">
|
||||
<Offset><AbsDimension x="0" y="28"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="CastingBarSpark" file="Interface\CastingBar\UI-CastingBar-Spark" alphaMode="ADD">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture name="CastingBarFlash" file="Interface\CastingBar\UI-CastingBar-Flash" alphaMode="ADD">
|
||||
<Size><AbsDimension x="256" y="64"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP">
|
||||
<Offset><AbsDimension x="0" y="28"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>CastingBarFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>CastingBarFrame_OnEvent()</OnEvent>
|
||||
<OnUpdate>CastingBarFrame_OnUpdate()</OnUpdate>
|
||||
</Scripts>
|
||||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||||
<BarColor r="1.0" g="0.7" b="0.0"/>
|
||||
</StatusBar>
|
||||
</Ui>
|
||||
@@ -1,397 +0,0 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<!--
|
||||
Benilla's named virtual Font objects: the COMPLETE set from the real 1.12.1 FrameXML
|
||||
`Fonts.xml`, quoted verbatim — names, TTF paths, heights, colors, outlines, and
|
||||
(critically) which families carry a `<Shadow>` and which don't. The reference copy is
|
||||
re-extractable from the client at any time with the `mpqx` bin (crates/benilla-mpq,
|
||||
virtual path `Interface\FrameXML\Fonts.xml`); this is our own authored file (MIT/Apache),
|
||||
only the TTFs themselves stream from the client at render.
|
||||
|
||||
Being COMPLETE is the point: the first partial transcription lacked `QuestFontNormalSmall`,
|
||||
a window substituted `GameFontNormalSmall` for it, and the parchment objectives rendered
|
||||
10px gold WITH the MasterFont drop shadow instead of 12px warm-brown WITHOUT one — a look
|
||||
bug born entirely from a missing font object, invisible to every engine test. Substituting
|
||||
"a similar font" is never safe (shadow/height/color all ride the name), so every name the
|
||||
real FrameXML defines exists here.
|
||||
|
||||
A FontString picks a font object up with `inherits="GameFontNormal"`; `SetFontObject`
|
||||
re-points it. Each object resolves {font path, height, color, outline, shadow} through its
|
||||
`inherits` chain. NOTE the load-bearing asymmetry, straight from the ref: the `GameFont*`
|
||||
families chain through `MasterFont` (whose only content is the (1,-1) black `<Shadow>`),
|
||||
while `GameFontBlack`/`GameFontWhite`, the `NumberFont*` family (outlined instead), and the
|
||||
parchment `QuestFont*` family (except the brown-shadowed `QuestTitleFont`) do NOT — dark
|
||||
text on parchment is shadowless in the real client.
|
||||
-->
|
||||
|
||||
<!-- The shared font-color globals, QUOTED from the real Fonts.xml's own <Script> block
|
||||
(ref l.8-19) — the |cAARRGGBB escape prefixes the stat/tooltip builders concatenate, and
|
||||
the two color tables tooltip AddLine calls read. Defined here (the ref's own home, loaded
|
||||
first) so every window shares one verified set. -->
|
||||
<Script><![CDATA[
|
||||
-- ── The four font-PATH globals (ref Fonts.xml l.4-7) ──────────────────────────────────
|
||||
-- This file quoted the reference's colour block (l.8-19) and skipped the four lines above
|
||||
-- it. `STANDARD_TEXT_FONT` is the one that matters: **68 corpus addons name it**, and per
|
||||
-- 1207's rule that number is read rather than quoted — 64 of them are copies of
|
||||
-- `Dewdrop-2.0.lua` and only 4 are addon code. But Dewdrop is *the* Ace2 menu library, and
|
||||
-- it calls `button.text:SetFont(STANDARD_TEXT_FONT, height)` **unguarded** at two of its
|
||||
-- three sites (l.207 and l.496; only l.2294 writes `STANDARD_TEXT_FONT or "Fonts\\..."`),
|
||||
-- so a nil global is a failed SetFont on every menu button the Ace2 ecosystem draws.
|
||||
--
|
||||
-- The other three come with it because they are the same four-line block and cost nothing:
|
||||
-- UNIT_NAME_FONT (0 corpus addons), DAMAGE_TEXT_FONT (1), NAMEPLATE_FONT (1). All four are
|
||||
-- the same face in 1.12 — the reference assigns FRIZQT__ to every one — and that identity
|
||||
-- is the reference's, not a simplification of ours.
|
||||
STANDARD_TEXT_FONT = "Fonts\\FRIZQT__.TTF" -- ref Fonts.xml l.4
|
||||
UNIT_NAME_FONT = "Fonts\\FRIZQT__.TTF" -- l.5
|
||||
DAMAGE_TEXT_FONT = "Fonts\\FRIZQT__.TTF" -- l.6
|
||||
NAMEPLATE_FONT = "Fonts\\FRIZQT__.TTF" -- l.7
|
||||
NORMAL_FONT_COLOR_CODE = "|cffffd200" -- ref Fonts.xml l.8
|
||||
HIGHLIGHT_FONT_COLOR_CODE = "|cffffffff" -- l.9
|
||||
RED_FONT_COLOR_CODE = "|cffff2020" -- l.10
|
||||
GREEN_FONT_COLOR_CODE = "|cff20ff20" -- l.11
|
||||
GRAY_FONT_COLOR_CODE = "|cff808080" -- l.12
|
||||
-- l.13, and it was the ONE line of this block that was missing — the comments beside it
|
||||
-- even skipped from `l.12` to `l.14`. `aux-addon.lua:7` concatenates it at file scope
|
||||
-- (`LIGHTYELLOW_FONT_COLOR_CODE .. "aux: " ..`), so its absence was not a wrong colour, it
|
||||
-- was the addon dying on its own first line.
|
||||
LIGHTYELLOW_FONT_COLOR_CODE = "|cffffff9a" -- l.13
|
||||
FONT_COLOR_CODE_CLOSE = "|r" -- l.14
|
||||
NORMAL_FONT_COLOR = { r = 1.0, g = 0.82, b = 0 } -- l.15
|
||||
HIGHLIGHT_FONT_COLOR = { r = 1.0, g = 1.0, b = 1.0 } -- l.16
|
||||
GRAY_FONT_COLOR = { r = 0.5, g = 0.5, b = 0.5 } -- l.17
|
||||
GREEN_FONT_COLOR = { r = 0.1, g = 1.0, b = 0.1 } -- l.18
|
||||
RED_FONT_COLOR = { r = 1.0, g = 0.1, b = 0.1 } -- l.19
|
||||
-- Added this session (decision 0216 §8, the spellbook slice) — the ref's own l.20, the
|
||||
-- one color this file was missing: a passive spell's name text (SpellBookFrame.lua:385).
|
||||
PASSIVE_SPELL_FONT_COLOR = { r = 0.77, g = 0.64, b = 0 } -- l.20
|
||||
|
||||
-- The item-quality palette — **built from the engine verb, exactly as the reference
|
||||
-- builds it** (ref UIParent.lua l.65-72, verbatim shape; decision 1199):
|
||||
--
|
||||
-- ITEM_QUALITY_COLORS = { }
|
||||
-- for i = -1, 6 do … = GetItemQualityColor(i) end
|
||||
--
|
||||
-- It used to be seven hand-typed rows here. They were CLOSE and not exact — 0.62 where
|
||||
-- the client's byte is 0x9d/255 = 0.6157, 0.64/0.21/0.93 where Epic is
|
||||
-- 0xa3/0x35/0xee — and they carried no `.hex`, which the reference's table has and
|
||||
-- which addons splice straight into a chat link. `GetItemQualityColor` is now a real
|
||||
-- engine binding over wow-5875-re RF-0055's byte-verified table, so the rounding and
|
||||
-- the second source both go.
|
||||
--
|
||||
-- The loop starts at -1 like the reference's, and the -1 row is not a curiosity: it is the
|
||||
-- colour a loot row wears while its item template is uncached, which is the value
|
||||
-- `GetLootSlotInfo` answers there. `GetItemQualityColor`'s clamp is UNSIGNED, so -1 lands
|
||||
-- on Common/white with 7-and-up (decision 1805, correcting 1199's "reads before the
|
||||
-- array").
|
||||
ITEM_QUALITY_COLORS = { }
|
||||
for i = -1, 6 do
|
||||
ITEM_QUALITY_COLORS[i] = { }
|
||||
ITEM_QUALITY_COLORS[i].r,
|
||||
ITEM_QUALITY_COLORS[i].g,
|
||||
ITEM_QUALITY_COLORS[i].b,
|
||||
ITEM_QUALITY_COLORS[i].hex = GetItemQualityColor(i)
|
||||
end
|
||||
|
||||
-- ── RAID_CLASS_COLORS (spec: ref Fonts.xml l.43-53 — same name, same nine keys, same
|
||||
-- channel values; our own way of writing them down) ───────────────────────────────────
|
||||
--
|
||||
-- The per-class colour every raid/party addon paints names with. **22 corpus addons** reach
|
||||
-- it — nine of them through `Babble-Class-2.0`, but seven more by hand (KLHThreatMeter,
|
||||
-- CT_RaidAssist, CT_ExpenseHistory, _LazyPig, FuBar_oRA2CooldownFu, and BigWigs in two
|
||||
-- plugins) — and absent it is `bad argument #1 to 'pairs' (table expected, got nil)` at
|
||||
-- file scope. That was the whole of the harness's remaining `table expected` row: three
|
||||
-- addons, one missing table, found by asking the blocker table which addons were behind it.
|
||||
--
|
||||
-- Written as the eight distinct triples plus a loop that lifts them into the `{r=,g=,b=}`
|
||||
-- records addons index (`RAID_CLASS_COLORS["HUNTER"].r`), the same spec-plus-builder shape
|
||||
-- ITEM_QUALITY_COLORS above takes. The only thing worth SAYING about a palette is which
|
||||
-- colours are in it, and a bare triple says that with no ceremony; the record shape is the
|
||||
-- addon-facing contract and belongs in the one line that builds it.
|
||||
--
|
||||
-- EIGHT triples for nine classes, and that is the 1.12 client's own doing: shamans have no
|
||||
-- colour of their own. They were Horde-only and paladins Alliance-only, so the two never
|
||||
-- shared a raid frame and the client hands SHAMAN the paladin pink. We ASSIGN it, which
|
||||
-- states the fact instead of re-typing three numbers and asking the next reader to believe
|
||||
-- a comment. (`ROGUE`'s yellow likewise reconciles with nothing else in the client. A later
|
||||
-- client's palette is not this one's; these are 1.12's.) Each class still gets its OWN
|
||||
-- record below, so an addon that writes to `.PALADIN.r` does not move SHAMAN — the
|
||||
-- reference's two equal-but-separate tables, preserved.
|
||||
local classRGB = {
|
||||
WARRIOR = { 0.78, 0.61, 0.43 },
|
||||
PALADIN = { 0.96, 0.55, 0.73 },
|
||||
HUNTER = { 0.67, 0.83, 0.45 },
|
||||
ROGUE = { 1.0, 0.96, 0.41 },
|
||||
PRIEST = { 1.0, 1.0, 1.0 },
|
||||
MAGE = { 0.41, 0.8, 0.94 },
|
||||
WARLOCK = { 0.58, 0.51, 0.79 },
|
||||
DRUID = { 1.0, 0.49, 0.04 },
|
||||
}
|
||||
classRGB.SHAMAN = classRGB.PALADIN
|
||||
RAID_CLASS_COLORS = {}
|
||||
for token, rgb in pairs(classRGB) do
|
||||
RAID_CLASS_COLORS[token] = { r = rgb[1], g = rgb[2], b = rgb[3] }
|
||||
end
|
||||
-- CHAT_FONT_HEIGHTS — the chat window's four selectable font heights, the reference's own
|
||||
-- table verbatim (ref Fonts.xml l.21-26). It sat in the note below as "costs nine lines the
|
||||
-- day something reads it" until decision 1589 (B246) built the chat tab's **Font Size**
|
||||
-- submenu, which is the reference's only reader too (`FCFOptionsDropDown_Initialize`
|
||||
-- iterates it). Still zero corpus addons, so the demand here is ours, not theirs.
|
||||
CHAT_FONT_HEIGHTS = { 12, 14, 16, 18 }
|
||||
|
||||
-- The page ink and the title ink per readable-material, ref Fonts.xml l.27-42 verbatim.
|
||||
--
|
||||
-- These were deliberately absent for a long time — "zero corpus addons name either, and
|
||||
-- nothing benilla ships reads them; they cost nine lines each the day something does".
|
||||
-- 1751's ninth window is that day: the reference's own `GetMaterialTextColors`
|
||||
-- (UIParent.lua l.1880-1888) reads them by these names, and it is what the sourced
|
||||
-- `ItemTextFrame.xml` calls. Our ItemTextFrame carried a private
|
||||
-- `BENILLA_MATERIAL_*_COLOR_TABLE` pair; both are deleted, and this is where 1.12 keeps
|
||||
-- the one that remains.
|
||||
MATERIAL_TEXT_COLOR_TABLE = {
|
||||
["Default"] = {0.18, 0.12, 0.06},
|
||||
["Stone"] = {1.0, 1.0, 1.0},
|
||||
["Parchment"] = {0.18, 0.12, 0.06},
|
||||
["Marble"] = {0, 0, 0},
|
||||
["Silver"] = {0.12, 0.12, 0.12},
|
||||
["Bronze"] = {0.18, 0.12, 0.06},
|
||||
}
|
||||
MATERIAL_TITLETEXT_COLOR_TABLE = {
|
||||
["Default"] = {0, 0, 0},
|
||||
["Stone"] = {0.93, 0.82, 0},
|
||||
["Parchment"] = {0, 0, 0},
|
||||
["Marble"] = {0.93, 0.82, 0},
|
||||
["Silver"] = {0.93, 0.82, 0},
|
||||
["Bronze"] = {0.93, 0.82, 0},
|
||||
}
|
||||
]]></Script>
|
||||
|
||||
<!-- MasterFont: the shadow-only root the GameFont* families chain through (ref l.56). -->
|
||||
<Font name="MasterFont" virtual="true">
|
||||
<Shadow>
|
||||
<Offset><AbsDimension x="1" y="-1"/></Offset>
|
||||
<Color r="0" g="0" b="0"/>
|
||||
</Shadow>
|
||||
</Font>
|
||||
|
||||
<!-- SystemFont: FRIZQT, 15px, yellow (ref l.64). -->
|
||||
<Font name="SystemFont" inherits="MasterFont" font="Fonts\FRIZQT__.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="15"/></FontHeight>
|
||||
<Color r="1.0" g="1.0" b="0"/>
|
||||
</Font>
|
||||
|
||||
<!-- The GameFont body family: FRIZQT, 12px, gold; color variants override only <Color> (ref l.70-96). -->
|
||||
<Font name="GameFontNormal" inherits="MasterFont" font="Fonts\FRIZQT__.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="12"/></FontHeight>
|
||||
<Color r="1.0" g="0.82" b="0"/>
|
||||
</Font>
|
||||
<Font name="GameFontHighlight" inherits="GameFontNormal" virtual="true">
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
<Font name="GameFontDisable" inherits="GameFontNormal" virtual="true">
|
||||
<Color r="0.5" g="0.5" b="0.5"/>
|
||||
</Font>
|
||||
<Font name="GameFontGreen" inherits="GameFontNormal" virtual="true">
|
||||
<Color r="0.1" g="1.0" b="0.1"/>
|
||||
</Font>
|
||||
<Font name="GameFontRed" inherits="GameFontNormal" virtual="true">
|
||||
<Color r="1.0" g="0.1" b="0.1"/>
|
||||
</Font>
|
||||
|
||||
<!-- GameFontBlack/White: 12px FRIZQT with NO MasterFont chain — shadowless (ref l.88-96). -->
|
||||
<Font name="GameFontBlack" font="Fonts\FRIZQT__.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="12"/></FontHeight>
|
||||
<Color r="0" g="0" b="0"/>
|
||||
</Font>
|
||||
<Font name="GameFontWhite" inherits="GameFontBlack" virtual="true">
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
|
||||
<!-- The GameFont Small family: FRIZQT, 10px, gold (ref l.97-125). -->
|
||||
<Font name="GameFontNormalSmall" inherits="MasterFont" font="Fonts\FRIZQT__.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="10"/></FontHeight>
|
||||
<Color r="1.0" g="0.82" b="0"/>
|
||||
</Font>
|
||||
<Font name="GameFontHighlightSmall" inherits="GameFontNormalSmall" virtual="true">
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
<Font name="GameFontDisableSmall" inherits="GameFontNormalSmall" virtual="true">
|
||||
<Color r="0.5" g="0.5" b="0.5"/>
|
||||
</Font>
|
||||
<Font name="GameFontDarkGraySmall" inherits="GameFontNormalSmall" virtual="true">
|
||||
<Color r="0.35" g="0.35" b="0.35"/>
|
||||
</Font>
|
||||
<Font name="GameFontGreenSmall" inherits="GameFontNormalSmall" virtual="true">
|
||||
<Color r="0.1" g="1.0" b="0.1"/>
|
||||
</Font>
|
||||
<Font name="GameFontRedSmall" inherits="GameFontNormalSmall" virtual="true">
|
||||
<Color r="1.0" g="0.1" b="0.1"/>
|
||||
</Font>
|
||||
<!-- Despite the name, the ref declares a SHADOW here, not an outline (ref l.119). -->
|
||||
<Font name="GameFontHighlightSmallOutline" inherits="GameFontHighlightSmall" virtual="true">
|
||||
<Shadow>
|
||||
<Offset><AbsDimension x="1" y="-1"/></Offset>
|
||||
<Color r="0" g="0" b="0"/>
|
||||
</Shadow>
|
||||
</Font>
|
||||
|
||||
<!-- The GameFont Large family: FRIZQT, 16px, gold (ref l.127-145). -->
|
||||
<Font name="GameFontNormalLarge" inherits="MasterFont" font="Fonts\FRIZQT__.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="16"/></FontHeight>
|
||||
<Color r="1.0" g="0.82" b="0"/>
|
||||
</Font>
|
||||
<Font name="GameFontHighlightLarge" inherits="GameFontNormalLarge" virtual="true">
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
<Font name="GameFontDisableLarge" inherits="GameFontNormalLarge" virtual="true">
|
||||
<Color r="0.5" g="0.5" b="0.5"/>
|
||||
</Font>
|
||||
<Font name="GameFontGreenLarge" inherits="GameFontNormalLarge" virtual="true">
|
||||
<Color r="0.1" g="1.0" b="0.1"/>
|
||||
</Font>
|
||||
<Font name="GameFontRedLarge" inherits="GameFontNormalLarge" virtual="true">
|
||||
<Color r="1.0" g="0.1" b="0.1"/>
|
||||
</Font>
|
||||
|
||||
<!-- FRIZQT, 20px, gold (ref l.145). -->
|
||||
<Font name="GameFontNormalHuge" inherits="MasterFont" font="Fonts\FRIZQT__.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="20"/></FontHeight>
|
||||
<Color r="1.0" g="0.82" b="0"/>
|
||||
</Font>
|
||||
|
||||
<!-- The Number family: ARIALN, outlined, white — NO shadow (ref l.150-179). -->
|
||||
<Font name="NumberFontNormal" font="Fonts\ARIALN.TTF" outline="NORMAL" virtual="true">
|
||||
<FontHeight><AbsValue val="14"/></FontHeight>
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
<Font name="NumberFontNormalYellow" inherits="NumberFontNormal" virtual="true">
|
||||
<Color r="1.0" g="0.82" b="0"/>
|
||||
</Font>
|
||||
<Font name="NumberFontNormalSmall" font="Fonts\ARIALN.TTF" outline="THICK" virtual="true" monochrome="true">
|
||||
<FontHeight><AbsValue val="12"/></FontHeight>
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
<Font name="NumberFontNormalSmallGray" inherits="NumberFontNormalSmall" outline="THICK" virtual="true">
|
||||
<Color r="0.6" g="0.6" b="0.6"/>
|
||||
</Font>
|
||||
<Font name="NumberFontNormalLarge" font="Fonts\ARIALN.TTF" outline="NORMAL" virtual="true">
|
||||
<FontHeight><AbsValue val="16"/></FontHeight>
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
<Font name="NumberFontNormalHuge" font="Fonts\skurri.ttf" outline="THICK" virtual="true">
|
||||
<FontHeight><AbsValue val="30"/></FontHeight>
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
|
||||
<!-- ChatFontNormal: ARIALN, 14px, white, (1,-1) black shadow (ref l.180). -->
|
||||
<Font name="ChatFontNormal" font="Fonts\ARIALN.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="14"/></FontHeight>
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
<Shadow>
|
||||
<Offset><AbsDimension x="1" y="-1"/></Offset>
|
||||
<Color r="0" g="0" b="0"/>
|
||||
</Shadow>
|
||||
</Font>
|
||||
|
||||
<!-- The quest/parchment fonts (ref l.191-215): MORPHEUS 18 title with a warm-brown (0.49,0.35,0.05)
|
||||
shadow; the FRIZQT body fonts are dark-on-parchment and SHADOWLESS (QuestFontNormalSmall
|
||||
chains through GameFontBlack, not MasterFont). -->
|
||||
<Font name="QuestTitleFont" font="Fonts\MORPHEUS.ttf" virtual="true">
|
||||
<FontHeight><AbsValue val="18"/></FontHeight>
|
||||
<Color r="0" g="0" b="0"/>
|
||||
<Shadow>
|
||||
<Offset><AbsDimension x="1" y="-1"/></Offset>
|
||||
<Color r="0.49" g="0.35" b="0.05"/>
|
||||
</Shadow>
|
||||
</Font>
|
||||
<Font name="QuestFont" font="Fonts\FRIZQT__.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="13"/></FontHeight>
|
||||
<Color r="0" g="0" b="0"/>
|
||||
</Font>
|
||||
<Font name="QuestFontNormalSmall" inherits="GameFontBlack" virtual="true">
|
||||
<Color r="0.30" g="0.18" b="0"/>
|
||||
</Font>
|
||||
<Font name="QuestFontHighlight" font="Fonts\FRIZQT__.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="14"/></FontHeight>
|
||||
<Color r="0" g="0" b="0"/>
|
||||
</Font>
|
||||
|
||||
<!-- The MORPHEUS 15px book/mail body fonts (ref l.216-229). -->
|
||||
<Font name="ItemTextFontNormal" font="Fonts\MORPHEUS.TTF" justifyH="LEFT" virtual="true">
|
||||
<FontHeight><AbsValue val="15"/></FontHeight>
|
||||
<Color r="0.18" g="0.12" b="0.06"/>
|
||||
</Font>
|
||||
<Font name="MailTextFontNormal" font="Fonts\MORPHEUS.TTF" justifyH="LEFT" virtual="true">
|
||||
<FontHeight><AbsValue val="15"/></FontHeight>
|
||||
<Color r="0.18" g="0.12" b="0.06"/>
|
||||
</Font>
|
||||
|
||||
<!-- SubSpellFont: FRIZQT 10, dark brown (ref l.231). -->
|
||||
<Font name="SubSpellFont" font="Fonts\FRIZQT__.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="10"/></FontHeight>
|
||||
<Color r="0.35" g="0.2" b="0"/>
|
||||
</Font>
|
||||
|
||||
<!-- The dialog (StaticPopup) button pair: FRIZQT 16 (ref l.237-245). -->
|
||||
<Font name="DialogButtonNormalText" font="Fonts\FRIZQT__.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="16"/></FontHeight>
|
||||
<Color r="1.0" g="0.82" b="0.0"/>
|
||||
</Font>
|
||||
<Font name="DialogButtonHighlightText" inherits="DialogButtonNormalText" virtual="true">
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
|
||||
<!-- The zone-announce banners: THICK-outlined FRIZQT; the ref's bare `<Shadow/>` transcribed
|
||||
as-is (offset defaults, i.e. no visible offset) (ref l.246-257). -->
|
||||
<Font name="ZoneTextFont" font="Fonts\FRIZQT__.TTF" outline="THICK" virtual="true">
|
||||
<FontHeight><AbsValue val="102"/></FontHeight>
|
||||
<Color r="1.0" g="0.9294" b="0.7607"/>
|
||||
<Shadow/>
|
||||
</Font>
|
||||
<Font name="SubZoneTextFont" font="Fonts\FRIZQT__.TTF" outline="THICK" virtual="true">
|
||||
<FontHeight><AbsValue val="26"/></FontHeight>
|
||||
</Font>
|
||||
|
||||
<Font name="ErrorFont" inherits="GameFontNormalLarge" virtual="true"/>
|
||||
<Font name="TextStatusBarText" inherits="NumberFontNormal" virtual="true"/>
|
||||
<Font name="TextStatusBarTextSmall" font="Fonts\ARIALN.TTF" outline="NORMAL" virtual="true" monochrome="false">
|
||||
<FontHeight><AbsValue val="12"/></FontHeight>
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
<Font name="CombatLogFont" inherits="GameFontHighlight" justifyH="LEFT" virtual="true"/>
|
||||
|
||||
<!-- The tooltip family: FRIZQT white, no shadow (ref l.267-285). -->
|
||||
<Font name="GameTooltipText" font="Fonts\FRIZQT__.TTF" justifyH="LEFT" virtual="true">
|
||||
<FontHeight><AbsValue val="12"/></FontHeight>
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
<Font name="GameTooltipTextSmall" font="Fonts\FRIZQT__.TTF" justifyH="LEFT" virtual="true">
|
||||
<FontHeight><AbsValue val="10"/></FontHeight>
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
<Font name="GameTooltipHeaderText" font="Fonts\FRIZQT__.TTF" justifyH="LEFT" virtual="true">
|
||||
<FontHeight><AbsValue val="14"/></FontHeight>
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Font>
|
||||
|
||||
<Font name="WorldMapTextFont" font="Fonts\FRIZQT__.TTF" outline="THICK" virtual="true">
|
||||
<FontHeight><AbsValue val="102"/></FontHeight>
|
||||
<Color r="1.0" g="0.9294" b="0.7607"/>
|
||||
<Shadow/>
|
||||
</Font>
|
||||
|
||||
<!-- The invoice (auction mail) parchment fonts (ref l.293-304). -->
|
||||
<Font name="InvoiceTextFontNormal" font="Fonts\FRIZQT__.TTF" justifyH="LEFT" virtual="true">
|
||||
<FontHeight><AbsValue val="12"/></FontHeight>
|
||||
<Color r="0.18" g="0.12" b="0.06"/>
|
||||
</Font>
|
||||
<Font name="InvoiceTextFontSmall" font="Fonts\FRIZQT__.TTF" justifyH="LEFT" virtual="true">
|
||||
<FontHeight><AbsValue val="10"/></FontHeight>
|
||||
<Color r="0.18" g="0.12" b="0.06"/>
|
||||
</Font>
|
||||
|
||||
<!-- CombatTextFont: FRIZQT 25, gold, MasterFont shadow (ref l.305). -->
|
||||
<Font name="CombatTextFont" inherits="MasterFont" font="Fonts\FRIZQT__.TTF" virtual="true">
|
||||
<FontHeight><AbsValue val="25"/></FontHeight>
|
||||
<Color r="1.0" g="0.82" b="0"/>
|
||||
</Font>
|
||||
</Ui>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -240,6 +240,39 @@
|
||||
-- reference at typical resolutions reads nearer two-thirds.
|
||||
ERA_WINDOW_SCALE = 0.78
|
||||
|
||||
-- ── UpdateNameplates (ref UIOptionsFrame.lua l.768-780, verbatim) ────────────────────
|
||||
-- Its reference home is `UIOptionsFrame.lua` and this file is our stand-in for that window
|
||||
-- (0950/0992), so the function belongs here rather than beside its caller.
|
||||
--
|
||||
-- **Decision 1878 recorded this as "a missing ENGINE verb, and 1203 says build it or leave
|
||||
-- it loudly absent", and named it the thing blocking the UIParent keystone. It is neither.**
|
||||
-- It is FrameXML — `UIOptionsFrame.lua:768`, absent from wow-re's binding-shape table — and
|
||||
-- every verb it calls is already ours: `ShowNameplates`/`HideNameplates`/
|
||||
-- `ShowFriendNameplates`/`HideFriendNameplates` are the four CVar-backed bindings in
|
||||
-- `script/cvars.rs`, all argc=0 arity=0, matching the reference's shapes.
|
||||
--
|
||||
-- `NAMEPLATES_ON`/`FRIENDNAMEPLATES_ON` are `RegisterForSave` globals the reference drives
|
||||
-- from the keybinding alone (1.12's own options UI has no nameplate row). Nil is a normal
|
||||
-- state for them and the else-branches are the reference's own answer to it, so this needs
|
||||
-- no seeding — which is why the whole thing is twelve lines rather than a build.
|
||||
--
|
||||
-- The reference's odd second arm is kept as it stands: on FRIENDNAMEPLATES_ON it calls
|
||||
-- Show and then Hide, which is a no-op in the real client too. Transcribed, not corrected;
|
||||
-- fixing a reference bug here would be inventing a difference.
|
||||
function UpdateNameplates()
|
||||
if ( NAMEPLATES_ON ) then
|
||||
ShowNameplates()
|
||||
else
|
||||
HideNameplates()
|
||||
end
|
||||
if ( FRIENDNAMEPLATES_ON ) then
|
||||
ShowFriendNameplates()
|
||||
HideFriendNameplates()
|
||||
else
|
||||
HideFriendNameplates()
|
||||
end
|
||||
end
|
||||
|
||||
function OptionsFrame_UpdateScale()
|
||||
-- The era panel is registered `checkFit = 1`: the panel manager also shrinks it to
|
||||
-- fit a small screen (UIPanelUpdateScaleForFit: min over width/height ratios
|
||||
|
||||
@@ -215,6 +215,40 @@
|
||||
-- statements on the same event). `MultiActionBar_Update` is existence-guarded because the
|
||||
-- ~25 per-window harnesses that load this file load no bars; the binding itself is engine
|
||||
-- level and always installed. Decision 1500.
|
||||
-- ── ITEM_QUALITY_COLORS (ref UIParent.lua l.65-72) ───────────────────────────────
|
||||
-- Its reference home is THIS file. It lived in our own `Fonts.xml` until 1888 put
|
||||
-- that file on the chain — the reference's `Fonts.xml` does not declare it, so the
|
||||
-- table had to come back here or vanish. Every reader is below UIParent.xml in the
|
||||
-- manifest (GameTooltip 169, UnitPopup 203, AuctionFrame 928; UnitPopup's is a
|
||||
-- FILE-SCOPE read inside a table literal, which is the one that makes the order
|
||||
-- load-bearing rather than incidental).
|
||||
-- The item-quality palette — **built from the engine verb, exactly as the reference
|
||||
-- builds it** (ref UIParent.lua l.65-72, verbatim shape; decision 1199):
|
||||
--
|
||||
-- ITEM_QUALITY_COLORS = { }
|
||||
-- for i = -1, 6 do … = GetItemQualityColor(i) end
|
||||
--
|
||||
-- It used to be seven hand-typed rows here. They were CLOSE and not exact — 0.62 where
|
||||
-- the client's byte is 0x9d/255 = 0.6157, 0.64/0.21/0.93 where Epic is
|
||||
-- 0xa3/0x35/0xee — and they carried no `.hex`, which the reference's table has and
|
||||
-- which addons splice straight into a chat link. `GetItemQualityColor` is now a real
|
||||
-- engine binding over wow-5875-re RF-0055's byte-verified table, so the rounding and
|
||||
-- the second source both go.
|
||||
--
|
||||
-- The loop starts at -1 like the reference's, and the -1 row is not a curiosity: it is the
|
||||
-- colour a loot row wears while its item template is uncached, which is the value
|
||||
-- `GetLootSlotInfo` answers there. `GetItemQualityColor`'s clamp is UNSIGNED, so -1 lands
|
||||
-- on Common/white with 7-and-up (decision 1805, correcting 1199's "reads before the
|
||||
-- array").
|
||||
ITEM_QUALITY_COLORS = { }
|
||||
for i = -1, 6 do
|
||||
ITEM_QUALITY_COLORS[i] = { }
|
||||
ITEM_QUALITY_COLORS[i].r,
|
||||
ITEM_QUALITY_COLORS[i].g,
|
||||
ITEM_QUALITY_COLORS[i].b,
|
||||
ITEM_QUALITY_COLORS[i].hex = GetItemQualityColor(i)
|
||||
end
|
||||
|
||||
-- ── UIParentLoadAddOn (ref UIParent.lua l.638-647, and now our slot for it) ──────
|
||||
-- It lived in our own BasicControls.xml until 1881 migrated that file to the chain;
|
||||
-- the reference never put it there, so the move is back to where it belongs.
|
||||
@@ -285,6 +319,16 @@
|
||||
end
|
||||
end
|
||||
|
||||
-- ref UIParent.lua l.194-196, verbatim — the fifth squatter, and the smallest. Stock
|
||||
-- `HelpFrame.xml` calls it when a GM survey is waiting (its l.1487), and its whole body is
|
||||
-- `UIParentLoadAddOn("Blizzard_GMSurveyUI")` — a verb 1881 already moved here and an addon
|
||||
-- that really is on the player's chain (patch-2.MPQ). So this is the reference's own
|
||||
-- function, not a stand-in: 1889 needed it because the stock help window is on the chain
|
||||
-- now and ours is gone.
|
||||
function GMSurveyFrame_LoadUI()
|
||||
UIParentLoadAddOn("Blizzard_GMSurveyUI")
|
||||
end
|
||||
|
||||
-- ref UIParent.lua's own `ShowMacroFrame`, moved here from our MacroFrame.xml when 1848
|
||||
-- retired that file — the third such squatter after `InspectUnit` (1832) and
|
||||
-- `ToggleTalentFrame` (1833), and by now the expected shape.
|
||||
|
||||
@@ -38,7 +38,13 @@
|
||||
|
||||
# The named virtual Font objects (decision 0084) — must load first so any FontString's
|
||||
# `inherits="GameFontNormal"` resolves against a registered object.
|
||||
Fonts.xml
|
||||
# ══ THE FONT REGISTRY — the reference's own, off the player's chain (1751, 1888) ══
|
||||
# All 51 `<Font>` objects, name for name, plus the colour codes, `CHAT_FONT_HEIGHTS`,
|
||||
# `MATERIAL_TEXT_COLOR_TABLE`, `MATERIAL_TITLETEXT_COLOR_TABLE` and `RAID_CLASS_COLORS`.
|
||||
# Ours was a transcription differing only in formatting (it inlined `<FontHeight>`), plus one
|
||||
# addition: `ITEM_QUALITY_COLORS`, whose reference home is `UIParent.lua` and which 1888 moved
|
||||
# to our UIParent.xml so it survives this swap.
|
||||
Interface\FrameXML\Fonts.xml
|
||||
|
||||
# ══ THE STRINGS — the reference's own first line, seated second here (1848) ══
|
||||
# **Second, not first, and that is deliberate.** Manifest entry 0 is the FONT REGISTRY:
|
||||
@@ -337,6 +343,19 @@ TalentWipeConfirm.xml
|
||||
# The shared CooldownFrame_SetTimer (the ref's Cooldown.lua file split) — before its
|
||||
# consumers (ActionBar's buttons, the multibars, the stance bar, BagFrame's slots).
|
||||
Cooldown.xml
|
||||
# ══ THE ACTION-BUTTON REGIONS — the reference's own, off the player's chain (1751) ══
|
||||
# `ActionButtonTemplate`: the REGIONS-ONLY half of the reference's two-level split — icon, count,
|
||||
# hotkey, name, border, flash, cooldown and normal texture. It declares NO scripts and calls
|
||||
# nothing, so it needs no `.lua` beside it. The handler half is `ActionBarButtonTemplate`, which
|
||||
# ActionBar.xml still declares and which inherits this; those handlers stay ours until
|
||||
# `ActionButton.lua`'s 21 verbs migrate (we have 10 of them, under the reference's own names).
|
||||
#
|
||||
# Ours was a transcription of this exact file — the eight `$parent*` child names match to the
|
||||
# letter — so the swap is a deletion.
|
||||
#
|
||||
# **It must come BEFORE ActionBar.xml.** A template is registered by name and the later line
|
||||
# wins, so seated below it our transcription would take the name straight back.
|
||||
Interface\FrameXML\ActionButtonTemplate.xml
|
||||
ActionBar.xml
|
||||
# The four OPTIONAL extra bars (bottom left/right 61-72 / 49-60, vertical right/left
|
||||
# 25-36 / 37-48), all shipping hidden until the player ticks their row (decision 1500):
|
||||
@@ -493,9 +512,16 @@ Interface\FrameXML\ColorPickerFrame.xml
|
||||
# WorldMapRaid1..40 with inherits=, so the virtual Frames have to be registered before it.
|
||||
WorldMapFrameTemplates.xml
|
||||
WorldMapFrame.xml
|
||||
# The cast bar (decision 0137 phase 1) — the extracted 1.12 CastingBarFrame, driven by
|
||||
# the SPELLCAST_* events `crate::ui_cast` fires. Only needs Fonts before it.
|
||||
CastingBar.xml
|
||||
# ══ THE CAST BAR — the reference's own, off the player's chain (1751) ══
|
||||
# `CastingBarFrame` + its Border/Flash/Spark/Text children, driven by the SPELLCAST_* events
|
||||
# `crate::ui_cast` fires (decision 0137 phase 1). Our `CastingBar.xml` was a transcription of this
|
||||
# very file under a different name — all five frame names identical — so the swap is a deletion.
|
||||
#
|
||||
# The file SOURCES ITS OWN LUA (`<Script file="CastingBarFrame.lua"/>` at its l.3), which is why
|
||||
# only the `.xml` is listed here and `every_chain_xml_brings_its_own_lua` is satisfied.
|
||||
#
|
||||
# It brings `CastingBarFrame_UpdatePosition`, which ours never had. Only needs Fonts before it.
|
||||
Interface\FrameXML\CastingBarFrame.xml
|
||||
# ══ THE MIRROR TIMERS — the reference's own, off the player's chain (1751, window 7) ══
|
||||
# `MirrorTimer1..3`, `MIRRORTIMER_NUMTIMERS`, `MirrorTimerColors` and the `MirrorTimer_Show` /
|
||||
# `MirrorTimerFrame_*` family out of `MirrorTimer.lua`. The breath/fatigue bars at top-centre,
|
||||
@@ -1014,7 +1040,12 @@ ScriptLogFrame.xml
|
||||
# resolved at call time, so that pair could sit either way round). The one seat that is a REAL
|
||||
# requirement rather than legibility is **after BuffFrame.xml**: the toast re-anchors
|
||||
# TemporaryEnchantFrame out of its own way, and that frame is declared there.
|
||||
HelpFrame.xml
|
||||
#
|
||||
# **The reference's own since 1889.** Ours was 1017 lines against the reference's 2088 — the
|
||||
# largest shortfall of any window we shipped — so this swap is mostly ARRIVAL, not replacement.
|
||||
# The file sources its own `HelpFrame.lua` (its l.3), so only the `.xml` is listed. Its one
|
||||
# named need, `GMSurveyFrame_LoadUI`, is now in UIParent.xml where the reference declares it.
|
||||
Interface\FrameXML\HelpFrame.xml
|
||||
# The cinematic frame: the ESC that skips a fly-by, and the SCREENSHOT chord it hands back. It
|
||||
# draws NOTHING — the letterbox moved to Bevy UI nodes in `crate::cinematic` (decision 1699),
|
||||
# because the lane this frame paints into is dark for the whole shot.
|
||||
|
||||
@@ -650,4 +650,8 @@ pub(super) fn exploration_xp(
|
||||
pub(super) fn level_up(l: LevelUpInfo, chat_log: &mut ChatLog) {
|
||||
let talent_points = u32::from(l.level >= 10);
|
||||
chat_log.push_level_up(&l, talent_points);
|
||||
// ...and park the raw tuple for `ui_unit`'s `PLAYER_LEVEL_UP`, whose nine args the reference's
|
||||
// own ChatFrame reads (1884). The Rust lines above stay until that window migrates and can
|
||||
// take over printing them — retiring them first would simply lose the ding.
|
||||
chat_log.push_level_up_gains(&l, talent_points);
|
||||
}
|
||||
|
||||
@@ -419,7 +419,10 @@ fn pre_resolved_lines_land_on_the_errors_frame_in_the_arms_colour() {
|
||||
script.set_screen_size(1024.0, 768.0);
|
||||
// The errors frame is the reference's own file since 1751 window 14, so this reads both
|
||||
// stores through the one loader that speaks them.
|
||||
for file in ["Fonts.xml", "Interface\\FrameXML\\UIErrorsFrame.xml"] {
|
||||
for file in [
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"Interface\\FrameXML\\UIErrorsFrame.xml",
|
||||
] {
|
||||
crate::ui_script::load_ui_for_test(&script, file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,6 +200,14 @@ pub(super) fn text_emote_event(
|
||||
pub(crate) struct ChatLog {
|
||||
pending: Vec<Pending>,
|
||||
broadcasts: Vec<super::broadcast::Broadcast>,
|
||||
/// The ding's gain tuple, parked for `ui_unit`'s `PLAYER_LEVEL_UP` fire (decision 1884).
|
||||
///
|
||||
/// The net layer has no `UiScript` — deliberately — so the gains arrive here, on the same
|
||||
/// net-to-UI channel `broadcasts` uses, and the feed that owns the level edge picks them up.
|
||||
/// They are matched BY LEVEL rather than just drained, because the trigger is a descriptor
|
||||
/// diff and the gains are packet-borne: the two land together today, and matching means a
|
||||
/// stale entry can never attach itself to a later ding if they ever stop doing so.
|
||||
level_up_gains: Vec<(LevelUpInfo, u32)>,
|
||||
}
|
||||
|
||||
impl ChatLog {
|
||||
@@ -217,6 +225,22 @@ impl ChatLog {
|
||||
pub(crate) fn take_broadcasts(&mut self) -> Vec<super::broadcast::Broadcast> {
|
||||
std::mem::take(&mut self.broadcasts)
|
||||
}
|
||||
|
||||
/// Park the ding's gains for the `PLAYER_LEVEL_UP` fire (see [`ChatLog::level_up_gains`]).
|
||||
pub(crate) fn push_level_up_gains(&mut self, info: &LevelUpInfo, talent_points: u32) {
|
||||
self.level_up_gains.push((*info, talent_points));
|
||||
}
|
||||
|
||||
/// The parked gains for `level`, removed from the queue. `None` when the level edge came from
|
||||
/// somewhere the packet did not — a GM demotion's descriptor write, or a first observation —
|
||||
/// in which case the caller fires zeros, which is what those gains actually are.
|
||||
pub(crate) fn take_level_up_gains(&mut self, level: u32) -> Option<(LevelUpInfo, u32)> {
|
||||
let i = self
|
||||
.level_up_gains
|
||||
.iter()
|
||||
.position(|(l, _)| l.level == level)?;
|
||||
Some(self.level_up_gains.remove(i))
|
||||
}
|
||||
}
|
||||
|
||||
impl ChatLog {
|
||||
|
||||
@@ -438,7 +438,7 @@ fn chat_vm() -> benilla_ui::script::UiScript {
|
||||
// that would hide a real ordering fault. (The tooltip file is the dropdown kit's own
|
||||
// dependency — its MenuBackdrop reads `TOOLTIP_DEFAULT_COLOR`.)
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
// The UIMenu kit is the reference's own file since 1751 window 21, so this reads both
|
||||
@@ -2324,3 +2324,38 @@ fn the_posture_emotes_carry_no_swim_suppression_flag() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// **The ding's gains reach `PLAYER_LEVEL_UP` matched BY LEVEL, and a miss is zeros, not absence.**
|
||||
///
|
||||
/// The net layer has no `UiScript`, so `SMSG_LEVELUP_INFO`'s tuple is parked on `ChatLog` and the
|
||||
/// feed that owns the level edge picks it up (decision 1884). Matching on the level is what keeps
|
||||
/// a stale entry from attaching to a later ding — the trigger is a descriptor diff and the gains
|
||||
/// are packet-borne, so the two are only coincidentally in step.
|
||||
///
|
||||
/// The miss case is the load-bearing half: a GM demotion writes the descriptor with no packet, and
|
||||
/// the event must still carry nine arguments. `ChatFrame.lua` guards every one with
|
||||
/// `if ( argN > 0 )`, so a zero reads as "no line" while a nil raises.
|
||||
#[test]
|
||||
fn level_up_gains_are_matched_by_level_and_a_miss_is_not_an_absence() {
|
||||
use benilla_protocol::messages::LevelUpInfo;
|
||||
|
||||
let mut log = super::feed::ChatLog::default();
|
||||
let ding = LevelUpInfo {
|
||||
level: 10,
|
||||
health: 22,
|
||||
powers: [15, 0, 0, 0, 0],
|
||||
stats: [0, 1, 2, 3, 0],
|
||||
};
|
||||
log.push_level_up_gains(&ding, 1);
|
||||
|
||||
// A level edge that is not this ding's leaves the entry parked...
|
||||
assert!(log.take_level_up_gains(11).is_none(), "wrong level matched");
|
||||
// ...and the right one takes it, exactly once.
|
||||
let (got, talent_points) = log.take_level_up_gains(10).expect("parked for level 10");
|
||||
assert_eq!(got, ding);
|
||||
assert_eq!(talent_points, 1);
|
||||
assert!(
|
||||
log.take_level_up_gains(10).is_none(),
|
||||
"the entry was taken, not copied"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1067,16 +1067,14 @@ mod bind_confirm_tests {
|
||||
const F_OBJECT_ENTRY: u16 = 3;
|
||||
|
||||
fn load_ui(s: &UiScript) {
|
||||
for file in ["Fonts.xml", "MoneyFrame.xml", "UiPanels.xml"] {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(s, &doc, &|_| None);
|
||||
assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
|
||||
// Through the chain-aware reader: this list names chain files now, and a
|
||||
// reader that joins `assets/ui` cannot resolve one (1838, 1887, 1888).
|
||||
for file in [
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
] {
|
||||
crate::ui_script::load_ui_for_test(s, file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1431,7 +1431,6 @@ fn diff_and_push(
|
||||
}
|
||||
}
|
||||
memory.pushed = fresh;
|
||||
script.fire_event("BAG_UPDATE_DELAYED", vec![]);
|
||||
return true;
|
||||
}
|
||||
// A `BAG_CLOSED` with nothing else to say still went into the VM — two identical bags swapped
|
||||
|
||||
@@ -1111,11 +1111,10 @@ fn feed_loot(
|
||||
script.fire_event("LOOT_SLOT_CLEARED", vec![ScriptValue::Int(i as i64 + 1)]);
|
||||
}
|
||||
}
|
||||
script.fire_event("LOOT_UPDATE", vec![]);
|
||||
// The reference keeps the two apart: `LOOT_UPDATE` repaints the rows, while a changed
|
||||
// candidate list refreshes the open dropdown in place without re-toggling it
|
||||
// (`LootFrame_OnEvent`'s `UIDropDownMenu_Refresh(GroupLootDropDown)`,
|
||||
// `LootFrame.lua:63`). Firing it only on a real change keeps a closed menu untouched.
|
||||
// A changed candidate list refreshes the open dropdown in place without re-toggling
|
||||
// it — `LootFrame_OnEvent`'s `UIDropDownMenu_Refresh(GroupLootDropDown)`, which the
|
||||
// reference hangs off `UPDATE_MASTER_LOOT_LIST` (`LootFrame.lua:62-64`). Firing it
|
||||
// only on a real change keeps a closed menu untouched.
|
||||
if before.master_candidates != after.master_candidates {
|
||||
script.fire_event("UPDATE_MASTER_LOOT_LIST", vec![]);
|
||||
}
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
//! announcement lines into the chat window once their names resolve. [`drain_loot_rolls`] pulls the
|
||||
//! `RollOnLoot` votes back out as [`ClientCommand::LootRoll`].
|
||||
//!
|
||||
//! ## `UPDATE_LOOT_ROLL` — why a snapshot seam needs an event the real client doesn't have
|
||||
//! ## The late item template, and the `?` that outlives it
|
||||
//!
|
||||
//! The reference's `GetLootRollItemInfo` reads live C state off the roll node, so its
|
||||
//! `GroupLootFrame_OnShow` can paint once and be done. Ours reads a **pushed snapshot**, and a
|
||||
//! snapshot is always at least one step behind the thing that made it change — twice over:
|
||||
//! The reference's `GetLootRollItemInfo` reads live C state off the roll node, so by the time
|
||||
//! `START_LOOT_ROLL` fires there is something to paint and `GroupLootFrame_OnShow` can paint once
|
||||
//! and be done. Ours reads a **pushed snapshot**, and a snapshot is at least one step behind the
|
||||
//! thing that changed it — twice over:
|
||||
//!
|
||||
//! 1. A roll is added to [`LootRolls::active`] and to `opened` in the same [`LootRolls::start`]
|
||||
//! call, so the *first* snapshot that contains a roll is built in the same pass that announces
|
||||
@@ -30,12 +31,20 @@
|
||||
//! `None` until the query lands, which is typically several frames after the roll opens.
|
||||
//!
|
||||
//! (1) is fixed by ordering — the push precedes the events here, as it does in [`crate::ui_loot`].
|
||||
//! (2) cannot be, so `UPDATE_LOOT_ROLL(rollID)` fires whenever an open roll's *display identity*
|
||||
//! ([`display_identity`]) changes underneath a frame, and the frame repaints. Ordering alone would
|
||||
//! be a guarantee resting on system order in a file far from the Lua that depends on it; with the
|
||||
//! repair in place the paint is correct either way, and ordering only decides whether there is a
|
||||
//! visible blank first. Both halves ship: (1) for the frame you actually see, (2) because it is what
|
||||
//! makes the documented in-flight fallback resolve instead of staying a `?` forever.
|
||||
//!
|
||||
//! **(2) is currently a live defect: a roll whose template lands late paints `?` and keeps it.**
|
||||
//! We used to fire an `UPDATE_LOOT_ROLL(rollID)` when an open roll's display identity changed, and
|
||||
//! our own `GroupLootFrame` re-entered its paint from it. That frame is the reference's now, and
|
||||
//! the reference has no such seam — so the repaint stopped when the window migrated, and the event
|
||||
//! went on being fired into a room with nobody in it. 1883 removed it: an event the 1.12 client
|
||||
//! does not have, which nothing listened for, was not fixing anything.
|
||||
//!
|
||||
//! **Decision 1838 already carries the fix**, and it is the reference's own invariant rather than a
|
||||
//! re-invented seam: hold the roll until its template resolves, so the snapshot is populated when
|
||||
//! the frame first paints, exactly as the reference's C state is. The cost is the roll timer — a
|
||||
//! roll announced late is a roll with less time on it — so it wants a deadline (announce anyway
|
||||
//! after N ms, with the `?`) rather than an unbounded wait. Not built yet; 1883 only removed the
|
||||
//! event that was standing in for it.
|
||||
//!
|
||||
//! ## Two client-side behaviours, and why
|
||||
//!
|
||||
@@ -510,29 +519,6 @@ fn snapshot(
|
||||
LootRollsState { rolls: entries }
|
||||
}
|
||||
|
||||
/// Everything about a roll a `GroupLootFrame` actually *paints* — deliberately **not**
|
||||
/// `time_left_ms`, which ticks every frame and would make "the snapshot changed" mean nothing.
|
||||
fn display_identity(e: &LootRollEntry) -> (&Option<String>, &Option<String>, Option<u32>, bool) {
|
||||
(&e.name, &e.texture, e.quality, e.bind_on_pickup)
|
||||
}
|
||||
|
||||
/// The rolls open in *both* snapshots whose display identity changed — i.e. the ones with a frame
|
||||
/// already up that is now painting the wrong thing. A roll only in `fresh` is newly opened and gets
|
||||
/// its `START_LOOT_ROLL`; one only in `last` is closed and gets its `CANCEL_LOOT_ROLL`.
|
||||
fn repainted(last: &LootRollsState, fresh: &LootRollsState) -> Vec<u32> {
|
||||
fresh
|
||||
.rolls
|
||||
.iter()
|
||||
.filter(|f| {
|
||||
last.rolls
|
||||
.iter()
|
||||
.find(|l| l.roll_id == f.roll_id)
|
||||
.is_some_and(|l| display_identity(l) != display_identity(f))
|
||||
})
|
||||
.map(|f| f.roll_id)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Tick the open rolls, push them into the VM, fire the open/close events, and drain the queued
|
||||
/// announcement lines into chat.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -580,7 +566,6 @@ fn feed_loot_rolls(
|
||||
// same `start()` call that queued `opened`, so pushing after would hand every fresh roll an
|
||||
// empty lookup. Same order as ui_loot's window feed, for the same reason.
|
||||
let fresh = snapshot(&rolls, &mut items, icons.as_deref(), &commands, catalogs);
|
||||
let changed = repainted(last, &fresh);
|
||||
if fresh != *last {
|
||||
script.set_loot_rolls(fresh.clone());
|
||||
*last = fresh;
|
||||
@@ -600,11 +585,6 @@ fn feed_loot_rolls(
|
||||
debug!("ui_loot_roll: roll {roll_id} cancelled");
|
||||
script.fire_event("CANCEL_LOOT_ROLL", vec![ScriptValue::Int(roll_id as i64)]);
|
||||
}
|
||||
// The late item template landing under a frame that is already up (see the module docs).
|
||||
for roll_id in changed {
|
||||
debug!("ui_loot_roll: roll {roll_id} display identity resolved");
|
||||
script.fire_event("UPDATE_LOOT_ROLL", vec![ScriptValue::Int(roll_id as i64)]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Drain the Lua votes: each `RollOnLoot(rollID, rollType)` becomes one `CMSG_LOOT_ROLL` addressed
|
||||
@@ -1008,55 +988,4 @@ mod tests {
|
||||
r.start(start(0xBB, 0, 4306));
|
||||
assert_eq!(r.active[0].roll_id, 2);
|
||||
}
|
||||
|
||||
fn entry(roll_id: u32, name: Option<&str>, time_left_ms: u32) -> LootRollEntry {
|
||||
LootRollEntry {
|
||||
roll_id,
|
||||
name: name.map(Into::into),
|
||||
texture: name.map(|_| "Interface\\Icons\\INV_Staff_12".into()),
|
||||
quantity: 1,
|
||||
quality: name.map(|_| 4),
|
||||
bind_on_pickup: name.is_some(),
|
||||
time_left_ms,
|
||||
item_id: 17182,
|
||||
// Lands with the name — one template answer fills both (decision 1059).
|
||||
link: name.map(|n| crate::ui_items::item_link(17182, n, 4)),
|
||||
random_property_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn state(entries: &[LootRollEntry]) -> LootRollsState {
|
||||
LootRollsState {
|
||||
rolls: entries.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// `UPDATE_LOOT_ROLL` fires only when what a frame *paints* changed. The load-bearing half is
|
||||
/// the negative: `time_left_ms` ticks every single frame, so counting it would fire the repaint
|
||||
/// 60x a second and make the signal worthless (and the timer bar reads the model directly — it
|
||||
/// has never needed an event).
|
||||
#[test]
|
||||
fn only_a_display_change_asks_for_a_repaint() {
|
||||
let flight = state(&[entry(7, None, 60_000)]);
|
||||
let landed = state(&[entry(7, Some("Staff of Jordan"), 59_000)]);
|
||||
|
||||
assert_eq!(repainted(&flight, &landed), vec![7], "the template landed");
|
||||
assert_eq!(repainted(&landed, &landed), Vec::<u32>::new(), "unchanged");
|
||||
|
||||
// The timer alone, which is what changes on almost every frame.
|
||||
let ticked = state(&[entry(7, Some("Staff of Jordan"), 58_000)]);
|
||||
assert_eq!(
|
||||
repainted(&landed, &ticked),
|
||||
Vec::<u32>::new(),
|
||||
"a ticking bar is not a repaint"
|
||||
);
|
||||
|
||||
// Opened and closed rolls are the START/CANCEL events' business, not this one's.
|
||||
let two = state(&[
|
||||
entry(7, Some("Staff of Jordan"), 59_000),
|
||||
entry(8, None, 60_000),
|
||||
]);
|
||||
assert_eq!(repainted(&landed, &two), Vec::<u32>::new(), "8 is new");
|
||||
assert_eq!(repainted(&two, &landed), Vec::<u32>::new(), "8 closed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use benilla_ui::script::{PetStats, UiScript};
|
||||
use benilla_ui::script::{PetStats, ScriptValue, UiScript};
|
||||
|
||||
use crate::names::NameCache;
|
||||
use crate::net::{NetCommands, ObjectStore};
|
||||
@@ -317,7 +317,10 @@ fn feed_pet_stats(
|
||||
script.set_pet_stats(fresh.0, fresh.1);
|
||||
// Push before firing — dispatch runs the Lua handlers synchronously (the `ui_unit` rule).
|
||||
if happiness_moved {
|
||||
script.fire_event("UNIT_HAPPINESS", vec![]);
|
||||
// `%s` — the unit token, per the reference's own fire site (SignalEvent2, decision 1884).
|
||||
// Every 1.12 `UNIT_*` event carries it, and its consumers gate on it: a handler's first
|
||||
// line is `if ( arg1 == this.unit )`, so an argless fire reaches nobody.
|
||||
script.fire_event("UNIT_HAPPINESS", vec![ScriptValue::Str("pet".into())]);
|
||||
}
|
||||
if xp_moved {
|
||||
script.fire_event("UNIT_PET_EXPERIENCE", vec![]);
|
||||
|
||||
@@ -14,28 +14,16 @@ fn action_ids(s: &mut UiScript) -> Vec<u32> {
|
||||
fn shipped_action_bar_drives_end_to_end() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in ["Cooldown.xml", "ActionBar.xml"] {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(
|
||||
report.errors.is_empty(),
|
||||
"{file}: loader errors: {:?}",
|
||||
report.errors
|
||||
);
|
||||
if file == "ActionBar.xml" {
|
||||
assert_eq!(
|
||||
report.frames, 59,
|
||||
"bar + XP StatusBar (+ its numerals overlay) + exhaustion tick + max-level rail + art frame + 12 buttons (each with a Cooldown child) + 2 page buttons + the performance meter and its hover button, \
|
||||
super::test_ui::load_ui(&s, "Cooldown.xml");
|
||||
// `load_ui` returns the same `report.frames` the disk reader asserted on, so this
|
||||
// count is the one that always stood here — moved, not re-derived.
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
let frames = super::test_ui::load_ui(&s, "ActionBar.xml");
|
||||
assert_eq!(
|
||||
frames, 59,
|
||||
"bar + XP StatusBar (+ its numerals overlay) + exhaustion tick + max-level rail + art frame + 12 buttons (each with a Cooldown child) + 2 page buttons + the performance meter and its hover button, \
|
||||
+ BonusActionBarFrame and its 12 buttons with their Cooldown children (25 — hidden, as the reference's is; decision 1223) — and NO ReputationWatchBar: its three frames went home to the reference's own ReputationFrame.xml with 1875"
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// `ExhaustionTick_Update` indexes `ReputationWatchBar` UNGUARDED — the reference's own code,
|
||||
// safe there because `ReputationFrame.xml` is always loaded and always declares the bar. Since
|
||||
@@ -43,7 +31,7 @@ fn shipped_action_bar_drives_end_to_end() {
|
||||
// or the tick raises on its first event.
|
||||
// In manifest order: the fonts its check-box labels colour from, the panel templates those
|
||||
// boxes inherit through, then the pane.
|
||||
super::test_ui::load_ui(&s, "Fonts.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\OptionsFrameTemplates.xml");
|
||||
@@ -161,21 +149,9 @@ fn shipped_action_bar_drives_end_to_end() {
|
||||
}
|
||||
|
||||
fn load_action_bar(s: &UiScript) {
|
||||
for file in ["Cooldown.xml", "ActionBar.xml"] {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(s, &doc, &|_| None);
|
||||
assert!(
|
||||
report.errors.is_empty(),
|
||||
"{file}: loader errors: {:?}",
|
||||
report.errors
|
||||
);
|
||||
}
|
||||
super::test_ui::load_ui(s, "Cooldown.xml");
|
||||
super::test_ui::load_ui(s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
super::test_ui::load_ui(s, "ActionBar.xml");
|
||||
|
||||
// `ExhaustionTick_Update` indexes `ReputationWatchBar` UNGUARDED — the reference's own code,
|
||||
// safe there because `ReputationFrame.xml` is always loaded and always declares the bar. Since
|
||||
@@ -183,7 +159,7 @@ fn load_action_bar(s: &UiScript) {
|
||||
// or the tick raises on its first event.
|
||||
// In manifest order: the fonts its check-box labels colour from, the panel templates those
|
||||
// boxes inherit through, then the pane.
|
||||
super::test_ui::load_ui(s, "Fonts.xml");
|
||||
super::test_ui::load_ui(s, "Interface\\FrameXML\\Fonts.xml");
|
||||
super::test_ui::load_ui(s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
super::test_ui::load_ui(s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
super::test_ui::load_ui(s, r"Interface\FrameXML\OptionsFrameTemplates.xml");
|
||||
@@ -802,6 +778,7 @@ fn shipped_bag_frame_drives_end_to_end() {
|
||||
for file in BAG_UI {
|
||||
let frames = load_ui(&s, file);
|
||||
if *file == "Cooldown.xml" {
|
||||
load_ui(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
load_ui(&s, "ActionBar.xml");
|
||||
}
|
||||
if *file == "Interface\\FrameXML\\MainMenuBarBagButtons.xml" {
|
||||
@@ -1298,9 +1275,10 @@ fn the_main_bar_pages_and_a_bonus_page_still_outranks_it() {
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in [
|
||||
// Fonts first: the pane's check-box labels colour from `RED_FONT_COLOR` in their own OnLoad.
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"UIParent.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
|
||||
@@ -1430,17 +1408,9 @@ fn the_main_bar_pages_and_a_bonus_page_still_outranks_it() {
|
||||
fn bonus_bar_slides_up_with_sound_and_down_without() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in ["Cooldown.xml", "ActionBar.xml"] {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
|
||||
}
|
||||
super::test_ui::load_ui(&s, "Cooldown.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
super::test_ui::load_ui(&s, "ActionBar.xml");
|
||||
|
||||
// `ExhaustionTick_Update` indexes `ReputationWatchBar` UNGUARDED — the reference's own code,
|
||||
// safe there because `ReputationFrame.xml` is always loaded and always declares the bar. Since
|
||||
@@ -1448,7 +1418,7 @@ fn bonus_bar_slides_up_with_sound_and_down_without() {
|
||||
// or the tick raises on its first event.
|
||||
// In manifest order: the fonts its check-box labels colour from, the panel templates those
|
||||
// boxes inherit through, then the pane.
|
||||
super::test_ui::load_ui(&s, "Fonts.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\OptionsFrameTemplates.xml");
|
||||
@@ -1621,17 +1591,9 @@ fn bonus_bar_slides_up_with_sound_and_down_without() {
|
||||
fn bonus_bar_turnaround_continues_from_position() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in ["Cooldown.xml", "ActionBar.xml"] {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
|
||||
}
|
||||
super::test_ui::load_ui(&s, "Cooldown.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
super::test_ui::load_ui(&s, "ActionBar.xml");
|
||||
|
||||
// `ExhaustionTick_Update` indexes `ReputationWatchBar` UNGUARDED — the reference's own code,
|
||||
// safe there because `ReputationFrame.xml` is always loaded and always declares the bar. Since
|
||||
@@ -1639,7 +1601,7 @@ fn bonus_bar_turnaround_continues_from_position() {
|
||||
// or the tick raises on its first event.
|
||||
// In manifest order: the fonts its check-box labels colour from, the panel templates those
|
||||
// boxes inherit through, then the pane.
|
||||
super::test_ui::load_ui(&s, "Fonts.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\OptionsFrameTemplates.xml");
|
||||
@@ -1688,17 +1650,9 @@ fn bonus_bar_turnaround_continues_from_position() {
|
||||
fn the_page_arrows_do_not_steal_each_other_s_clicks() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in ["Cooldown.xml", "ActionBar.xml"] {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
|
||||
}
|
||||
super::test_ui::load_ui(&s, "Cooldown.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
super::test_ui::load_ui(&s, "ActionBar.xml");
|
||||
|
||||
// `ExhaustionTick_Update` indexes `ReputationWatchBar` UNGUARDED — the reference's own code,
|
||||
// safe there because `ReputationFrame.xml` is always loaded and always declares the bar. Since
|
||||
@@ -1706,7 +1660,7 @@ fn the_page_arrows_do_not_steal_each_other_s_clicks() {
|
||||
// or the tick raises on its first event.
|
||||
// In manifest order: the fonts its check-box labels colour from, the panel templates those
|
||||
// boxes inherit through, then the pane.
|
||||
super::test_ui::load_ui(&s, "Fonts.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\OptionsFrameTemplates.xml");
|
||||
|
||||
@@ -12,7 +12,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
/// Fonts then BasicControls — the manifest's own order, and the whole dependency this file has.
|
||||
fn basic_controls() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
s.resolve();
|
||||
|
||||
@@ -11,7 +11,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -26,10 +26,10 @@ fn harness() -> UiScript {
|
||||
// under a timed aura. Our copy carried them as `X = X or "%d s"` fallbacks; the reference's
|
||||
// file formats them straight and `format(nil, …)` raises.
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Fonts.xml"); // NORMAL/HIGHLIGHT_FONT_COLOR + the FontStrings' faces
|
||||
// `GameTooltip`, which the reference's BuffButton_Update indexes on EVERY repaint to ask
|
||||
// `IsOwned(this)` (BuffFrame.lua l.104) — not just on hover. Ours guarded it; the reference
|
||||
// does not, so a session without the tooltip loses the whole repaint.
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); // NORMAL/HIGHLIGHT_FONT_COLOR + the FontStrings' faces
|
||||
// `GameTooltip`, which the reference's BuffButton_Update indexes on EVERY repaint to ask
|
||||
// `IsOwned(this)` (BuffFrame.lua l.104) — not just on hover. Ours guarded it; the reference
|
||||
// does not, so a session without the tooltip loses the whole repaint.
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
// `SecondsToTimeAbbrev`, which 1.12 keeps in UIParent.lua and so do we since window 18.
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
@@ -38,6 +38,7 @@ fn harness() -> UiScript {
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
load_xml(&s, "ActionBar.xml"); // BENILLA_FALLBACK_ICON (the unknown-icon fallback)
|
||||
// The timer switch, PLANTED ON — not the shipped value. 1.12 declares it in
|
||||
// UIOptionsFrame.lua (default "0"); we have no counterpart to that file, so it lives with the
|
||||
|
||||
@@ -15,8 +15,12 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn harness() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "CastingBar.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
// The stock file writes `CastingBarText:SetText(FAILED)` rather than a literal, so the window
|
||||
// needs GlobalStrings — which the manifest has above it, and which our own retired
|
||||
// `CastingBar.xml` did not need because it hardcoded "Failed".
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\CastingBarFrame.xml");
|
||||
s
|
||||
}
|
||||
|
||||
@@ -381,9 +385,9 @@ fn bottom(s: &UiScript, name: &str) -> f64 {
|
||||
fn managed_positions_track_the_bottom_bar_stack() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "CastingBar.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\CastingBarFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit the chat menus build from
|
||||
load_xml(&s, "ChatFrame.xml");
|
||||
|
||||
|
||||
@@ -1198,7 +1198,7 @@ fn an_addons_tab_click_selects_through_the_generic_entry_point() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
// The reference's `PanelTemplates_SelectTab` ends with `if GameTooltip:IsOwned(tab)` —
|
||||
|
||||
@@ -39,7 +39,7 @@ fn bake_strings(s: &UiScript) {
|
||||
fn chat_with_menu() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"UIParent.xml",
|
||||
|
||||
@@ -17,7 +17,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn chat_ui() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"UIParent.xml",
|
||||
|
||||
@@ -12,7 +12,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
/// The full ChatFrame stack (fonts first, so the FontString's `inherits="ChatFontNormal"` resolves).
|
||||
fn chat_frame() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
// GameTooltip.xml + UIDropDownMenu.xml are real RUNTIME dependencies of the chat tabs since
|
||||
// decision 1589: a left click closes any open menu (`CloseDropDownMenus`, the reference's own
|
||||
// first move in `FCF_Tab_OnClick`) and a right click opens the window's options menu.
|
||||
@@ -241,7 +241,7 @@ fn chat_click_dismisses_a_stuck_spell_but_not_an_item() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for f in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
@@ -978,7 +978,7 @@ fn the_chat_menu_builds_its_rows_on_the_references_kit() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for f in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -22,7 +22,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn ui_with_the_cinematic_frame() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "MoneyFrame.xml"); // StaticPopup's money row, or UiPanels errors at load
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
|
||||
@@ -17,7 +17,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn picker() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"UIParent.xml",
|
||||
|
||||
@@ -18,7 +18,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn load_combat_text() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "CombatText.xml");
|
||||
s.run("SHOW_COMBAT_TEXT = \"1\"; CombatText_UpdateDisplayedMessages()")
|
||||
|
||||
@@ -18,7 +18,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn load_combo_frame() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
|
||||
@@ -10,7 +10,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -55,7 +55,7 @@ fn one_item_backpack(item_id: u32, name: &str, quality: u32) -> ContainerState {
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -49,7 +49,7 @@ const ROOM_UI: &[&str] = &[
|
||||
/// cannot.
|
||||
fn load_room(s: &UiScript) {
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -20,7 +20,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
/// which GameTooltip.xml declares.
|
||||
fn load_dropdown_kit(s: &UiScript) {
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
|
||||
@@ -10,7 +10,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -15,7 +15,7 @@ fn harness() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for f in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
@@ -387,7 +387,7 @@ fn the_quest_tracker_stacks_below_the_durability_guy() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for f in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -15,7 +15,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -39,7 +39,7 @@ fn info_and_error_messages_stack_hold_and_expire() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml");
|
||||
|
||||
// Empty at load. The frame itself is shown — a MessageFrame with nothing to say simply draws
|
||||
@@ -109,7 +109,7 @@ fn an_error_toast_draws_over_an_open_panel_window() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
|
||||
@@ -178,7 +178,7 @@ fn escape_is_consumed_by_a_focused_editbox_and_leaves_windows_open() {
|
||||
fn escape_closes_the_options_window_before_opening_the_menu() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
@@ -478,7 +478,7 @@ fn escape_ladder_targeting_rung_after_cast_before_windows() {
|
||||
fn an_addon_frame_registered_in_uispecialframes_closes_on_escape() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -15,12 +15,13 @@ fn exp_bar_harness() -> UiScript {
|
||||
// is the reference's own since 1751, and a hand-rolled reader here would look for it in the
|
||||
// wrong store — and would leave its `<Script file="TextStatusBar.lua"/>` unresolved besides.
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
|
||||
|
||||
@@ -40,7 +40,7 @@ fn load_inline(s: &UiScript, xml: &str) {
|
||||
fn harness() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
@@ -399,7 +399,7 @@ fn the_reference_on_vertical_scroll_path_runs_once_a_scroll_child_exists() {
|
||||
#[test]
|
||||
fn scrolling_edit_helpers_answer_bare_calls_from_a_handler() {
|
||||
let s = UiScript::new().unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
|
||||
@@ -15,13 +15,14 @@ use super::test_ui::load_ui_strict as load_xml;
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
// The row right-click menu is the shared UnitPopup "FRIEND" menu, so the window's slice of
|
||||
// the manifest includes it (it loads well before FriendsFrame.xml in the real order).
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "UnitPopup.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
// The guild pane's frames inherit the reference's shared UIPanelButtonTemplate /
|
||||
|
||||
@@ -37,7 +37,7 @@ fn harness_with(extra: &[&str]) -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
let files: Vec<&str> = [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
// `GameMenuFrame` and the panels it opens declare `parent="UIParent"`, resolved at LOAD
|
||||
// (decision 1734) — UIParent must already be there, as it is in the manifest.
|
||||
"UIParent.xml",
|
||||
@@ -662,7 +662,11 @@ fn the_bag_row_greys_under_the_menu_without_any_of_it_disappearing() {
|
||||
// bar anchors into and seats itself above (`BenillaActionBarArt_SeatAbove`, nil-guarded in
|
||||
// BagFrame.xml precisely because most harnesses load no bar).
|
||||
let mut s = bag_harness_with(
|
||||
&["Cooldown.xml", "ActionBar.xml"],
|
||||
&[
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
],
|
||||
&["Interface\\FrameXML\\MerchantFrame.xml"],
|
||||
);
|
||||
s.set_money(0);
|
||||
|
||||
@@ -70,8 +70,8 @@ fn setup() -> UiScript {
|
||||
// loader warns on a key with no global behind it rather than failing — which is exactly the
|
||||
// kind of warning `load_ui_no_warnings` is here to catch (decision 1838).
|
||||
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
|
||||
load_xml(&s, "Fonts.xml"); // ITEM_QUALITY_COLORS + GameFontNormalSmall
|
||||
// The loot window's slots inherit it — the same dependency the inspect window needed (1832).
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); // ITEM_QUALITY_COLORS + GameFontNormalSmall
|
||||
// The loot window's slots inherit it — the same dependency the inspect window needed (1832).
|
||||
load_xml(&s, r"Interface\FrameXML\ItemButtonTemplate.xml");
|
||||
// `UIPanelCloseButton`, which the loot window's four close buttons inherit.
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
@@ -80,6 +80,7 @@ fn setup() -> UiScript {
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml"); // PASS/NEED/GREED + item hovers
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
load_xml(&s, "ActionBar.xml"); // BENILLA_FALLBACK_ICON (the in-flight icon fallback) —
|
||||
// buff_tests.rs's own load-order precedent for this same global.
|
||||
s
|
||||
@@ -444,9 +445,12 @@ fn in_flight_roll_does_not_error_and_falls_back() {
|
||||
/// exactly how a roll dialog that shipped "green" reached the director showing a `?` icon and a
|
||||
/// blank name.
|
||||
///
|
||||
/// The guarantee this pins is therefore not an ordering but a *repair*: whenever a roll's display
|
||||
/// identity changes under an open frame — the snapshot finally arriving, or a late item template —
|
||||
/// `UPDATE_LOOT_ROLL(rollID)` repaints it.
|
||||
/// What this pins now is the DEFECT, deliberately: a roll whose template lands after the frame is
|
||||
/// up keeps its `?`. Our retired frame repaired it from an `UPDATE_LOOT_ROLL(rollID)`; the stock
|
||||
/// frame has no such seam and never listened, so 1883 stopped firing an event the 1.12 client does
|
||||
/// not have. Decision 1838 carries the real fix — hold the roll until its template resolves, which
|
||||
/// is the reference's own invariant. This test goes green-to-red the day that lands, which is the
|
||||
/// point of writing it against the current behaviour rather than deleting it.
|
||||
#[test]
|
||||
fn a_roll_that_opens_before_its_snapshot_stays_blank() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
@@ -473,11 +477,12 @@ fn a_roll_that_opens_before_its_snapshot_stays_blank() {
|
||||
|
||||
// ...and then the snapshot carrying it lands.
|
||||
s.set_loot_rolls(rolls());
|
||||
s.fire_event("UPDATE_LOOT_ROLL", vec![ScriptValue::Int(7)]);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
// **The repaint does not happen, and this is the migration's one real loss.** Our retired file
|
||||
// split the paint out of `OnShow` precisely so `UPDATE_LOOT_ROLL` could re-enter it; the
|
||||
// split the paint out of `OnShow` precisely so an `UPDATE_LOOT_ROLL` could re-enter it — an
|
||||
// event the 1.12 client does not have, which 1883 stopped firing once the stock frame proved
|
||||
// nothing listened for it. The
|
||||
// reference has no such seam, because its `GetLootRollItemInfo` reads live C state that is
|
||||
// already populated when `START_LOOT_ROLL` fires. Ours reads a pushed snapshot, and while
|
||||
// `feed_loot_rolls` does push it BEFORE firing, the item template can still be in flight.
|
||||
@@ -506,9 +511,8 @@ fn a_roll_that_opens_before_its_snapshot_stays_blank() {
|
||||
}),
|
||||
"and no icon arrives either — same missing repaint, same one cause"
|
||||
);
|
||||
// An update for a roll this frame does not hold still leaves it alone and still does not
|
||||
// A snapshot naming a roll this frame does not hold still leaves it alone and still does not
|
||||
// error — the half of this test that survives the migration intact.
|
||||
s.fire_event("UPDATE_LOOT_ROLL", vec![ScriptValue::Int(999)]);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert_eq!(
|
||||
s.eval::<Option<String>>("return GroupLootFrame1Name:GetText()")
|
||||
@@ -532,7 +536,7 @@ fn managed_positions_engage_for_the_bare_frame_name() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
// UiPanels.xml before GroupLootFrame.xml, mirroring the shipped manifest order
|
||||
// (`ui_script::load_default_ui`): the roll file's CONFIRM_LOOT_ROLL entry indexes
|
||||
|
||||
@@ -203,12 +203,13 @@ fn setup() -> UiScript {
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "UnitPopup.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
//! anywhere in that chain files every ticket under the wrong heading with nothing on screen to
|
||||
//! show for it.
|
||||
|
||||
use benilla_ui::script::{GmTicketIntent, GmTicketWrite, ScriptValue, UiScript};
|
||||
use benilla_ui::script::{GmTicketIntent, ScriptValue, UiScript};
|
||||
|
||||
use super::test_ui::load_ui as load_xml;
|
||||
|
||||
@@ -19,7 +19,7 @@ fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
@@ -30,7 +30,18 @@ fn setup() -> UiScript {
|
||||
// talent-wipe fixture hit this first).
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"HelpFrame.xml",
|
||||
// The stock file's `HelpFrame_OnShow` calls `UpdateMicroButtons()` before
|
||||
// `GetGMStatus()`, so without the micro row the OnShow raises and the status ask never
|
||||
// happens. Ours never called it. MicroMenu needs the bar's button kit under it.
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
"MicroMenu.xml",
|
||||
// `TicketStatusFrame_OnEvent` re-anchors TemporaryEnchantFrame before it arms the repoll
|
||||
// timer, so without BuffFrame the handler raises and `refreshTime` is never set. This is
|
||||
// the one ordering requirement benilla.toc already calls out for this window.
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
"Interface\\FrameXML\\HelpFrame.xml",
|
||||
] {
|
||||
load_xml(&s, file);
|
||||
}
|
||||
@@ -70,107 +81,6 @@ fn open_ticket_args(
|
||||
]
|
||||
}
|
||||
|
||||
/// **The whole point of decision 1687: one click from Home to the text box.**
|
||||
///
|
||||
/// The category list and the per-category blurb page are gone, so this walks what is left — open
|
||||
/// the window, press the one button, type, submit — and asserts the ticket goes out **uncategorised
|
||||
/// (0)**. That last part is the assertion that actually pins 1687: 0 is what makes a GM's queue read
|
||||
/// "Unknown" instead of a heading the player never chose.
|
||||
#[test]
|
||||
fn one_click_from_home_files_an_uncategorised_ticket() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = setup();
|
||||
s.run("ToggleHelpFrame()").unwrap();
|
||||
let _ = s.take_gm_ticket_intents(); // the OnShow GetGMStatus
|
||||
|
||||
assert!(
|
||||
s.eval::<bool>("return HelpFrameHome:IsVisible()").unwrap(),
|
||||
"the window opens on Home"
|
||||
);
|
||||
s.run("HelpFrameHomeIssues:Click()").unwrap();
|
||||
assert!(
|
||||
s.eval::<bool>("return HelpFrameOpenTicket:IsVisible()")
|
||||
.unwrap(),
|
||||
"one click lands on the editor — no category page in between"
|
||||
);
|
||||
|
||||
s.run("HelpFrameOpenTicketText:SetText(\"My sword vanished.\")")
|
||||
.unwrap();
|
||||
s.run("HelpFrameOpenTicketSubmit_OnClick()").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
s.take_gm_ticket_intents(),
|
||||
vec![GmTicketIntent::Write(GmTicketWrite {
|
||||
category: 0,
|
||||
text: "My sword vanished.".into(),
|
||||
is_new: true,
|
||||
})],
|
||||
"a create files under 0 — \"Unknown\" in the GM's queue"
|
||||
);
|
||||
}
|
||||
|
||||
/// **Auto-Unstuck sits on Home now**, beside the ticket button — it used to be the Stuck category's
|
||||
/// action button, three pages down, which is nowhere.
|
||||
///
|
||||
/// Clicked through the real button rather than by calling the handler, because the button's
|
||||
/// existence on Home *is* the change.
|
||||
#[test]
|
||||
fn auto_unstuck_is_a_button_on_home() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = setup();
|
||||
s.run("ToggleHelpFrame()").unwrap();
|
||||
s.run("BenillaHelpFrameHomeUnstick:Click()").unwrap();
|
||||
assert_eq!(s.take_stuck_casts(), 1);
|
||||
assert!(
|
||||
!s.eval::<bool>("return HelpFrame:IsVisible()").unwrap(),
|
||||
"and it closes the window"
|
||||
);
|
||||
}
|
||||
|
||||
/// **The two Home buttons do not overlap, and both fit their labels.**
|
||||
///
|
||||
/// The layout is ours (the reference had one button here), and its whole trick is that the left
|
||||
/// button hangs by its TOPRIGHT and the right one off that fixed edge, so fitting them to their
|
||||
/// text grows them *away* from the gutter. Nothing else in the suite would notice them landing on
|
||||
/// top of each other: a script error inside `OnShow` is swallowed into the VM's error record, so a
|
||||
/// fit that never ran still leaves every other test green.
|
||||
#[test]
|
||||
fn the_two_home_buttons_sit_side_by_side_without_overlapping() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = setup();
|
||||
s.run("ToggleHelpFrame()").unwrap();
|
||||
s.resolve();
|
||||
|
||||
let edge = |frame: &str, edge: &str| {
|
||||
s.eval::<f32>(&format!("return {frame}:Get{edge}()"))
|
||||
.unwrap_or_else(|e| panic!("{frame}:Get{edge}(): {e}"))
|
||||
};
|
||||
let (ticket_l, ticket_r) = (
|
||||
edge("HelpFrameHomeIssues", "Left"),
|
||||
edge("HelpFrameHomeIssues", "Right"),
|
||||
);
|
||||
let (stuck_l, stuck_r) = (
|
||||
edge("BenillaHelpFrameHomeUnstick", "Left"),
|
||||
edge("BenillaHelpFrameHomeUnstick", "Right"),
|
||||
);
|
||||
|
||||
assert!(
|
||||
ticket_r <= stuck_l,
|
||||
"the buttons overlap: ticket ends at {ticket_r}, unstick starts at {stuck_l}"
|
||||
);
|
||||
assert!(
|
||||
ticket_r > ticket_l && stuck_r > stuck_l,
|
||||
"both buttons must have width: {ticket_l}..{ticket_r}, {stuck_l}..{stuck_r}"
|
||||
);
|
||||
// The fit ran: an unfitted button keeps its authored 250px, and "Auto-Unstuck" is nowhere near
|
||||
// that wide. This is what catches an OnShow that silently errored out.
|
||||
assert!(
|
||||
stuck_r - stuck_l < 200.0,
|
||||
"the unstick button was never fitted to its label (width {})",
|
||||
stuck_r - stuck_l
|
||||
);
|
||||
}
|
||||
|
||||
/// **`UPDATE_TICKET`'s two faces.** With a ticket the editor becomes an editor (Save Changes /
|
||||
/// Exit); with the bare `arg1 = 0` it goes back to being a form (Submit / Cancel). The zero leg is
|
||||
/// the one that would silently rot: it is the ordinary answer, so a window stuck in edit mode
|
||||
@@ -219,81 +129,6 @@ fn an_open_ticket_turns_the_form_into_an_editor_and_a_zero_turns_it_back() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The Submit button picks its verb from the window's own `hasTicket` — an edit after an answer is
|
||||
/// an UPDATE, a submit before one is a CREATE — **and the two legs send different categories**.
|
||||
///
|
||||
/// That asymmetry is decision 1687's real content, and it is the half a suite would otherwise miss.
|
||||
/// A create has nothing to go on and sends 0. An edit sends the server's own category straight
|
||||
/// back, because `HandleGMTicketUpdateTextOpcode` overwrites the field with whatever arrives: send
|
||||
/// 0 there and a GM who re-filed the ticket onto a real heading watches it drop to "Unknown" the
|
||||
/// moment the player fixes a typo. Nothing on screen would ever show that.
|
||||
#[test]
|
||||
fn a_create_files_under_zero_and_an_edit_gives_the_server_its_category_back() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = setup();
|
||||
s.run("ShowUIPanel(HelpFrame) HelpFrame_ShowFrame(\"OpenTicket\")")
|
||||
.unwrap();
|
||||
let _ = s.take_gm_ticket_intents(); // the OnShow GetGMStatus
|
||||
|
||||
// A stale `ticketType` must NOT leak into a create — the window has no picker, so a create is
|
||||
// uncategorised no matter what is lying around on the frame.
|
||||
s.run("HelpFrameOpenTicket.ticketType = 3 HelpFrameOpenTicketText:SetText(\"a\")")
|
||||
.unwrap();
|
||||
s.run("HelpFrameOpenTicketSubmit_OnClick()").unwrap();
|
||||
let intents = s.take_gm_ticket_intents();
|
||||
let [GmTicketIntent::Write(create)] = intents.as_slice() else {
|
||||
panic!("expected exactly one write, got {intents:?}");
|
||||
};
|
||||
assert!(create.is_new, "no ticket known yet — this is a create");
|
||||
assert_eq!(create.category, 0, "a create is uncategorised");
|
||||
|
||||
// The server answers: this ticket is filed under 4 (Item) — a GM moved it there.
|
||||
s.fire_event("UPDATE_TICKET", open_ticket_args(4, "a", 0.1, 0.2, 0.01));
|
||||
s.run("HelpFrameOpenTicketText:SetText(\"a, still\")")
|
||||
.unwrap();
|
||||
s.run("HelpFrameOpenTicketSubmit_OnClick()").unwrap();
|
||||
let intents = s.take_gm_ticket_intents();
|
||||
let [GmTicketIntent::Write(edit)] = intents.as_slice() else {
|
||||
panic!("expected exactly one write, got {intents:?}");
|
||||
};
|
||||
assert!(!edit.is_new, "a ticket is known — this is an update");
|
||||
assert_eq!(edit.text, "a, still");
|
||||
assert_eq!(
|
||||
edit.category, 4,
|
||||
"the edit must hand the GM's own category back, not overwrite it with 0"
|
||||
);
|
||||
}
|
||||
|
||||
/// **The queue gate.** `UPDATE_GM_STATUS(0)` takes the petition queue down, and asking for the
|
||||
/// editor then closes the window and says why instead of showing a form that cannot submit.
|
||||
/// The `1` leg must put it back — a one-way gate would lock the player out for the session.
|
||||
#[test]
|
||||
fn a_downed_queue_refuses_the_editor_and_says_so_and_comes_back_up() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = setup();
|
||||
s.fire_event("UPDATE_GM_STATUS", vec![ScriptValue::Int(0)]);
|
||||
s.run("ShowUIPanel(HelpFrame) HelpFrame_ShowFrame(\"OpenTicket\")")
|
||||
.unwrap();
|
||||
assert!(
|
||||
!s.eval::<bool>("return HelpFrame:IsVisible()").unwrap(),
|
||||
"the window closes"
|
||||
);
|
||||
assert!(
|
||||
s.eval::<bool>("return StaticPopup_Visible(\"HELP_TICKET_QUEUE_DISABLED\") ~= nil")
|
||||
.unwrap(),
|
||||
"and the dialog says why"
|
||||
);
|
||||
|
||||
s.fire_event("UPDATE_GM_STATUS", vec![ScriptValue::Int(1)]);
|
||||
s.run("ShowUIPanel(HelpFrame) HelpFrame_ShowFrame(\"OpenTicket\")")
|
||||
.unwrap();
|
||||
assert!(
|
||||
s.eval::<bool>("return HelpFrameOpenTicket:IsVisible()")
|
||||
.unwrap(),
|
||||
"queue back up, editor opens"
|
||||
);
|
||||
}
|
||||
|
||||
/// The toast follows the ticket: up while one is open, gone when it is not. It is the only thing
|
||||
/// on screen that says a ticket exists at all once the window is closed.
|
||||
#[test]
|
||||
@@ -345,23 +180,6 @@ fn the_toast_repolls_the_server_only_after_the_full_interval() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Abandoning goes through a confirm, and only its Yes sends the delete. A dialog whose Yes did
|
||||
/// nothing would look identical to one that worked, right up until the ticket reappeared.
|
||||
#[test]
|
||||
fn abandoning_a_ticket_confirms_first_and_only_yes_sends_it() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = setup();
|
||||
s.run("StaticPopup_Show(\"HELP_TICKET_ABANDON_CONFIRM\")")
|
||||
.unwrap();
|
||||
s.run("StaticPopup_OnClick(StaticPopup1, 2)").unwrap();
|
||||
assert!(s.take_gm_ticket_intents().is_empty(), "No sends nothing");
|
||||
|
||||
s.run("StaticPopup_Show(\"HELP_TICKET_ABANDON_CONFIRM\")")
|
||||
.unwrap();
|
||||
s.run("StaticPopup_OnClick(StaticPopup1, 1)").unwrap();
|
||||
assert_eq!(s.take_gm_ticket_intents(), vec![GmTicketIntent::Delete]);
|
||||
}
|
||||
|
||||
/// `ToggleHelpFrame` is the micro button's whole wiring, and opening the window asks the server
|
||||
/// for the queue status — without that ask the gate above would run on a stale assumption for the
|
||||
/// life of the session.
|
||||
@@ -385,88 +203,3 @@ fn toggling_the_window_opens_it_and_asks_for_the_queue_status() {
|
||||
s.run("ToggleHelpFrame()").unwrap();
|
||||
assert!(!s.eval::<bool>("return HelpFrame:IsVisible()").unwrap());
|
||||
}
|
||||
|
||||
/// **The retail-only text is gone from the Home page** (director's call, 2026-08-29).
|
||||
///
|
||||
/// The page is entirely GlobalStrings off the player's own chain, so three of its strings still
|
||||
/// pointed at `worldofwarcraft.com` — a dead PvP-policy link, and a closing paragraph directing the
|
||||
/// player to Blizzard's forums and policy pages. The window overrides them before the markup
|
||||
/// resolves.
|
||||
///
|
||||
/// This test exists because the failure mode is *invisible in a test suite*: nothing breaks if the
|
||||
/// override is dropped, the page simply starts advertising dead links again. Asserted on the
|
||||
/// rendered FontStrings rather than the globals, so it also catches the markup being re-pointed at
|
||||
/// a different key.
|
||||
#[test]
|
||||
fn the_home_page_advertises_no_dead_retail_links() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let s = setup();
|
||||
s.run("ShowUIPanel(HelpFrame) HelpFrame_ShowFrame(\"Home\")")
|
||||
.unwrap();
|
||||
|
||||
for frame in [
|
||||
"HelpFrameHomePvpPolicyUrl",
|
||||
"HelpFrameHomeText2",
|
||||
"HelpFrameHomeIssue3",
|
||||
"HelpFrameHomeText1",
|
||||
] {
|
||||
let text = s
|
||||
.eval::<String>(&format!("return {frame}:GetText() or \"\""))
|
||||
.unwrap()
|
||||
.to_lowercase();
|
||||
for dead in [
|
||||
"worldofwarcraft.com",
|
||||
"http://",
|
||||
"www.",
|
||||
".shtml",
|
||||
"the forums",
|
||||
] {
|
||||
assert!(
|
||||
!text.contains(dead),
|
||||
"{frame} still carries retail-only text ({dead:?}): {text:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The PvP guidance itself is KEPT — only the sentence introducing the link went. A test that
|
||||
// let the whole bullet be emptied would pass on an over-correction, which is the other way to
|
||||
// get this wrong.
|
||||
let pvp = s
|
||||
.eval::<String>("return HelpFrameHomeIssue3:GetText()")
|
||||
.unwrap();
|
||||
assert!(
|
||||
pvp.contains("PVP game mechanics"),
|
||||
"the PvP guidance must survive the link's removal: {pvp:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// **The geometry oracle** (decision 0675): every element the transcription shares with the
|
||||
/// reference file carries the reference's own `<AbsDimension>` numbers.
|
||||
///
|
||||
/// **Verified to fail**, as 0675 requires, and re-verified after 1687 lowered `min_compared` from
|
||||
/// 60 to 28: nudging `TicketStatusFrame`'s width 208 → 209 reports
|
||||
/// `TicketStatusFrame: ours [(209.0, 52.0), …] != ref [(208.0, 52.0), …]` and fails. Re-checking
|
||||
/// that after shrinking the floor is the point — a guard whose floor drops below what it actually
|
||||
/// compares stops guarding and never says so.
|
||||
#[test]
|
||||
fn the_windows_geometry_matches_the_reference_file() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let Some(reference) = super::framexml_diff::reference("HelpFrame.xml") else {
|
||||
return; // no install — this test is a no-op rather than a failure
|
||||
};
|
||||
/// Deliberate deviations, by REFERENCE name. Each earns its reason here; a tolerance would
|
||||
/// let a real difference hide, so this is a list and stays a list.
|
||||
const EXPECTED: &[&str] = &[
|
||||
// The ticket editor's scroll frame carries the house kit's shared trough in place of the
|
||||
// three loose bar textures the reference hangs beside it (HelpFrame.xml's header).
|
||||
"HelpFrameOpenTicketScrollFrame",
|
||||
// Home's button row is ours: the reference has one button centred under the text, we have
|
||||
// two, so the left one hangs by its TOPRIGHT at -6 rather than centred at 0. Its SIZE still
|
||||
// matches, which is why only the offset differs.
|
||||
"HelpFrameHomeIssues",
|
||||
];
|
||||
// 1687 deleted the category list and the per-category page, and roughly seventy named elements
|
||||
// went with them — hence 28 rather than 60. The floor still has to bite: it is what stops this
|
||||
// guard quietly comparing nothing after the next window-shrinking change.
|
||||
super::framexml_diff::assert_geometry_matches("HelpFrame.xml", &reference, EXPECTED, 28);
|
||||
}
|
||||
|
||||
@@ -366,7 +366,7 @@ fn shown_inspect_honor_page() -> UiScript {
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
s.run(RANK_GLOBALS).unwrap();
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
// `TEXT`, the stock level line's formatter.
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"MoneyFrame.xml",
|
||||
|
||||
@@ -114,7 +114,7 @@ fn armed() -> UiScript {
|
||||
// `PLAYER_LEVEL`, the template the stock level line formats through — the reference keeps its
|
||||
// strings here, and our retired file had the sentence written into it (1832).
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
// `TEXT`, which the stock `InspectPaperDollFrame_SetLevel` formats its level line through.
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
@@ -161,7 +161,7 @@ fn shipped_inspect_frame_loads_clean() {
|
||||
// `PLAYER_LEVEL`, the template the stock level line formats through — the reference keeps its
|
||||
// strings here, and our retired file had the sentence written into it (1832).
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
// `TEXT`, which the stock `InspectPaperDollFrame_SetLevel` formats its level line through.
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
|
||||
@@ -87,7 +87,7 @@ fn the_self_menu_row_gates_on_the_binding_and_confirms_before_sending() {
|
||||
)
|
||||
.unwrap();
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"UIParent.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
|
||||
@@ -43,7 +43,7 @@ pub(crate) fn harness() -> UiScript {
|
||||
s.register_bindings(&cmds);
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -89,7 +89,7 @@ fn harness_with(player: &str) -> UiScript {
|
||||
// reader cannot name it (decision 1848).
|
||||
for file in [
|
||||
r"Interface\FrameXML\GlobalStrings.lua",
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"Interface\\FrameXML\\BasicControls.xml", // `TEXT`
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
|
||||
@@ -279,7 +279,7 @@ mod tests {
|
||||
assert_eq!(toc.directive("Title"), Some("benilla"));
|
||||
assert_eq!(
|
||||
toc.files.first().map(String::as_str),
|
||||
Some("Fonts.xml"),
|
||||
Some("Interface\\FrameXML\\Fonts.xml"),
|
||||
"the font registry is the manifest's first entry — the loader splits there"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,28 +22,19 @@ fn harness_with(extra: &[&str]) -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
let files: Vec<&str> = [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
"MicroMenu.xml",
|
||||
]
|
||||
.into_iter()
|
||||
.chain(extra.iter().copied())
|
||||
.collect();
|
||||
// Through `test_ui::load_ui`, not off disk: the list above now names a chain file, and a
|
||||
// reader that joins `assets/ui` cannot resolve one (the wall 1838 hit, and 1887 again).
|
||||
for file in files {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(
|
||||
report.errors.is_empty(),
|
||||
"{file}: loader errors: {:?}",
|
||||
report.errors
|
||||
);
|
||||
super::test_ui::load_ui(&s, file);
|
||||
}
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
s
|
||||
|
||||
@@ -21,7 +21,7 @@ fn minimap_zoom_buttons_resync_when_switching_inside_and_outside() {
|
||||
s.run("function GetMinimapZoneText() return '' end")
|
||||
.unwrap();
|
||||
s.run("function PlaySound() end").unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
// The shipped load order provides GameTooltip before the cluster; Minimap_Update's tooltip
|
||||
// half (the PVP tint slice, decision 0287) touches it from OnLoad on.
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -86,7 +86,7 @@ fn tracking_frame_follows_get_tracking_texture_across_player_auras_changed() {
|
||||
s.run("function GetMinimapZoneText() return '' end")
|
||||
.unwrap();
|
||||
s.run("function PlaySound() end").unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "MinimapCluster.xml");
|
||||
|
||||
@@ -132,7 +132,7 @@ fn game_time_session(hour: u32, minute: u32) -> UiScript {
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\Localization.xml");
|
||||
s.run("LocalizeFrames()").unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
// `TEXT()`, which the reference's own tooltip and label formatting passes every string through.
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -245,7 +245,7 @@ fn a_click_on_the_minimap_parks_a_centre_relative_ping_request() {
|
||||
s.run("function GetMinimapZoneText() return '' end")
|
||||
.unwrap();
|
||||
s.run("function PlaySound() end").unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "MinimapCluster.xml");
|
||||
s.resolve();
|
||||
@@ -289,7 +289,7 @@ fn get_ping_position_is_nil_until_there_is_a_ping() {
|
||||
s.run("function GetMinimapZoneText() return '' end")
|
||||
.unwrap();
|
||||
s.run("function PlaySound() end").unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "MinimapCluster.xml");
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn harness() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
// The bars' START driver: `UIParent_OnEvent`'s MIRROR_TIMER_START arm is what calls
|
||||
// `MirrorTimer_Show`, which is where the reference keeps it (UIParent.lua l.97 + l.374-377)
|
||||
// and where window 7 moved ours back to. Without this file the event reaches nothing.
|
||||
|
||||
@@ -26,7 +26,7 @@ fn harness(money: u64) -> UiScript {
|
||||
// below is deliberate rather than an artefact of an unfed VM — a flat 8px per digit makes
|
||||
// every expected number readable as `digits x 8 + icon`.
|
||||
s.set_text_measurer(Box::new(super::FixedWidthFont(8.0)));
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
|
||||
let doc = benilla_ui::framexml::parse(
|
||||
@@ -374,7 +374,7 @@ fn the_money_type_table_carries_all_seven_reference_types() {
|
||||
fn the_chains_money_input_frame_splits_an_amount_across_its_three_boxes() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let s = UiScript::new().unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml"); // COPPER_PER_GOLD / COPPER_PER_SILVER live here
|
||||
// The manifest's own order: the `.lua` brings the ten verbs, the `.xml` the template.
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyInputFrame.lua");
|
||||
|
||||
@@ -18,10 +18,11 @@ use super::test_ui::load_ui as load_xml;
|
||||
/// `ActionBar.xml` (the anchor target + shared globals both new bars need).
|
||||
fn load_action_bar(s: &UiScript) {
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
|
||||
@@ -432,10 +433,11 @@ fn multibar_hover_renders_the_buttons_own_action() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
|
||||
@@ -657,7 +659,7 @@ fn raising_a_bottom_bar_moves_the_managed_bottom_stack() {
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_action_bar(&s);
|
||||
load_xml(&s, "MultiBars.xml");
|
||||
load_xml(&s, "CastingBar.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\CastingBarFrame.xml");
|
||||
|
||||
// The pass writes the y of a frame row into its anchor; read it back off the anchor rather
|
||||
// than off a resolved rect, so a hidden cast bar answers the same as a visible one.
|
||||
|
||||
@@ -37,7 +37,7 @@ fn harness() -> UiScript {
|
||||
fn harness_on(mut s: UiScript) -> UiScript {
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
// Every panel window declares `parent="UIParent"`, resolved at LOAD — so UIParent has to
|
||||
// exist by the time they are read, exactly as it does in the manifest (decision 1734).
|
||||
"UIParent.xml",
|
||||
@@ -716,7 +716,7 @@ fn interface_harness() -> UiScript {
|
||||
load_definers(
|
||||
&s,
|
||||
&[
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
@@ -749,6 +749,7 @@ fn interface_harness() -> UiScript {
|
||||
// loaded it too.
|
||||
"Interface\\FrameXML\\ColorPickerFrame.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
|
||||
@@ -779,7 +780,7 @@ fn chat_harness() -> UiScript {
|
||||
load_definers(
|
||||
&s,
|
||||
&[
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
@@ -805,9 +806,10 @@ fn actionbars_harness() -> UiScript {
|
||||
load_definers(
|
||||
&s,
|
||||
&[
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"UIParent.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
|
||||
|
||||
@@ -43,7 +43,7 @@ fn harness() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"UIParent.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
|
||||
@@ -817,7 +817,7 @@ fn gossip_bank_option_hands_the_left_slot_to_the_bank() {
|
||||
let order_first = |bank_first: bool| {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml"); // the reference bank's slot buttons inherit it
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
|
||||
@@ -14,8 +14,9 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn harness(extra: &[&str]) -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
load_xml(&s, "ActionBar.xml");
|
||||
for f in extra {
|
||||
load_xml(&s, f);
|
||||
|
||||
@@ -13,17 +13,18 @@ fn load_pet_bar(s: &UiScript) {
|
||||
for file in [
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
"UIParent.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
|
||||
// coupling of MainMenuBar to that pane. So an action-bar harness loads it, and with it the
|
||||
// two template files its check boxes inherit through (1875).
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
r"Interface\FrameXML\OptionsFrameTemplates.xml",
|
||||
@@ -363,17 +364,18 @@ fn pet_bar_row(with_multibar: bool) -> (usize, f32) {
|
||||
for file in [
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
"UIParent.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
|
||||
// coupling of MainMenuBar to that pane. So an action-bar harness loads it, and with it the
|
||||
// two template files its check boxes inherit through (1875).
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
r"Interface\FrameXML\OptionsFrameTemplates.xml",
|
||||
@@ -654,7 +656,7 @@ fn the_keybind_pair_pushes_and_casts_without_the_clicks_forks() {
|
||||
/// prerequisite for the managed stack — for `GetBindingText`, which renders the key.
|
||||
fn load_pet_bar_with_tooltip(s: &UiScript) {
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
@@ -662,6 +664,7 @@ fn load_pet_bar_with_tooltip(s: &UiScript) {
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
|
||||
|
||||
@@ -26,7 +26,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn load_pet_frame() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
|
||||
@@ -90,7 +90,7 @@ fn setup() -> UiScript {
|
||||
// out of GlobalStrings, with no fallback of its own — `format(nil, …)` raises, and the window
|
||||
// never paints.
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
|
||||
@@ -13,7 +13,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -17,7 +17,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn questgiver_show_hide_plays_open_and_close_kits() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -79,7 +79,7 @@ fn questgiver_show_hide_plays_open_and_close_kits() {
|
||||
fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -159,7 +159,7 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
|
||||
fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -245,7 +245,7 @@ fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
|
||||
fn reward_panel_choice_click_selects_and_completes_with_zero_based_index() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -311,7 +311,7 @@ fn reward_panel_choice_click_selects_and_completes_with_zero_based_index() {
|
||||
fn greeting_goodbye_button_closes_the_window() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -355,7 +355,7 @@ fn greeting_goodbye_button_closes_the_window() {
|
||||
fn detail_panel_action_buttons_resolve_to_real_onscreen_rects() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -470,7 +470,7 @@ fn detail_panel_action_buttons_resolve_to_real_onscreen_rects() {
|
||||
fn write_on_still_fades_when_instant_text_is_off() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -534,7 +534,7 @@ fn write_on_still_fades_when_instant_text_is_off() {
|
||||
fn npc_name_reaches_the_title_bar_on_open_and_on_refresh() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -600,7 +600,7 @@ fn npc_name_reaches_the_title_bar_on_open_and_on_refresh() {
|
||||
fn greeting_panel_title_rows_grow_to_their_wrapped_titles() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -687,7 +687,7 @@ fn greeting_panel_title_rows_grow_to_their_wrapped_titles() {
|
||||
fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
|
||||
@@ -25,7 +25,7 @@ fn harness() -> UiScript {
|
||||
// the player's own strings it draws the KEY, which is exactly what a `text=` attribute does
|
||||
// with an unknown name.
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
// `SecondsToTime`, which the reference's `QuestTimerFrame_Update` formats every row with, and
|
||||
// `UIParent_ManageFramePositions`, which its OnShow/OnHide call.
|
||||
load_xml(&s, "UIParent.xml");
|
||||
|
||||
@@ -83,7 +83,7 @@ const HAMMER_LINK: &str = "|cffffffff|Hitem:2024:0:0:0|h[Militia Hammer]|h|r";
|
||||
fn shipped_questlog_frame_loads_clean() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let s = UiScript::new().unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -107,7 +107,7 @@ fn shipped_questlog_frame_drives_end_to_end() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -302,7 +302,7 @@ fn shift_click_toggles_the_watch_checkbox_and_the_tracker_hud() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -425,7 +425,7 @@ fn watch_guards_no_op_without_erroring() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -522,7 +522,7 @@ fn progress_auto_watches_for_five_minutes() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -587,7 +587,7 @@ fn the_auto_watch_flag_is_the_references_uvar_and_gates_the_watch() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -634,7 +634,7 @@ fn the_row_tag_is_its_own_right_flush_string_and_the_state_word_wins() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
// The state word comes from the reference's OWN globals (`COMPLETE`/`FAILED`) as of this row's
|
||||
// ref-verbatim tag handling — the app loads GlobalStrings.lua as `benilla.toc`'s second entry,
|
||||
// so this harness must too or the override silently reads nil.
|
||||
@@ -707,7 +707,7 @@ fn empty_quest_log_hides_rows_and_disables_abandon() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -760,7 +760,7 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -919,7 +919,7 @@ fn overflowing_detail_content_clips_to_the_scrollframe_rect() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -983,7 +983,7 @@ fn wheel_over_the_detail_pane_changes_vertical_scroll() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -1032,7 +1032,7 @@ fn selection_change_resets_detail_scroll_but_a_quest_log_update_refresh_does_not
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -1092,7 +1092,7 @@ fn reward_row_hover_serves_the_shared_item_tooltip() {
|
||||
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -1186,7 +1186,7 @@ fn popup_children_inherit_the_dialog_stratum() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
@@ -1237,7 +1237,7 @@ fn reward_rows_preview_and_post_and_a_plain_click_stays_inert() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -1320,7 +1320,7 @@ fn shift_click_on_a_title_posts_the_quest_name_with_chat_open_and_watches_with_i
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -1414,7 +1414,7 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -1491,7 +1491,7 @@ fn share_quest_is_dark_on_an_empty_log_even_in_a_party() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
@@ -1522,7 +1522,7 @@ fn share_quest_click_queues_the_selected_quests_id() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
|
||||
@@ -24,7 +24,7 @@ use super::test_ui::load_ui_strict as load_xml;
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
|
||||
@@ -221,19 +221,7 @@ mod tests {
|
||||
|
||||
let mut s = UiScript::new().expect("VM");
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
// The in-game UI materializes on world entry (1051), so a player always exists by the time the
|
||||
// manifest loads — and the stock macro window's character tab formats `UnitName("player")`
|
||||
// into its label inside its own OnLoad. A manifest load with no player is a state the client
|
||||
// never reaches (decision 1848).
|
||||
s.set_unit(
|
||||
"player",
|
||||
Some(benilla_ui::script::UnitState {
|
||||
exists: true,
|
||||
name: Some("Probefour".into()),
|
||||
level: 60,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
seat_a_player(&mut s);
|
||||
let failures = super::super::manifest::load_default_ui(&s);
|
||||
assert!(failures.is_empty(), "the default UI: {failures:#?}");
|
||||
|
||||
@@ -356,19 +344,7 @@ mod tests {
|
||||
let mut s = UiScript::new().expect("VM");
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
|
||||
// The in-game UI materializes on world entry (1051), so a player always exists by the time the
|
||||
// manifest loads — and the stock macro window's character tab formats `UnitName("player")`
|
||||
// into its label inside its own OnLoad. A manifest load with no player is a state the client
|
||||
// never reaches (decision 1848).
|
||||
s.set_unit(
|
||||
"player",
|
||||
Some(benilla_ui::script::UnitState {
|
||||
exists: true,
|
||||
name: Some("Probefour".into()),
|
||||
level: 60,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
seat_a_player(&mut s);
|
||||
let failures = super::super::manifest::load_default_ui(&s);
|
||||
assert!(failures.is_empty(), "the default UI: {failures:#?}");
|
||||
|
||||
@@ -532,19 +508,7 @@ mod tests {
|
||||
}
|
||||
let mut s = UiScript::new().expect("VM");
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
// The in-game UI materializes on world entry (1051), so a player always exists by the time the
|
||||
// manifest loads — and the stock macro window's character tab formats `UnitName("player")`
|
||||
// into its label inside its own OnLoad. A manifest load with no player is a state the client
|
||||
// never reaches (decision 1848).
|
||||
s.set_unit(
|
||||
"player",
|
||||
Some(benilla_ui::script::UnitState {
|
||||
exists: true,
|
||||
name: Some("Probefour".into()),
|
||||
level: 60,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
seat_a_player(&mut s);
|
||||
let base = super::super::manifest::load_default_ui(&s);
|
||||
assert!(base.is_empty(), "the shipped manifest itself: {base:#?}");
|
||||
s.resolve();
|
||||
@@ -697,19 +661,7 @@ mod tests {
|
||||
// global our own FrameXML defines.
|
||||
let mut s = UiScript::new().expect("VM");
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
// The in-game UI materializes on world entry (1051), so a player always exists by the time the
|
||||
// manifest loads — and the stock macro window's character tab formats `UnitName("player")`
|
||||
// into its label inside its own OnLoad. A manifest load with no player is a state the client
|
||||
// never reaches (decision 1848).
|
||||
s.set_unit(
|
||||
"player",
|
||||
Some(benilla_ui::script::UnitState {
|
||||
exists: true,
|
||||
name: Some("Probefour".into()),
|
||||
level: 60,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
seat_a_player(&mut s);
|
||||
let failures = super::super::manifest::load_default_ui(&s);
|
||||
assert!(failures.is_empty(), "the shipped manifest: {failures:#?}");
|
||||
let have: std::collections::HashSet<String> = s
|
||||
@@ -1028,6 +980,7 @@ mod tests {
|
||||
let loads = {
|
||||
let mut probe = UiScript::new().expect("VM");
|
||||
probe.set_screen_size(1024.0, 768.0);
|
||||
seat_a_player(&mut probe);
|
||||
let base = super::super::manifest::load_default_ui(&probe);
|
||||
assert!(base.is_empty(), "the shipped manifest itself: {base:#?}");
|
||||
probe.resolve();
|
||||
@@ -1457,19 +1410,7 @@ mod tests {
|
||||
|
||||
let mut s = UiScript::new().expect("VM");
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
// The in-game UI materializes on world entry (1051), so a player always exists by the time the
|
||||
// manifest loads — and the stock macro window's character tab formats `UnitName("player")`
|
||||
// into its label inside its own OnLoad. A manifest load with no player is a state the client
|
||||
// never reaches (decision 1848).
|
||||
s.set_unit(
|
||||
"player",
|
||||
Some(benilla_ui::script::UnitState {
|
||||
exists: true,
|
||||
name: Some("Probefour".into()),
|
||||
level: 60,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
seat_a_player(&mut s);
|
||||
let failures = super::super::manifest::load_default_ui(&s);
|
||||
assert!(failures.is_empty(), "the shipped manifest: {failures:#?}");
|
||||
let have: std::collections::HashSet<String> = s
|
||||
@@ -1687,7 +1628,7 @@ mod tests {
|
||||
"Interface/FrameXML/ContainerFrame.xml"
|
||||
));
|
||||
assert!(!super::is_chain_entry("BagFrame.xml"));
|
||||
assert!(!super::is_chain_entry("Fonts.xml"));
|
||||
assert!(!super::is_chain_entry("UIParent.xml"));
|
||||
}
|
||||
/// Every global function and virtual template name a manifest entry declares.
|
||||
///
|
||||
@@ -2088,6 +2029,13 @@ mod tests {
|
||||
"TextStatusBar.lua — a CVar change does not repaint the bar text",
|
||||
),
|
||||
("DISPLAY_SIZE_CHANGED", "the four paperdoll files"),
|
||||
(
|
||||
"GMSURVEY_DISPLAY",
|
||||
"HelpFrame.lua — the post-ticket survey. A real 1.12 event (fired at \
|
||||
`0x5e797b`, id 538) whose whole UI is the LoadOnDemand `Blizzard_GMSurveyUI`; \
|
||||
we have neither the producer nor the addon on the chain, and the ticket \
|
||||
flow works without it",
|
||||
),
|
||||
("ITEM_TEXT_TRANSLATION", "ItemTextFrame.lua"),
|
||||
(
|
||||
"PARTY_MEMBER_DISABLE",
|
||||
@@ -2131,17 +2079,52 @@ mod tests {
|
||||
stack.push(p);
|
||||
} else if p.extension().is_some_and(|x| x == "rs") {
|
||||
let text = std::fs::read_to_string(&p).unwrap_or_default();
|
||||
// Split so this file cannot match its own walker (see the sibling gate).
|
||||
const CALL: &str = concat!("fire_event", "(");
|
||||
let mut from = 0;
|
||||
while let Some(i) = text[from..].find("fire_event(") {
|
||||
let at = from + i + "fire_event(".len();
|
||||
let mut fires_indirectly = false;
|
||||
while let Some(i) = text[from..].find(CALL) {
|
||||
let at = from + i + CALL.len();
|
||||
from = at;
|
||||
let rest = text[at..].trim_start();
|
||||
if !rest.starts_with('"') {
|
||||
// `fire_event(event, args)` — the name reached the call through a
|
||||
// variable, so adjacency cannot see it. `ui_cast.rs` does exactly
|
||||
// this: a `match edge` yields `("SPELLCAST_INTERRUPTED", vec![])` and
|
||||
// one call site fires whatever it produced. Treated below.
|
||||
fires_indirectly = true;
|
||||
}
|
||||
if let Some(body) = rest.strip_prefix('"') {
|
||||
if let Some(end) = body.find('"') {
|
||||
fired.insert(body[..end].to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
if fires_indirectly {
|
||||
// The name reached `fire_event` through a variable. Rather than guess, we
|
||||
// match the SHAPE that produced it: `("EVENT_NAME", vec!` — a tuple whose
|
||||
// second element is the argument vector, which is how `ui_cast.rs`'s
|
||||
// `match edge` hands a name and its args to one shared call site.
|
||||
//
|
||||
// The first attempt at this counted EVERY SCREAMING_CASE literal in such a
|
||||
// file, and that swept up eighteen names off the UNPRODUCED list — a gate
|
||||
// made vacuous by its own conservatism, which is the failure mode this
|
||||
// file has now hit twice. The shape is the discipline: it admits the
|
||||
// literals that are actually being fired and nothing else.
|
||||
const SHAPE: &str = "\", vec!";
|
||||
for (i, _) in text.match_indices(SHAPE) {
|
||||
let before = &text[..i];
|
||||
let Some(q) = before.rfind('"') else { continue };
|
||||
let lit = &before[q + 1..];
|
||||
if !lit.is_empty()
|
||||
&& lit.chars().all(|c| {
|
||||
c.is_ascii_uppercase() || c == '_' || c.is_ascii_digit()
|
||||
})
|
||||
{
|
||||
fired.insert(lit.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2193,4 +2176,127 @@ mod tests {
|
||||
what it says:\n {fixed:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Seat a player before a probe loads the manifest — **what the live client always does.**
|
||||
///
|
||||
/// The in-game UI materializes on world entry (1051), so a player always exists by the time
|
||||
/// the manifest loads, and the stock macro window's character tab formats `UnitName("player")`
|
||||
/// into its label inside its own `OnLoad`. A manifest load with no player is a state the
|
||||
/// client never reaches (decision 1848) — and one a probe reaches by default, where it raises
|
||||
/// `bad argument #2 to 'format'` and looks exactly like a load failure.
|
||||
///
|
||||
/// This was five identical copies of the same six-line comment and the same seven-line seed,
|
||||
/// inlined at every probe in this file — and the sixth, `chain_gap_report`'s, did not have it,
|
||||
/// which is why that instrument could not run at all. One function is harder to forget.
|
||||
fn seat_a_player(s: &mut UiScript) {
|
||||
s.set_unit(
|
||||
"player",
|
||||
Some(benilla_ui::script::UnitState {
|
||||
exists: true,
|
||||
name: Some("Probefour".into()),
|
||||
level: 60,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// **Every event benilla fires must be an event the 1.12 client HAS** — the 1818/1819 seam,
|
||||
/// in the one direction nothing was checking.
|
||||
///
|
||||
/// [`every_event_a_chain_file_registers_has_a_producer`] runs the other way: a stock file
|
||||
/// listens, does anything fire it. This asks whether a name we fire is a 1.12 name at all.
|
||||
/// Firing a Classic Era event is invisible to every other gate — our own halves agree with
|
||||
/// each other, the name is spelled correctly, and Lua that never registers it never notices.
|
||||
/// That is exactly how `UNIT_POWER_UPDATE` (1819) survived, and how the three this test found
|
||||
/// on its first run did: `BAG_UPDATE_DELAYED` (Era-only; 1.12 has `BAG_UPDATE` alone),
|
||||
/// `LOOT_UPDATE` and `UPDATE_LOOT_ROLL` (both invented here), each fired into a room with
|
||||
/// nobody in it. Decision 1883.
|
||||
///
|
||||
/// **The oracle is the reference binary's own string table.** An event the client can
|
||||
/// dispatch is a NUL-terminated string in `WoW.exe`; a name that is not there is a name the
|
||||
/// client cannot dispatch. That is a stronger oracle than the FrameXML corpus, which only
|
||||
/// shows what the stock UI happens to consume — and which this repo has only four of the
|
||||
/// LoadOnDemand addons of, so a corpus grep alone flags real events like `CRAFT_UPDATE`.
|
||||
///
|
||||
/// Test files are skipped: `script/tests/events.rs` fires synthetic names (`E3`) at the
|
||||
/// dispatcher on purpose, and a test's own scaffolding is not a product surface.
|
||||
#[test]
|
||||
fn every_event_we_fire_is_an_event_the_reference_has() {
|
||||
let data = benilla_formats::wow_data_or_skip!();
|
||||
let exe = data.parent().expect("install root").join("WoW.exe");
|
||||
let Ok(bytes) = std::fs::read(&exe) else {
|
||||
eprintln!("skipping: no WoW.exe at {exe:?}");
|
||||
return;
|
||||
};
|
||||
|
||||
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.and_then(|p| p.parent())
|
||||
.expect("workspace root")
|
||||
.join("crates");
|
||||
let mut fired: std::collections::BTreeMap<String, String> =
|
||||
std::collections::BTreeMap::new();
|
||||
let mut stack = vec![root];
|
||||
while let Some(dir) = stack.pop() {
|
||||
for e in std::fs::read_dir(&dir).into_iter().flatten().flatten() {
|
||||
let p = e.path();
|
||||
if p.is_dir() {
|
||||
stack.push(p);
|
||||
continue;
|
||||
}
|
||||
if p.extension().is_none_or(|x| x != "rs") {
|
||||
continue;
|
||||
}
|
||||
// A test's own synthetic events are not a surface we ship.
|
||||
if p.to_string_lossy().contains("test") {
|
||||
continue;
|
||||
}
|
||||
let text = std::fs::read_to_string(&p).unwrap_or_default();
|
||||
// Split so this file cannot match its OWN walker — it did on the first run, and
|
||||
// reported four fragments of this function as ghost events.
|
||||
const CALL: &str = concat!("fire_event", "(");
|
||||
let mut from = 0;
|
||||
while let Some(i) = text[from..].find(CALL) {
|
||||
let at = from + i + CALL.len();
|
||||
from = at;
|
||||
let rest = text[at..].trim_start();
|
||||
if let Some(body) = rest.strip_prefix('"') {
|
||||
if let Some(end) = body.find('"') {
|
||||
fired
|
||||
.entry(body[..end].to_string())
|
||||
.or_insert_with(|| p.display().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
fired.len() > 100,
|
||||
"the walker found only {} fired events — it stopped matching, which would make this \
|
||||
gate silently vacuous",
|
||||
fired.len()
|
||||
);
|
||||
|
||||
let ghosts: Vec<String> = fired
|
||||
.iter()
|
||||
// A `BENILLA_`-prefixed name declares itself ours and cannot be mistaken for a 1.12
|
||||
// one — the same discipline `BENILLA_ALLOW_OWN_UI` uses. `BENILLA_QUEST_PROGRESS` is
|
||||
// the live example: an engine event our quest log registers, which exists because the
|
||||
// shipped 1.12 auto-watch chain is broken at the `QUEST_WATCH_UPDATE` arg seam. The
|
||||
// prefix is what makes an invented event honest instead of a mistake.
|
||||
.filter(|(ev, _)| !ev.starts_with("BENILLA_"))
|
||||
.filter(|(ev, _)| {
|
||||
let needle: Vec<u8> = ev.bytes().chain(std::iter::once(0)).collect();
|
||||
!bytes.windows(needle.len()).any(|w| w == needle)
|
||||
})
|
||||
.map(|(ev, at)| format!("{ev} (fired from {at})"))
|
||||
.collect();
|
||||
assert!(
|
||||
ghosts.is_empty(),
|
||||
"benilla fires {} event(s) the 1.12 client does not have — a Classic Era name, or one \
|
||||
invented here. Nothing in the stock UI can ever listen for these:\n {}",
|
||||
ghosts.len(),
|
||||
ghosts.join("\n ")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ fn harness() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for f in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -896,17 +896,14 @@ fn every_texture_frame_outranks_its_status_bars() {
|
||||
fn the_boot_phase_materializes_no_frames() {
|
||||
let mut s = benilla_ui::script::UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/ui/Fonts.xml");
|
||||
let text = std::fs::read_to_string(&dir).expect("Fonts.xml");
|
||||
let doc = benilla_ui::framexml::parse(&text).expect("parses");
|
||||
let provider = |_: &str| -> Option<Vec<u8>> { None };
|
||||
let report = benilla_ui::loader::load(&s, &doc, &provider);
|
||||
// The font registry is manifest entry 0 and comes off the chain since 1888, so this reads
|
||||
// through `test_ui::load_ui` rather than joining `assets/ui` — the file is not ours any more.
|
||||
// `load_ui` returns the same `report.frames` this asserted on and fails on any loader error.
|
||||
let frames = super::test_ui::load_ui(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
assert_eq!(
|
||||
report.frames, 0,
|
||||
"the boot-phase load materialized {} frame(s) — the login screen is meant to carry none",
|
||||
report.frames
|
||||
frames, 0,
|
||||
"the boot-phase load materialized {frames} frame(s) — the login screen is meant to carry none"
|
||||
);
|
||||
assert!(report.errors.is_empty(), "{:?}", report.errors);
|
||||
}
|
||||
|
||||
/// **The font registry alone covers the WHOLE glyph-atlas bake plan** — the property that makes
|
||||
@@ -1039,18 +1036,23 @@ fn every_shipped_font_object_is_published_as_a_lua_global() {
|
||||
let failures = super::load_default_ui(&s);
|
||||
assert!(failures.is_empty(), "loader errors: {failures:?}");
|
||||
|
||||
// Collect the declared names straight out of the shipped XML, so the sweep cannot go stale.
|
||||
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/ui");
|
||||
// Collect the declared names straight out of the MANIFEST's own entries, so the sweep cannot
|
||||
// go stale — and so it follows a file onto the chain. It swept `assets/ui` until 1888 put the
|
||||
// font registry on the chain and the count fell from 51 to 3, which is the failure this shape
|
||||
// prevents: the sweep's subject is what the manifest declares, not what happens to be ours.
|
||||
let mut names: Vec<String> = Vec::new();
|
||||
for entry in std::fs::read_dir(&dir).expect("assets/ui").flatten() {
|
||||
let path = entry.path();
|
||||
if path.extension().is_some_and(|e| e == "xml") {
|
||||
let text = std::fs::read_to_string(&path).expect("read");
|
||||
for chunk in text.split("<Font ").skip(1) {
|
||||
if let Some(rest) = chunk.split_once("name=\"") {
|
||||
if let Some((name, _)) = rest.1.split_once('"') {
|
||||
names.push(name.to_string());
|
||||
}
|
||||
for entry in &super::addons::Addon::builtin().toc.files {
|
||||
if !entry.to_ascii_lowercase().ends_with(".xml") {
|
||||
continue;
|
||||
}
|
||||
let Some(bytes) = super::test_ui::read(&entry.replace('\\', "/")) else {
|
||||
continue;
|
||||
};
|
||||
let text = benilla_ui::source::decode(&bytes);
|
||||
for chunk in text.split("<Font ").skip(1) {
|
||||
if let Some(rest) = chunk.split_once("name=\"") {
|
||||
if let Some((name, _)) = rest.1.split_once('"') {
|
||||
names.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ fn click(s: &mut UiScript, name: &str, button: &str) {
|
||||
#[test]
|
||||
fn shipped_spellbook_loads_clean() {
|
||||
let s = UiScript::new().unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
@@ -119,13 +119,14 @@ fn shipped_spellbook_loads_clean() {
|
||||
fn shipped_spellbook_drives_end_to_end() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
load_xml(&s, "ActionBar.xml");
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
|
||||
@@ -222,7 +223,7 @@ fn shipped_spellbook_shows_the_cooldown_pie() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for f in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
@@ -301,7 +302,7 @@ fn shipped_spellbook_shows_the_cooldown_pie() {
|
||||
fn shipped_spellbook_empty_slot_draws_only_the_background() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(640.0, 700.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
@@ -338,7 +339,7 @@ fn shipped_spellbook_empty_slot_draws_only_the_background() {
|
||||
#[test]
|
||||
fn set_checked_uses_blizzard_bool_coercion() {
|
||||
let s = UiScript::new().unwrap();
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "SpellBookFrame.xml");
|
||||
for (arg, want) in [
|
||||
("1", true),
|
||||
@@ -408,7 +409,7 @@ fn pet_book() -> benilla_ui::script::PetBookState {
|
||||
fn the_pet_tab_switches_books_and_renders_the_pets_spells() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
@@ -664,7 +665,7 @@ fn the_macro_editor_takes_a_shift_click_and_only_a_shift_click() {
|
||||
);
|
||||
for file in [
|
||||
r"Interface\FrameXML\GlobalStrings.lua",
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"Interface\\FrameXML\\BasicControls.xml", // `TEXT`
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
@@ -682,6 +683,7 @@ fn the_macro_editor_takes_a_shift_click_and_only_a_shift_click() {
|
||||
// The icon chooser's scroll frame inherits `ClassTrainerListScrollFrameTemplate`.
|
||||
r"Interface\FrameXML\ClassTrainerFrameTemplates.xml",
|
||||
"MicroMenu.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
|
||||
|
||||
@@ -10,7 +10,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -26,7 +26,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -12,7 +12,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -19,13 +19,16 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn harness() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "GameTooltip.xml"); // TOOLTIP_DEFAULT_* (the dropdown kit's MenuBackdrop)
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); // the unit popups' kit (TargetFrameDropDown's template)
|
||||
load_xml(&s, "UnitPopup.xml");
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
load_xml(&s, "ActionBar.xml"); // BENILLA_FALLBACK_ICON
|
||||
// Before UnitPopup: that file reads ITEM_QUALITY_COLORS at FILE SCOPE, and its
|
||||
// declarer is UIParent (ref UIParent.lua:65) since 1888.
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "UnitPopup.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BuffFrame.xml");
|
||||
|
||||
@@ -14,7 +14,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn taxi_script() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml"); // TaxiNodeOnButtonEnter's tooltip + SetTooltipMoney
|
||||
|
||||
@@ -95,7 +95,7 @@ fn load_entry(s: &UiScript, entry: &str, strict_templates: bool, no_warnings: bo
|
||||
/// One file's bytes, from whichever store its path names — the chain for a path, this crate's
|
||||
/// `assets/ui` for a bare name. Also the `<Include>` / `<Script file=>` provider, which is why it
|
||||
/// takes an already-resolved path in either space.
|
||||
fn read(req: &str) -> Option<Vec<u8>> {
|
||||
pub(super) fn read(req: &str) -> Option<Vec<u8>> {
|
||||
if super::reference_ui::is_chain_entry(req) {
|
||||
return super::reference_ui::read(req);
|
||||
}
|
||||
@@ -142,7 +142,7 @@ fn read(req: &str) -> Option<Vec<u8>> {
|
||||
/// Needs client data: open with `benilla_formats::wow_data_or_skip!()`.
|
||||
pub(super) const MERCHANT_UI: &[&str] = &[
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"Interface\\FrameXML\\BasicControls.xml", // TEXT()
|
||||
"Interface\\FrameXML\\ItemButtonTemplate.xml",
|
||||
"MoneyFrame.xml",
|
||||
@@ -154,7 +154,7 @@ pub(super) const MERCHANT_UI: &[&str] = &[
|
||||
|
||||
pub(super) const LOOT_UI: &[&str] = &[
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Fonts.xml", // ITEM_QUALITY_COLORS — the row-name palette
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"Interface\\FrameXML\\ItemButtonTemplate.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml", // StaticPopup, and the LOOT_BIND / CONFIRM_LOOT_DISTRIBUTION dialogs
|
||||
@@ -162,6 +162,11 @@ pub(super) const LOOT_UI: &[&str] = &[
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
"GameTooltip.xml", // TOOLTIP_DEFAULT_COLOR, read by the dropdown backdrop
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml", // GroupLootDropDown's OnLoad calls UIDropDownMenu_Initialize
|
||||
// `UnitPopup.xml` reads ITEM_QUALITY_COLORS at FILE SCOPE for its three loot-threshold rows,
|
||||
// exactly as the reference's own UnitPopup.lua:47-49 does — so its declarer has to precede it.
|
||||
// That declarer is UIParent (ref UIParent.lua:65); 1888 moved the table there when Fonts.xml
|
||||
// went on the chain, because the reference's Fonts.xml does not declare it.
|
||||
"UIParent.xml",
|
||||
"UnitPopup.xml",
|
||||
// …and what its rows' OnLoad calls: every `PartyMemberFrame<N>` and its pet frame runs
|
||||
// `UnitFrame_Initialize`, which lives in UnitFrame.lua and itself calls
|
||||
@@ -219,7 +224,7 @@ pub(super) const CHARACTER_UI: &[&str] = &[
|
||||
// `PaperDollFrame_OnLoad` sets seven labels from them at LOAD, and `PaperDollFrame_SetStats`
|
||||
// concatenates `SPELL_STAT0_NAME`..`4` on every repaint.
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
// `GetText` — the reference's gendered-string helper, which stock
|
||||
// `ReputationFrame.lua:65` calls for every row's standing label. Manifest line 94
|
||||
// (1875).
|
||||
@@ -253,6 +258,7 @@ pub(super) const CHARACTER_UI: &[&str] = &[
|
||||
// template and not four party member frames, so it takes the included file directly.
|
||||
"Interface\\FrameXML\\PartyFrameTemplates.xml",
|
||||
"Interface\\FrameXML\\PetFrame.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
"MicroMenu.xml",
|
||||
// The two page files these three need before the window can be OPENED, which is not the same
|
||||
@@ -289,7 +295,7 @@ pub(super) const BAG_UI: &[&str] = &[
|
||||
// deleted `BagFrame.xml` carried `X = X or "…"` fallbacks for exactly this gap; the real file
|
||||
// is the better answer, and these tests already gate on the install.
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
// `TEXT()` — the reference's own identity-function wrapper, which stock
|
||||
// `MainMenuBarBackpackButton`'s OnEnter calls (`GameTooltip:SetText(TEXT(BACKPACK_TOOLTIP)…)`)
|
||||
// and `BagSlotButton_OnEnter` calls for `EQUIP_CONTAINER`. Manifest entry 3, and not optional
|
||||
@@ -308,6 +314,7 @@ pub(super) const BAG_UI: &[&str] = &[
|
||||
// The bag BAR declares `parent="MainMenuBarArtFrame"`, resolved at LOAD — so without this the
|
||||
// six buttons fall back to UIParent and sit at a level no production run ever puts them at.
|
||||
// It also carries `MainMenuBar_UpdateKeyRing`, which is what puts the keyring on the bar.
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
// `UpdateMicroButtons` — the KEYRING's own OnShow/OnHide calls it (ContainerFrame.lua l.117,
|
||||
// l.137), because in the reference the keyring's existence moves the micro-button row.
|
||||
|
||||
@@ -20,7 +20,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn harness(extra: &[&str]) -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
for f in extra {
|
||||
@@ -227,7 +227,11 @@ fn your_own_portrait_explains_the_menu_instead_of_showing_your_health() {
|
||||
/// table so the pass means what it says.
|
||||
#[test]
|
||||
fn action_button_hover_takes_the_default_corner() {
|
||||
let mut s = harness(&["Cooldown.xml", "ActionBar.xml"]);
|
||||
let mut s = harness(&[
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
]);
|
||||
s.register_cvars(crate::cvars::registered_pairs());
|
||||
s.run("BenillaActionButton_OnEnter(ActionButton3)").unwrap();
|
||||
assert!(s.errors().is_empty(), "hover errors: {:?}", s.errors());
|
||||
@@ -256,7 +260,12 @@ fn action_button_hover_takes_the_default_corner() {
|
||||
/// (`script/tooltip/verbs.rs`).
|
||||
#[test]
|
||||
fn ubertooltips_off_seats_action_bar_plates_beside_the_button() {
|
||||
let mut s = harness(&["Cooldown.xml", "ActionBar.xml", "MultiBars.xml"]);
|
||||
let mut s = harness(&[
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
"MultiBars.xml",
|
||||
]);
|
||||
s.register_cvars(crate::cvars::registered_pairs());
|
||||
s.set_cvar_engine("UberTooltips", "0");
|
||||
|
||||
@@ -313,7 +322,12 @@ fn ubertooltips_off_seats_action_bar_plates_beside_the_button() {
|
||||
/// un-collapsed with it.
|
||||
#[test]
|
||||
fn ubertooltips_off_seats_stance_plates_beside_the_button() {
|
||||
let mut s = harness(&["Cooldown.xml", "ActionBar.xml", "StanceBar.xml"]);
|
||||
let mut s = harness(&[
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
"StanceBar.xml",
|
||||
]);
|
||||
s.register_cvars(crate::cvars::registered_pairs());
|
||||
s.set_shapeshift_forms(vec![benilla_ui::script::ShapeshiftFormView {
|
||||
spell_id: 5487,
|
||||
@@ -355,6 +369,7 @@ fn ubertooltips_off_seats_stance_plates_beside_the_button() {
|
||||
fn buff_hover_hangs_below_left_of_the_button() {
|
||||
let mut s = harness(&[
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
|
||||
@@ -41,7 +41,11 @@ fn load_addon_xml(s: &UiScript, text: &str) -> benilla_ui::loader::LoadReport {
|
||||
fn harness() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for f in ["Fonts.xml", "UIParent.xml", "GameTooltip.xml"] {
|
||||
for f in [
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
] {
|
||||
load_xml(&s, f);
|
||||
}
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
@@ -60,12 +60,13 @@ fn unit(name: &str, guid: u64, health: u32) -> UnitState {
|
||||
fn load_tot() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
load_xml(&s, "UnitPopup.xml");
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
load_xml(&s, "ActionBar.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml");
|
||||
|
||||
@@ -24,7 +24,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
fn trainer_script() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
|
||||
@@ -9,16 +9,10 @@ use benilla_ui::script::UiScript;
|
||||
/// Fonts (for any `inherits=`), then UIParent — the manifest's order.
|
||||
fn ui_parent() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
for file in ["Fonts.xml", "UIParent.xml"] {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
|
||||
// Through the chain-aware reader: this list names chain files now, and a
|
||||
// reader that joins `assets/ui` cannot resolve one (1838, 1887, 1888).
|
||||
for file in ["Interface\\FrameXML\\Fonts.xml", "UIParent.xml"] {
|
||||
crate::ui_script::test_ui::load_ui(&s, file);
|
||||
}
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
s.run(
|
||||
@@ -166,13 +160,8 @@ fn mouse_is_over_survives_a_frame_with_no_resolved_rect() {
|
||||
#[test]
|
||||
fn raid_class_colors_is_the_references_own_nine() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/ui/Fonts.xml"),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(report.errors.is_empty(), "{:?}", report.errors);
|
||||
// RAID_CLASS_COLORS comes off the chain with the font registry since 1888.
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
|
||||
// PaintChips-2.0's own line, verbatim in shape — the one that was raising.
|
||||
@@ -225,13 +214,8 @@ fn raid_class_colors_is_the_references_own_nine() {
|
||||
#[test]
|
||||
fn the_font_path_globals_are_the_references_own_four() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/ui/Fonts.xml"),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(report.errors.is_empty(), "{:?}", report.errors);
|
||||
// The four font-path globals come off the chain with the registry since 1888.
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
|
||||
for name in [
|
||||
|
||||
@@ -19,7 +19,7 @@ fn load_unit_frames(s: &UiScript) {
|
||||
// drawing an empty plate. Hand-setting the union of those is a second copy of the reference's
|
||||
// own file; naming the file is the only version that cannot drift. (`DEAD` is l.898 of it.)
|
||||
load_xml(s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(s, "Fonts.xml");
|
||||
load_xml(s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(s, "UIParent.xml");
|
||||
// The bars' numerals machinery (decision 1082), which the manifest loads immediately ahead of
|
||||
// UnitFrames.xml and which every bar's OnLoad wires into since 1143.
|
||||
@@ -1198,18 +1198,20 @@ fn the_party_art_paints_over_the_bars() {
|
||||
// GlobalStrings first, for the same reason `load_unit_frames` names it — the stock unit-frame
|
||||
// files resolve it at LOAD (`CombatFeedback.lua` l.7-17, `UnitFrame.lua` l.1-6).
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
// Before UnitPopup: that file reads ITEM_QUALITY_COLORS at FILE SCOPE and its
|
||||
// declarer is UIParent (ref UIParent.lua:65) since 1888.
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "UnitPopup.xml");
|
||||
// The reference's own kit, in the manifest's order. `UIParent.xml` is not decoration here:
|
||||
// `RaiseFrameLevel`/`LowerFrameLevel` live in it (ref UIParent.lua l.1890-1896) and stock
|
||||
// `TargetofTargetTextureFrame`'s OnLoad calls one of them.
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BuffFrame.xml");
|
||||
|
||||
@@ -15,7 +15,7 @@ use super::test_ui::load_ui as load_xml;
|
||||
/// live here since window 6 (our ItemRef.xml used to carry a private second copy of both).
|
||||
fn load_popup_frames(s: &UiScript) {
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"UIParent.xml",
|
||||
// `SmallMoneyFrame_OnLoad`, which UiPanels' own StaticPopup money rows call at load.
|
||||
"MoneyFrame.xml",
|
||||
@@ -463,7 +463,7 @@ fn solo_target_inspect_click_reaches_inspect_unit() {
|
||||
/// `ActionBar.xml` are the pet bar's own load deps, not this menu's.
|
||||
fn load_pet_menu_frames(s: &UiScript) {
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"UIParent.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
@@ -482,6 +482,7 @@ fn load_pet_menu_frames(s: &UiScript) {
|
||||
"Interface\\FrameXML\\TargetFrame.xml",
|
||||
"Interface\\FrameXML\\PetFrame.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"ActionBar.xml",
|
||||
"PetActionBar.xml",
|
||||
] {
|
||||
|
||||
@@ -13,7 +13,7 @@ fn harness() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -15,7 +15,7 @@ pub(crate) fn harness() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for f in [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -40,7 +40,7 @@ fn harness() -> UiScript {
|
||||
// GlobalStrings. Our deleted copy carried `X = X or "…"` fallbacks for a harness with no
|
||||
// chain; the reference's file has none, and it should not.
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
// `TEXT()`, which AutoFollowStatus_OnEvent puts its message through.
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
|
||||
@@ -33,7 +33,9 @@ use std::collections::BTreeSet;
|
||||
use bevy::prelude::*;
|
||||
|
||||
use benilla_formats::{Talent, TalentCatalog};
|
||||
use benilla_ui::script::{TalentPrereqView, TalentTabView, TalentUiState, TalentView, UiScript};
|
||||
use benilla_ui::script::{
|
||||
ScriptValue, TalentPrereqView, TalentTabView, TalentUiState, TalentView, UiScript,
|
||||
};
|
||||
|
||||
use crate::net::{ClientCommand, NetCommands, ObjectStore, SelfPlayer};
|
||||
use crate::ui_action::{PlayerActions, Spells};
|
||||
@@ -157,7 +159,26 @@ fn feed_talents(
|
||||
);
|
||||
script.set_talents(fresh.clone());
|
||||
memory.pushed = fresh;
|
||||
script.fire_event("CHARACTER_POINTS_CHANGED", vec![]);
|
||||
// `%d%d` — the talent-point and profession-point DELTAS, per the reference's own fire
|
||||
// site (SignalEvent2, decision 1884). `ChatFrame.lua:1326` opens its branch with
|
||||
// `if ( arg2 > 0 )`, unguarded, so an argless fire is not merely ignored there: it
|
||||
// compares nil with a number and raises. `memory.points` still holds the previous pair
|
||||
// here — it is updated at the end of this function — and a first observation seeds at
|
||||
// zero, the same rule the professions line below uses.
|
||||
let (talent_delta, profession_delta) = match memory.points {
|
||||
Some((old_talent, old_professions)) => (
|
||||
i64::from(points.0) - i64::from(old_talent),
|
||||
i64::from(points.1) - i64::from(old_professions),
|
||||
),
|
||||
None => (0, 0),
|
||||
};
|
||||
script.fire_event(
|
||||
"CHARACTER_POINTS_CHANGED",
|
||||
vec![
|
||||
ScriptValue::Int(talent_delta),
|
||||
ScriptValue::Int(profession_delta),
|
||||
],
|
||||
);
|
||||
}
|
||||
// The professions line rides the cp2 RISE only (a spend consumes, a rise frees — the
|
||||
// reference prints on arg2 > 0); the first observation seeds silently.
|
||||
|
||||
@@ -1553,15 +1553,50 @@ fn feed_units(
|
||||
// Trigger PROVISIONAL (0578's pattern): fired off the descriptor diff, which lands in the
|
||||
// same update batch as the ding's XP fields, so consumers read a coherent picture. The
|
||||
// real client plausibly fires it from its `SMSG_LEVELUP_INFO` handler instead, with the
|
||||
// packet's gain tuple as arg2+ — unpinned, and no 1.12 FrameXML consumer reads past arg1
|
||||
// (`ReputationWatchBar_Update` takes arg1; the tick's handler takes none), so the extra
|
||||
// args wait for a consumer.
|
||||
// packet's gain tuple as arg2+.
|
||||
//
|
||||
// **THE ARGS ARE NOT OPTIONAL, AND THE CLAIM THAT USED TO STAND HERE WAS WRONG.** It said no
|
||||
// 1.12 FrameXML consumer reads past arg1, citing `ReputationWatchBar_Update` (arg1) and the
|
||||
// tick's handler (none). It missed the main one: `ChatFrame.lua:1283-1320` reads **arg1
|
||||
// through arg9** — the level, the health and mana gains, the talent points, and the five stat
|
||||
// gains, each printed as its own system line. The reference's own fire site says the same,
|
||||
// `%d%d%d%d%d%d%d%d%d` (SignalEvent2). Two consumers were surveyed, the conclusion was drawn
|
||||
// from two, and the third is the one that matters.
|
||||
//
|
||||
// We fire arg1 alone, so the stock ChatFrame's `if ( arg3 > 0 )` would compare nil with a
|
||||
// number and raise — this blocks the ChatFrame window. The gains are on a packet we already
|
||||
// parse (`SMSG_LEVELUP_INFO`, twelve u32) and already spend: `ui_chat/feed.rs` composes those
|
||||
// very lines in Rust because the event could not carry them. Decision 1884 scopes plumbing
|
||||
// the tuple here and retiring the Rust duplicate; this trigger is a descriptor diff and the
|
||||
// gains arrive on the packet, so it is a join, not a one-liner.
|
||||
if let Some((store, _)) = self_q.iter().next() {
|
||||
if let Some(level) = store.0.unit_level() {
|
||||
let prev = memo.last_level.replace(level);
|
||||
if prev.is_some_and(|p| level != p) {
|
||||
gate.audit("feed_units", "the level edge");
|
||||
script.fire_event("PLAYER_LEVEL_UP", vec![ScriptValue::Int(i64::from(level))]);
|
||||
// All nine, per the reference's own fire site (`%d%d%d%d%d%d%d%d%d`): level,
|
||||
// health gain, mana gain, talent points, then the five stat gains in
|
||||
// `SPELL_STAT0..4` order. Absent gains are ZEROS, not a shorter payload — a
|
||||
// demotion really did gain nothing, and every consumer guards with `if ( argN > 0 )`
|
||||
// so zero reads as "no line" while nil raises.
|
||||
let (info, talent_points) = chat.take_level_up_gains(level).unzip();
|
||||
let gain = |f: fn(&benilla_protocol::messages::LevelUpInfo) -> u32| {
|
||||
ScriptValue::Int(i64::from(info.as_ref().map_or(0, f)))
|
||||
};
|
||||
script.fire_event(
|
||||
"PLAYER_LEVEL_UP",
|
||||
vec![
|
||||
ScriptValue::Int(i64::from(level)),
|
||||
gain(|l| l.health),
|
||||
gain(|l| l.powers[0]),
|
||||
ScriptValue::Int(i64::from(talent_points.unwrap_or(0))),
|
||||
gain(|l| l.stats[0]),
|
||||
gain(|l| l.stats[1]),
|
||||
gain(|l| l.stats[2]),
|
||||
gain(|l| l.stats[3]),
|
||||
gain(|l| l.stats[4]),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,17 @@ mod common;
|
||||
/// the `MoneyTypeInfo` table this window registers `AUCTION_DEPOSIT` into, The `MoneyInputFrame_*` money-entry
|
||||
/// helpers used to mean loading MerchantFrame.xml as well; 1751 moved that kit to MoneyFrame.xml
|
||||
/// on its way to the chain, so the dependency is gone.
|
||||
const FILES: [&str; 11] = [
|
||||
"Fonts.xml",
|
||||
const FILES: [&str; 12] = [
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
// The four money-entry frames come off the chain since 1882 — `MoneyInputFrameTemplate` and
|
||||
// the `MoneyInputFrame_*` verbs, replacing our own verbatim copy of both.
|
||||
r"Interface\FrameXML\MoneyInputFrame.lua",
|
||||
r"Interface\FrameXML\MoneyInputFrame.xml",
|
||||
"UiPanels.xml",
|
||||
// `auctionRowName` colours each row from ITEM_QUALITY_COLORS, whose declarer is
|
||||
// UIParent (ref UIParent.lua:65) since 1888 put the font registry on the chain.
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"ScrollTemplates.xml",
|
||||
|
||||
@@ -35,7 +35,7 @@ use benilla_ui::script::{UiScript, UnitCombatStats, UnitState};
|
||||
/// `RESISTANCE_TOOLTIP_SUBTEXT` (`:184`/`:224`), `ARMOR` and `ARMOR_TOOLTIP` (`:236`/`:249`).
|
||||
const FILES: [&str; 12] = [
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
// The reference's own since 1751 window 24 — `common::load_ui` speaks both stores.
|
||||
"Interface\\FrameXML\\ItemButtonTemplate.xml",
|
||||
|
||||
@@ -27,7 +27,7 @@ const UI_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/ui");
|
||||
/// the manifest seats it. So this list carries the chain pair and the loader below has to be able
|
||||
/// to READ a chain entry, which a disk-only provider under `assets/ui` cannot.
|
||||
const FILES: [&str; 7] = [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -23,7 +23,7 @@ const FILES: [&str; 9] = [
|
||||
// `attempt to concatenate a nil value` kills the handler before it reaches its `ShowUIPanel`,
|
||||
// so the window simply never opens. Our deleted copy carried the string as a local fallback.
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
// `GetMaterialTextColors`, which the reference's own `ItemTextFrame_OnEvent` calls to pick the
|
||||
|
||||
@@ -13,7 +13,7 @@ const UI_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/ui");
|
||||
/// MerchantFrame.xml rides along because MailFrame.xml reuses its global `BenillaMoney_*` coin
|
||||
/// helpers (postage display), so a load error in either fails here.
|
||||
const FILES: [&str; 9] = [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
// The send tab's money entry comes off the chain since 1882 — `MoneyInputFrameTemplate` and
|
||||
// the `MoneyInputFrame_*` verbs. Seated straight after MoneyFrame.xml, benilla.toc's order.
|
||||
|
||||
@@ -23,7 +23,7 @@ use benilla_ui::script::{
|
||||
const FILES: [&str; 13] = [
|
||||
// `PLAYER_LEVEL` and the rest of the strings the stock file formats through.
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
// `TEXT`.
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\ItemButtonTemplate.xml",
|
||||
|
||||
@@ -14,7 +14,7 @@ const UI_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/ui");
|
||||
/// MerchantFrame.xml rides along because TradeFrame.xml reuses its global `BenillaMoney_*` coin
|
||||
/// helpers (the two gold displays), so a load error in either fails here.
|
||||
const FILES: [&str; 9] = [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
// The money entry comes off the chain since 1882 — `MoneyInputFrameTemplate` and the
|
||||
// `MoneyInputFrame_*` verbs the window's OnLoad calls. Seated straight after MoneyFrame.xml,
|
||||
|
||||
@@ -21,7 +21,7 @@ mod common;
|
||||
/// CraftFrame.xml rides along (it loads right after TradeSkillFrame.xml in the app and shares
|
||||
/// its guarded-global utilities) so a load error in EITHER window fails here.
|
||||
const FILES: [&str; 10] = [
|
||||
"Fonts.xml",
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
Reference in New Issue
Block a user