the tooltip and five windows go stock, and the battleground keeps score
- the tooltip is the reference's own file: `GameTooltip.xml` loads off the chain, ours is deleted, and every verb it calls was already bound, so this is a plain swap; retired with it: the `$parentThicken` plate fill that thickened the plate from the art's ~73% to ~84% opacity — the reference's plate is the raw `UI-Tooltip-Background` tint, and that is what ships; the one engine fact the transcription had been hiding: the reference's instance declares no size, and a frame with one anchor and no size resolves to NO rect under the reference's own layout law — our declared size had been a harness crutch (1968); and the dressing room goes stock with `<DressUpModel>` as a widget kind (factory `0x495c00`): a `PlayerModel` plus its own `Undress`/`Dress`/`TryOn`, where `Undress` strips the body and not the hands, and Close is read off the window since the stock OnHide queues nothing (1969) - the mail window and the auction house are the reference's own: `MailFrame.xml` loads off the chain and brings the stationery picker, invoice pane and autocomplete ours had left out; the stationery family is built off wow-re's carve over a list computed off `Stationery.dbc` rows × the player's bags (`0x622270`, not the bank); the selection rides `CMSG_SEND_MAIL` field 6 and a send with none silently aborts; its two events fire where the client fires them (1970); `Blizzard_AuctionUI` loads on demand from the `AUCTION_HOUSE_SHOW` arm, and three root fixes its Lua surfaced: `GetAuctionItemClasses()` is static — read at the addon's load, before any session, so the class tree is a login-scoped push; `SetAuctionCompareItem` (`0x535d70`) on the vendor compare's one body; `NEW_AUCTION_UPDATE` resets the create form; its dressing-room pane shares the app's one room (1971) - the battleground keeps score: `WorldStateFrame.xml` loads off the chain and ours is deleted; the stock file is three things where ours was one: the readout, the capture bars, and the battleground scoreboard this client never had, built off wow-re's round — `MSG_PVP_LOG_DATA` both ways (throttled to 5000 ms client-side, `0x4aa170`) and `CMSG_LEAVE_BATTLEFIELD`; eleven score verbs over a board the app pushes, where the filter is a SORT, not a compaction — team first, killing blows descending, deaths ascending, honor descending, name — so the filtered count shrinks while `GetBattlefieldScore` still reaches every wire row; `HIDE_OUTDOOR_WORLD_STATE` joins the options uvar block, a capture point is a bar, and the scoreboard and battlefield-minimap toggles become bound specs at the install's own `SHIFT-SPACE`/`SHIFT-M` (1972) - the trade skill and craft windows go stock as `Blizzard_TradeSkillUI`/`Blizzard_CraftUI` on demand, from the reference's own UIParent arms; our two transcriptions are deleted; the four item-link verbs are built off wow-re's carve on the client's link builder (`0x52adb0`) — zero values on any miss, `GetTradeSkillReagentItemLink` raising Blizzard's own typo'd Usage, kept, and `GetCraftItemLink` the `|Henchant:` link in fixed white; `SetTextColor`'s channels coerce nil to 0 on FontString, Font and Button where ours had raised (1973); and the Inspect, Talent, Macro and Raid addons load on demand — `benilla.toc` lists no `Interface\AddOns` row, and below level 10 the talent addon is never even loaded; the gates walk every reached addon and found `Blizzard_GMSurveyUI`'s four engine verbs nothing answers, reachable only through `GMSURVEY_DISPLAY`, which nothing fires (1889; 1967) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,977 +0,0 @@
|
||||
<Ui>
|
||||
<!--
|
||||
benilla's Enchanting window (decision 0437 phase 3): the 1.12 `CraftFrame`, re-expressed from
|
||||
the reference `Blizzard_CraftUI.xml`/`.lua` (session scratchpad `ref-ui/`, decision 0246
|
||||
discipline — never committed) onto our own engine/feeds. Geometry/art-paths/structure below are
|
||||
QUOTED from the ref (line citations inline); the Lua is the ref's logic turned onto benilla's
|
||||
Era API (`GetCraftName`/`GetCraftDisplaySkillLine`/`GetNumCrafts`/`GetCraftInfo`/reagents/
|
||||
`GetCraftSpellFocus`/`DoCraft`/`CloseCraft`, decision 0437's engine surface,
|
||||
`crates/benilla-ui/src/script/craft.rs`). The exact twin of `TradeSkillFrame.xml` — same
|
||||
engine-gap substitutions, same FLAT-list INTERIM, same doc conventions — this header only calls
|
||||
out where CRAFT'S OWN ref actually differs (verified against `Blizzard_CraftUI.lua`, not
|
||||
assumed from TradeSkill's shape).
|
||||
|
||||
NOT an NPC session (unlike Gossip/Merchant/Trainer): the window opens client-side off your own
|
||||
cast of an effect-47 opener spell — no NPC, no server round trip to open it (0437 §"What
|
||||
scoping verified", TradeSkillFrame.xml's own identical header note). It registers
|
||||
`CRAFT_SHOW`/`CRAFT_UPDATE`/`CRAFT_CLOSE` on ITSELF (TradeSkillFrame.xml's own precedent: this
|
||||
engine has no separate FrameXML driver that dispatches SHOW for you).
|
||||
|
||||
Modeled on the two known-good dressed windows for the framing (MerchantFrame.xml's own header
|
||||
comment, TradeSkillFrame.xml's own precedent for this exact art family — both windows are
|
||||
`ClassTrainerFrame`-styled 384×512 four-quadrant windows and share BYTE-IDENTICAL geometry for
|
||||
every region this file also carries: the portrait, the four border quadrants' Size/Anchors, the
|
||||
skill-border/horizontal-bar decorative art, the rank StatusBar block, the persistent selection
|
||||
highlight, the 8-row list + its ScrollFrame, the detail header art, the reagent grid's anchor
|
||||
chain (down to the SAME "Reagent7 anchors off Reagent6, not Reagent5" ref quirk), and the
|
||||
Create/Exit button positions):
|
||||
- MerchantFrame.xml/TradeSkillFrame.xml — the 384×512 four-quadrant window art (no
|
||||
<Backdrop>: the art IS the frame), the "player" portrait binding (`SetPortraitTexture(region,
|
||||
"player")` — ref l.89-91 confirms Craft ALSO binds "player", not "npc": this is not an NPC
|
||||
session), the UIPanel slot system (carries NO hardcoded <Anchors> — ShowUIPanel/SetLeftFrame
|
||||
SetPoints it), OnShow/OnHide PlaySound pair.
|
||||
- TradeSkillFrame.xml — the FLAT layout constraint (below), the faux-scroll list kit
|
||||
(FauxScrollFrameTemplate, ScrollTemplates.xml), the locally-reconstructed
|
||||
UIPanelButtonTemplate/close-button/item-button idioms (no such templates ship in this house
|
||||
dialect — TalentFrame.xml's own ENGINE API note, restated per-window, StackSplit.xml's own
|
||||
"own local virtual template, not a cross-file inherits" convention), the persistent
|
||||
selection-highlight Frame, and the `button.craftIndex`/`Button:SetTextColor` engine-gap
|
||||
substitutions (deviation 7 below).
|
||||
|
||||
FLAT layout — a CHOICE inherited from TradeSkillFrame.xml, which inherited it from
|
||||
TrainerFrame.xml, which stated it as a house constraint it never was. A runtime-shown child
|
||||
<Frame> DOES render its own <Layers> FontStrings in this engine (probed; decision 1517). The
|
||||
detail-pane FontStrings + the title live on BenillaCraftFrame's OWN <Layers> (OVERLAY, above
|
||||
the window's own BACKGROUND/BORDER/ARTWORK art groups) because that is how this window was
|
||||
built, not because nesting fails. The ref's
|
||||
`CraftDetailScrollFrame`/`CraftDetailScrollChildFrame` therefore don't exist here — the detail
|
||||
is a FIXED (non-scrolling) pane, computed once from the flattened ScrollFrame+ScrollChild
|
||||
offsets (TOPLEFT 20,-234 + each child's own ref-quoted offset — BYTE-IDENTICAL to TradeSkill's
|
||||
own detail-scroll anchor, since both refs place it there). Per-region regions that DO need
|
||||
Show()/Hide() live on Button/StatusBar/Texture children (those render their own Layers) — the
|
||||
rank bar, the detail icon, and the 8 reagent slots.
|
||||
|
||||
v1 DEVIATIONS (pinned to decision 0437's in-flight wow-re §5 dispatch where noted):
|
||||
1. FLAT LIST, NO HEADERS. `GetCraftInfo`'s `craftType` is never `"header"` in v1 (the engine
|
||||
API contract) — the ref's header BRANCH (the row's fold icon/texture swap, the row-text
|
||||
21px-right reposition loop) is still dropped outright, not even as a no-op:
|
||||
`Expand`/`CollapseCraftSkillLine` exist but are wired as true engine-side NO-OPS
|
||||
(`craft.rs`'s own module doc + `Expand/CollapseCraftSkillLine` comment) — the 1.12 craft
|
||||
list is single-group, so the real client's own header path is unexercised here; a click
|
||||
changes nothing observable beyond the button's own local state.
|
||||
`CraftExpandButtonFrame`/`CraftCollapseAllButton` (ref l.265-325) now SHIP, for fidelity —
|
||||
this deviation's own earlier revision omitted them entirely; closed this session
|
||||
(TradeSkillFrame.xml's own identical CollapseAll tab, restored there the same session).
|
||||
`BenillaCraftFrame_Update`'s own restored tail (ref l.248-282, minus the row-text-reposition
|
||||
loop, still dropped above) counts headers to size the tab's texture — since `GetCraftInfo`
|
||||
never returns `"header"`, that count is always 0, so the tail's own `numHeaders > 0` branch
|
||||
(ref l.270) always takes the ELSE arm: `BenillaCraftExpandButtonFrame` ends up HIDDEN in
|
||||
practice, a faithful-but-currently-inert outcome (this file's own deviation 2 "ships for
|
||||
fidelity, never actually visible yet" shape). `CraftTypeColor["header"]`/`["used"]`/
|
||||
`["none"]` are kept in the transcribed color table for fidelity but are unreachable this way
|
||||
(TradeSkillFrame.xml's own identical posture). v1 still ships no filter dropdowns.
|
||||
2. THE SPELL-FOCUS TOOL NEVER REDDENS. `BuildColoredListString` is REUSED, not redefined — it
|
||||
is already a true Lua GLOBAL after `TradeSkillFrame.xml` loads (`if not
|
||||
BuildColoredListString then function BuildColoredListString(...) ... end end`, no `local`),
|
||||
and `TradeSkillFrame.xml`'s own header comment names this file as the second consumer.
|
||||
`CraftFrame.xml` is loaded AFTER `TradeSkillFrame.xml` in `ui_script/mod.rs`'s load list, so
|
||||
this file calls it directly with no guard/redefinition. It reds an unmet tool byte-faithfully,
|
||||
but `GetCraftSpellFocus`'s `hasTool` has no client-side proximity check yet (0437 INTERIM,
|
||||
pinned to the §5 dispatch, TradeSkillFrame.xml's own identical note) — v1 never actually
|
||||
reds a tool in practice.
|
||||
3. NO DRESSING-ROOM / CHAT-LINK INSERT. The ref's Ctrl+click `DressUpItemLink` / Shift+click
|
||||
`ChatFrameEditBox:Insert` on `CraftIcon`/`CraftReagent*` (ref l.44-51, 555-563) are dropped
|
||||
— the cursor-item arc hasn't landed (MerchantFrame.xml's/TradeSkillFrame.xml's own identical
|
||||
"a no-op here until the cursor-item arc" precedent). `CraftIcon`'s `this.hasItem = 1`
|
||||
OnLoad flag (ref l.553) is dropped too — it is read only by `UIParent.lua`'s own generic
|
||||
Ctrl-click dressing-room handler (grepped), which this dropped branch never reaches; a real,
|
||||
now-inert vestige, not a benilla omission.
|
||||
4. NO `SKILL_LINES_CHANGED` self-listen on the rank StatusBar (ref l.253-260, TradeSkillFrame's
|
||||
own identical deviation): the skill-up feedback/diff-watcher is 0437 phase 4, not yet built
|
||||
— the bar's value/color/text refresh only rides this window's own SHOW/UPDATE/selection
|
||||
paint.
|
||||
5. NO `PLAYER_ENTERING_WORLD` hide (TradeSkillFrame.xml's own identical choice for this session
|
||||
shape) — opening this window is your own client-side cast, not an NPC session.
|
||||
6. NO BEAST TRAINING / NO TRAINING POINTS (0437's own scope line — the pet arc owns Beast
|
||||
Training's CraftFrame use). Every ref region/branch that exists ONLY to serve a pet ability
|
||||
row is omitted, not stubbed: `CraftFramePointsLabel`/`CraftFramePointsText`
|
||||
(`Craft_UpdateTrainingPoints`, ref l.428-438), the per-row `Craft<n>Cost` FontString (always
|
||||
blank in v1 since `GetCraftInfo`'s `trainingPointCost` is a hardcoded `0` — `craft.rs`'s own
|
||||
module doc), and the Requirements line's pet-level fallback branch (`requiredLevel`, also a
|
||||
hardcoded `0` — ref l.364-369, unreachable). `UNIT_PET_TRAINING_POINTS` is not registered
|
||||
(nothing left to drive it). `GetCraftButtonToken()` always returns `"CREATE"` — Beast
|
||||
Training's `"TRAIN"` token is the only other value the ref ever returns, and that branch is
|
||||
out of scope (`craft.rs`'s own note).
|
||||
7. ENGINE API gaps (TradeSkillFrame.xml's own "no engine equivalent" posture, restated per
|
||||
substitution actually used here):
|
||||
- (CLOSED — the row name is the button's own label now.) An earlier revision recorded
|
||||
`Button:SetTextColor`/`:LockHighlight`/`:UnlockHighlight` as having no engine method and
|
||||
routed row colouring through a `$parentName` child FontString. The engine has shipped
|
||||
all three for a long time; the note went stale and the workaround outlived it, costing
|
||||
this window the white hover/selected label the real client has (decision 1605). The row
|
||||
name is now a `<ButtonText>` (the ref's `Craft<n>Text`) under the base's
|
||||
`<NormalFont>/<HighlightFont>/<DisabledFont>` trio, and Update calls `SetTextColor` /
|
||||
`LockHighlight` / `UnlockHighlight` where the ref does (l.190/234/236). `$parentSubText`
|
||||
stays a child FontString, as it is in the ref, and is whitened by hand for the selected
|
||||
row (l.232). NOT taken: the ref's per-row `craftButton.r/g/b` stash and the base's own
|
||||
`OnEnter`/Craft's `OnLeave` subtext recolour dance (l.191-193, xml l.22-26) — with
|
||||
Beast Training out of scope (deviation 6) `craftSubSpellName` is always `""`, so every
|
||||
one of those paints an empty string. The row still carries NO hover tooltip: the ref
|
||||
has none either. (An earlier revision of this line added "TradeSkill's own row hover IS
|
||||
a real ref behavior; Craft's is not" — WRONG about *tooltips*, and never checked:
|
||||
`TradeSkillSkillButtonTemplate` overrides `OnClick` alone, and the shared base
|
||||
`ClassTrainerSkillButtonTemplate` (`Interface\FrameXML\ClassTrainerFrameTemplates.xml`)
|
||||
only recolours `$parentSubText` on hover. NEITHER window's rows tooltip.)
|
||||
- `Button:GetID()`/`:SetID()` have no native method (same grep) — the ref's
|
||||
`craftButton:SetID(craftIndex)` + `this:GetID()` pair becomes a plain Lua field,
|
||||
`button.craftIndex`, set in the Update loop and read by the click handler
|
||||
(`ClassTrainerSkillButtonTemplate`'s own `this.r/g/b`-restoring `OnLeave` — a color dance
|
||||
made moot by the FontString substitution above — is dropped for the same reason
|
||||
TradeSkillFrame.xml's own row `OnLeave` dropped it: nothing ever recolors the row on
|
||||
hover in this engine, so there is nothing to restore).
|
||||
- `EditBox:SetNumber`/`SecondsToTime` are NOT needed here at all: Craft has no repeat-count
|
||||
input (deviation 8) and no per-recipe cooldown concept (no `CraftSkillCooldown` region
|
||||
exists in the ref) — TradeSkillFrame.xml's own engine-gap shims for those simply don't
|
||||
apply to this window.
|
||||
8. NO CREATE ALL (a REAL ref difference, not an engine gap — verified: `CraftCreateButton`'s
|
||||
OnClick is bare `DoCraft(GetCraftSelectionIndex())`, ref l.667, no count argument
|
||||
anywhere). This window ships ONE action button; no repeat-count EditBox, no -/+ buttons, no
|
||||
`CraftCreateAllButton`, no `GetCraftskillRepeatCount`/`UPDATE_CRAFTRECAST` — `craft.rs`'s
|
||||
own module doc.
|
||||
9. Dead ref code dropped, not transcribed (TradeSkillFrame.xml's own `reagentToAnchorTo`
|
||||
precedent for exactly this move): `CraftSubTypeColor` + its `subColor` local (ref l.15-22,
|
||||
189) are computed but never read anywhere in the reference — grepped, confirmed zero other
|
||||
uses — a real vestige. The row-width scrollbar toggle (ref l.174-187, `293` vs `323`) is
|
||||
dropped the same way TradeSkill's was: the ScrollFrame itself is always visible in both
|
||||
engines (only its *scrollbar* toggles on overflow), so rows are fixed at 293
|
||||
(TradeSkillFrame.xml's own row width).
|
||||
10. SELECTION SURVIVES `CRAFT_SHOW` (a deliberate deviation from Craft's OWN ref, flagged):
|
||||
the real `CraftFrame_Show` (ref l.26-49) unconditionally re-selects index 1 on every open,
|
||||
discarding whatever was selected before. Our engine's `set_craft` already PRESERVES the
|
||||
selection across a re-push by spell id (`craft.rs`'s own doc, TradeSkill's identical
|
||||
`set_trade_skill` law) specifically so re-opening the same skill line doesn't visibly jump
|
||||
the detail pane — forcing index 1 on every Show would fight that mechanism for no benefit.
|
||||
`BenillaCraftFrame_Show` therefore mirrors `BenillaTradeSkillFrame_Show`'s own pattern
|
||||
(keep a nonzero selection, else pick 1) instead of Craft's own ref line-for-line.
|
||||
11. THE ITEM-TARGET PICK UX NOTE (0437 §"What scoping verified", `craft.rs`'s own
|
||||
`needs_item_target` doc): `DoCraft` on an `ENCHANT_ITEM`-family recipe arms an APP-SIDE
|
||||
item-target pick (the `TARGET_FLAG_ITEM` cast-target flow) — this XML plays NO part in
|
||||
that flow; `CraftCreateButton_OnClick` below is the same bare `DoCraft(...)` call
|
||||
regardless of which recipe is selected, exactly like the ref's own button.
|
||||
12. GlobalStrings the ref reads (`REQUIRES_LABEL`, `SPELL_REAGENTS`, `PARENS_TEMPLATE`,
|
||||
`CREATE`, `EXIT`) are declared LOCALLY below as the byte-exact 1.12 enUS values (session
|
||||
scratchpad extraction, decision 0246's discipline, line-cited per constant),
|
||||
TradeSkillFrame.xml's own idiom — with ONE exception: `CREATE` is declared as a REAL Lua
|
||||
GLOBAL (`CREATE = CREATE or "Create"`, guarded so a second file loading it stays
|
||||
idempotent), not `local`, because `getglobal(GetCraftButtonToken())` (ref l.106, faithfully
|
||||
kept in `BenillaCraftFrame_Update` below) needs an actual `_G["CREATE"]` entry to
|
||||
resolve — a genuine structural requirement of that ref call, not a style choice.
|
||||
`GRAY_FONT_COLOR`/`HIGHLIGHT_FONT_COLOR` are Fonts.xml globals (CharacterFrame.xml's own
|
||||
precedent for reading them unqualified).
|
||||
|
||||
Registers at the left UIPanel slot (`UiPanels.xml`). The ref's OWN registration
|
||||
(`Blizzard_CraftUI.lua:24`, `UIPanelWindows["CraftFrame"] = { area = "left", pushable = 4 }`) is
|
||||
`pushable = 4` — VERIFIED at that line (NOT `3`; `3` is `TradeSkillFrame`'s own line 13 value, a
|
||||
different window/depth — grepped both files directly to confirm before using either number).
|
||||
ESC-closable through the standard UIPanel chain. `CloseCraft()` fires on hide for ANY reason
|
||||
(ESC, close X, Exit, `CRAFT_CLOSE`, or another left-slot window displacing it) —
|
||||
TradeSkillFrame.xml's own `CloseTradeSkill`-on-hide precedent, adapted (no server round trip per
|
||||
0437, purely client-side session teardown).
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- ── Constants (ref Blizzard_CraftUI.lua:2-4) ────────────────────────────────────────────────
|
||||
local CRAFTS_DISPLAYED = 8 -- CRAFTS_DISPLAYED (ref l.2)
|
||||
local MAX_CRAFT_REAGENTS = 8 -- MAX_CRAFT_REAGENTS (ref l.3)
|
||||
local CRAFT_SKILL_HEIGHT = 16 -- CRAFT_SKILL_HEIGHT (ref l.4)
|
||||
|
||||
-- The recipe-row color table (ref l.6-13), transcribed verbatim including "header"/"used"/
|
||||
-- "none" — all UNREACHABLE in v1 (this file's header comment, deviation 1). CraftSubTypeColor
|
||||
-- (ref l.15-22) is DROPPED outright — dead ref code, deviation 9.
|
||||
CraftTypeColor = {}
|
||||
CraftTypeColor["optimal"] = { r = 1.00, g = 0.50, b = 0.25 }
|
||||
CraftTypeColor["medium"] = { r = 1.00, g = 1.00, b = 0.00 }
|
||||
CraftTypeColor["easy"] = { r = 0.25, g = 0.75, b = 0.25 }
|
||||
CraftTypeColor["trivial"] = { r = 0.50, g = 0.50, b = 0.50 }
|
||||
CraftTypeColor["used"] = { r = 0.50, g = 0.50, b = 0.50 }
|
||||
CraftTypeColor["header"] = { r = 1.00, g = 0.82, b = 0 }
|
||||
CraftTypeColor["none"] = { r = 0.25, g = 0.75, b = 0.25 }
|
||||
|
||||
-- ── GlobalStrings the ref reads (byte-exact 1.12 enUS, this file's header comment cites the
|
||||
-- extraction + the CREATE-must-be-global exception) — chunk-local except CREATE. ─────────────
|
||||
local REQUIRES_LABEL = "Requires:" -- REQUIRES_LABEL (GlobalStrings.lua:3339)
|
||||
local PARENS_TEMPLATE = "(%s)" -- PARENS_TEMPLATE (2984)
|
||||
local EXIT_LABEL = "Exit" -- EXIT (1933)
|
||||
CREATE = CREATE or "Create" -- CREATE (871) -- REAL global, see header comment
|
||||
|
||||
-- BuildColoredListString is REUSED from TradeSkillFrame.xml (already a true global by the time
|
||||
-- this file loads — this file's header comment, deviation 2). No redefinition here.
|
||||
|
||||
-- ── Detail pane ─────────────────────────────────────────────────────────────────────────────
|
||||
-- No crafts known at all (ref l.131-150): blank the whole detail pane. Regions on this window's
|
||||
-- own Layers "hide" via SetText("") — TradeSkillFrame.xml's own convention, which predates
|
||||
-- region Show/Hide (region.rs has the real pair now); equivalent either way.
|
||||
function BenillaCraftFrame_HideDetails()
|
||||
getglobal("BenillaCraftFrameTitleText"):SetText(GetCraftName())
|
||||
getglobal("BenillaCraftName"):SetText("")
|
||||
getglobal("BenillaCraftRequirements"):SetText("")
|
||||
getglobal("BenillaCraftDescription"):SetText("")
|
||||
getglobal("BenillaCraftIcon"):Hide()
|
||||
for i = 1, MAX_CRAFT_REAGENTS do
|
||||
getglobal("BenillaCraftReagent" .. i):Hide()
|
||||
end
|
||||
getglobal("BenillaCraftCreateButton"):Disable()
|
||||
end
|
||||
|
||||
-- Fill the detail pane for the selected recipe `id` (ref CraftFrame_SetSelection, l.285-408;
|
||||
-- the header branch is dropped — deviation 1, never reached). Also the SOURCE of the selection
|
||||
-- highlight's color (set once here, ref l.305-306).
|
||||
function BenillaCraftFrame_SetSelection(id)
|
||||
if not id or id == 0 then
|
||||
BenillaCraftFrame_HideDetails()
|
||||
return
|
||||
end
|
||||
SelectCraft(id)
|
||||
if GetCraftSelectionIndex() > GetNumCrafts() then
|
||||
return
|
||||
end
|
||||
local craftName, craftSubSpellName, craftType = GetCraftInfo(id)
|
||||
local color = CraftTypeColor[craftType]
|
||||
if color then
|
||||
getglobal("BenillaCraftHighlight"):SetVertexColor(color.r, color.g, color.b)
|
||||
end
|
||||
|
||||
-- General info (ref l.308-320).
|
||||
getglobal("BenillaCraftFrameTitleText"):SetText(GetCraftName())
|
||||
getglobal("BenillaCraftName"):SetText(craftName)
|
||||
local icon = getglobal("BenillaCraftIcon")
|
||||
icon:Show()
|
||||
icon:SetNormalTexture(GetCraftIcon(id) or BENILLA_FALLBACK_ICON)
|
||||
|
||||
-- The description + the Reagents label's DYNAMIC reposition (ref l.312-318, re-expressed
|
||||
-- here): when there's a description, the label sits 10px below its bottom edge; when
|
||||
-- there isn't, it sits directly where the (blank) description would have been.
|
||||
if GetCraftDescription(id) then
|
||||
getglobal("BenillaCraftDescription"):SetText(GetCraftDescription(id))
|
||||
getglobal("BenillaCraftReagentLabel"):SetPoint("TOPLEFT", "BenillaCraftDescription", "BOTTOMLEFT", 0, -10)
|
||||
else
|
||||
getglobal("BenillaCraftDescription"):SetText(" ")
|
||||
getglobal("BenillaCraftReagentLabel"):SetPoint("TOPLEFT", "BenillaCraftDescription", "TOPLEFT", 0, 0)
|
||||
end
|
||||
|
||||
-- Reagents (ref l.321-359): up to MAX_CRAFT_REAGENTS, gray-out + creatable gate.
|
||||
local creatable = true
|
||||
local numReagents = GetCraftNumReagents(id)
|
||||
for i = 1, MAX_CRAFT_REAGENTS do
|
||||
local reagent = getglobal("BenillaCraftReagent" .. i)
|
||||
if i <= numReagents then
|
||||
local reagentName, reagentTexture, need, have = GetCraftReagentInfo(id, i)
|
||||
if not reagentName or not reagentTexture then
|
||||
-- Item template not landed yet for this reagent — hide it, like the ref does
|
||||
-- for any nil reagent (engine API note, decision 0437).
|
||||
reagent:Hide()
|
||||
else
|
||||
reagent:Show()
|
||||
reagent.icon:SetTexture(reagentTexture)
|
||||
reagent.name:SetText(reagentName)
|
||||
have = have or 0
|
||||
if have < need then
|
||||
reagent.icon:SetVertexColor(0.5, 0.5, 0.5)
|
||||
reagent.name:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b)
|
||||
creatable = false
|
||||
else
|
||||
reagent.icon:SetVertexColor(1.0, 1.0, 1.0)
|
||||
reagent.name:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
|
||||
end
|
||||
local haveText = have
|
||||
if have >= 100 then haveText = "*" end
|
||||
reagent.count:SetText(haveText .. " /" .. need)
|
||||
end
|
||||
else
|
||||
reagent:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- Requires: line (ref l.361-372) — ONE concatenated FontString, unlike TradeSkill's split
|
||||
-- Label+Text pair (Craft's own ref genuinely differs here — this file's header comment
|
||||
-- points at TradeSkillFrame.xml's split for comparison). The pet-level fallback branch
|
||||
-- (ref l.364-369) is unreachable in v1 (deviation 6) and dropped.
|
||||
local requiredTotems = BuildColoredListString(GetCraftSpellFocus(id))
|
||||
if requiredTotems then
|
||||
getglobal("BenillaCraftRequirements"):SetText(REQUIRES_LABEL .. " " .. requiredTotems)
|
||||
else
|
||||
getglobal("BenillaCraftRequirements"):SetText("")
|
||||
end
|
||||
|
||||
if creatable then
|
||||
getglobal("BenillaCraftCreateButton"):Enable()
|
||||
else
|
||||
getglobal("BenillaCraftCreateButton"):Disable()
|
||||
end
|
||||
end
|
||||
|
||||
-- ── The list page (ref CraftFrame_Update, l.99-283 — the header/expand-icon/training-points
|
||||
-- tail is dropped: deviations 1/6, never reached in v1) ────────────────────────────────────────
|
||||
-- `<OnVerticalScroll>` is its own chunk and cannot see this file's
|
||||
-- `local CRAFT_SKILL_HEIGHT`, so the pitch is handed over here (1868).
|
||||
function BenillaCraftList_OnVerticalScroll()
|
||||
FauxScrollFrame_OnVerticalScroll(CRAFT_SKILL_HEIGHT, BenillaCraftFrame_Update)
|
||||
end
|
||||
|
||||
function BenillaCraftFrame_Update()
|
||||
getglobal("BenillaCraftFrameTitleText"):SetText(GetCraftName())
|
||||
getglobal("BenillaCraftCreateButton"):SetText(getglobal(GetCraftButtonToken()))
|
||||
|
||||
local numCrafts = GetNumCrafts()
|
||||
if numCrafts == 0 then
|
||||
BenillaCraftFrame_HideDetails()
|
||||
else
|
||||
getglobal("BenillaCraftCollapseAllButton"):Enable() -- ref l.158 (no matching Disable()
|
||||
-- anywhere in Craft's own ref — a real asymmetry vs. TradeSkill's own l.86/94 pair,
|
||||
-- kept as the ref has it, not "fixed").
|
||||
end
|
||||
|
||||
local scrollFrame = getglobal("BenillaCraftListScrollFrame")
|
||||
FauxScrollFrame_Update(scrollFrame, numCrafts, CRAFTS_DISPLAYED, CRAFT_SKILL_HEIGHT)
|
||||
BenillaScrollTrough_Follow("BenillaCraftListScrollFrame")
|
||||
local craftOffset = FauxScrollFrame_GetOffset(scrollFrame)
|
||||
|
||||
-- The skill-rank StatusBar (ref l.107-123): Show/Hide are real here (real widgets, not
|
||||
-- flattened Layer regions).
|
||||
local name, rank, maxRank = GetCraftDisplaySkillLine()
|
||||
local rankFrame = getglobal("BenillaCraftRankFrame")
|
||||
if name then
|
||||
getglobal("BenillaCraftRankFrameSkillName"):SetText(name)
|
||||
rankFrame:SetStatusBarColor(0.0, 0.0, 1.0, 0.5)
|
||||
getglobal("BenillaCraftRankFrameBackground"):SetVertexColor(0.0, 0.0, 0.75, 0.5)
|
||||
rankFrame:SetMinMaxValues(0, maxRank)
|
||||
rankFrame:SetValue(rank)
|
||||
getglobal("BenillaCraftRankFrameSkillRank"):SetText(rank .. "/" .. maxRank)
|
||||
rankFrame:Show()
|
||||
getglobal("BenillaCraftSkillBorderLeft"):Show()
|
||||
getglobal("BenillaCraftSkillBorderRight"):Show()
|
||||
else
|
||||
rankFrame:Hide()
|
||||
getglobal("BenillaCraftSkillBorderLeft"):Hide()
|
||||
getglobal("BenillaCraftSkillBorderRight"):Hide()
|
||||
end
|
||||
|
||||
local highlight = getglobal("BenillaCraftHighlightFrame")
|
||||
highlight:Hide()
|
||||
|
||||
local selection = GetCraftSelectionIndex()
|
||||
for i = 1, CRAFTS_DISPLAYED do
|
||||
local craftIndex = i + craftOffset
|
||||
local button = getglobal("BenillaCraft" .. i)
|
||||
if craftIndex <= numCrafts then
|
||||
local craftName, craftSubSpellName, craftType, numAvailable = GetCraftInfo(craftIndex)
|
||||
button.craftIndex = craftIndex
|
||||
local color = CraftTypeColor[craftType]
|
||||
if color then
|
||||
button:SetTextColor(color.r, color.g, color.b) -- ref l.190
|
||||
button.subtext:SetTextColor(color.r, color.g, color.b) -- ref l.195
|
||||
end
|
||||
if (numAvailable or 0) == 0 then
|
||||
button:SetText(" " .. craftName)
|
||||
else
|
||||
button:SetText(" " .. craftName .. " [" .. numAvailable .. "]")
|
||||
end
|
||||
if craftSubSpellName and craftSubSpellName ~= "" then
|
||||
button.subtext:SetText(format(PARENS_TEMPLATE, craftSubSpellName))
|
||||
else
|
||||
button.subtext:SetText("")
|
||||
end
|
||||
button:Show()
|
||||
-- TWO things paint a selected row, and the ref does both here (l.229-236): the
|
||||
-- glow bar is BenillaCraftHighlightFrame, and the WHITE LABEL is the row's own
|
||||
-- LockHighlight() — the template's `<HighlightFont inherits="GameFontHighlight">`
|
||||
-- instance winning over the difficulty colour SetTextColor just wrote, which
|
||||
-- reaches the NORMAL instance only. The subtext is whitened by hand because it is
|
||||
-- a child FontString, not the button's label, exactly as the ref does it (l.232).
|
||||
if selection == craftIndex then
|
||||
highlight:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0)
|
||||
highlight:Show()
|
||||
button:LockHighlight()
|
||||
button.subtext:SetTextColor(1.0, 1.0, 1.0)
|
||||
else
|
||||
button:UnlockHighlight()
|
||||
end
|
||||
else
|
||||
button.craftIndex = nil
|
||||
button:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- Count headers + how many stay folded (ref l.249-259), to drive the CollapseAll tab's own
|
||||
-- texture below — restored, deviation 1 (still functionally inert: GetCraftInfo never
|
||||
-- returns "header" in v1, so numHeaders is always 0 here).
|
||||
local numHeaders, notExpanded = 0, 0
|
||||
for i = 1, numCrafts do
|
||||
local craftName, craftSubSpellName, craftType, numAvailable, isExpanded = GetCraftInfo(i)
|
||||
if craftName and craftType == "header" then
|
||||
numHeaders = numHeaders + 1
|
||||
if not isExpanded then
|
||||
notExpanded = notExpanded + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
-- If all headers are not expanded then show the collapse texture, otherwise the expand one
|
||||
-- (ref l.261-267).
|
||||
local collapseAll = getglobal("BenillaCraftCollapseAllButton")
|
||||
if notExpanded ~= numHeaders then
|
||||
collapseAll.collapsed = nil
|
||||
collapseAll:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-Up")
|
||||
else
|
||||
collapseAll.collapsed = true
|
||||
collapseAll:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-Up")
|
||||
end
|
||||
-- Show the tab only when there are headers to fold (ref l.269-282; the row-text-reposition
|
||||
-- loops there stay dropped, deviation 1) — always Hidden in v1 since numHeaders is always 0.
|
||||
if numHeaders > 0 then
|
||||
getglobal("BenillaCraftExpandButtonFrame"):Show()
|
||||
else
|
||||
getglobal("BenillaCraftExpandButtonFrame"):Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- A row click selects it (ref CraftButton_OnClick + SetSelection's non-header path; the
|
||||
-- header-fold branch is dropped, deviation 1). Left-button only. NO OnEnter/OnLeave here —
|
||||
-- Craft's OWN ref never adds a row-hover tooltip (verified, header comment deviation 7).
|
||||
function BenillaCraftSkillButton_OnClick(button, mouseButton)
|
||||
if mouseButton ~= "LeftButton" then return end
|
||||
local index = button.craftIndex
|
||||
if not index then return end
|
||||
BenillaCraftFrame_SetSelection(index)
|
||||
BenillaCraftFrame_Update()
|
||||
end
|
||||
|
||||
-- The window's wheel handler (bubbled up the parent chain from a row) → one row via the faux
|
||||
-- kit (TradeSkillFrame.xml's own note: the engine bubbles a spin up the parent chain, so the
|
||||
-- handler lives on the window, not a child).
|
||||
function BenillaCraftFrame_OnMouseWheel(delta)
|
||||
BenillaFauxScrollFrame_OnMouseWheel(getglobal("BenillaCraftListScrollFrame"), delta)
|
||||
end
|
||||
|
||||
-- ── The CollapseAll tab (ref CraftCollapseAllButton_OnClick, l.417-425) — TradeSkillFrame.xml's
|
||||
-- own identical shape: toggles Expand/CollapseCraftSkillLine(0) off the button's own `.collapsed`
|
||||
-- flag (re-derived every Update() above), no direct BenillaCraftFrame_Update() call (the ref
|
||||
-- doesn't call one either). UNLIKE TradeSkill's own pair, `Expand`/`CollapseCraftSkillLine` are
|
||||
-- true engine no-ops here (header comment, deviation 1) — no touched flag, no CRAFT_UPDATE, so a
|
||||
-- click changes nothing observably beyond this button's own local state until the next Update()
|
||||
-- runs some other way. ──────────────────────────────────────────────────────────────────────
|
||||
function BenillaCraftCollapseAllButton_OnClick(self)
|
||||
if self.collapsed then
|
||||
self.collapsed = nil
|
||||
ExpandCraftSkillLine(0)
|
||||
else
|
||||
self.collapsed = true
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
|
||||
CollapseCraftSkillLine(0)
|
||||
end
|
||||
end
|
||||
|
||||
-- ── The detail icon (ref CraftIcon OnEnter, l.564-568 — the Ctrl/Shift dressing-room branches
|
||||
-- dropped, deviation 3): SetCraftSpell, NOT SetCraftItem — Craft has no product-item concept
|
||||
-- (craft.rs's own module doc). The binding behind it is a SELECTOR into the shared spell/item
|
||||
-- builders (`SetCraftSpell 0x533e90`); a CREATE_ITEM recipe therefore renders the product's
|
||||
-- full ITEM tooltip here even though the row's ICON is the spell's (Law D) — the two are
|
||||
-- different laws on the same row, byte-verified. ─────────────────────────────────────────────
|
||||
function BenillaCraftIcon_OnEnter(button)
|
||||
if GetCraftSelectionIndex() ~= 0 then
|
||||
GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetCraftSpell(GetCraftSelectionIndex())
|
||||
GameTooltip:Show()
|
||||
end
|
||||
end
|
||||
|
||||
-- ── The reagent slots (ref CraftItemTemplate OnEnter, l.31-35 — the dressing-room/chat-link
|
||||
-- OnClick branches dropped, deviation 3) ────────────────────────────────────────────────────
|
||||
function BenillaCraftReagent_OnLoad(button, index)
|
||||
button.reagentIndex = index
|
||||
local n = button:GetName()
|
||||
button.icon = getglobal(n .. "Icon")
|
||||
button.name = getglobal(n .. "Name")
|
||||
button.count = getglobal(n .. "Count")
|
||||
end
|
||||
|
||||
function BenillaCraftReagent_OnEnter(button)
|
||||
GameTooltip:SetOwner(button, "ANCHOR_TOPLEFT")
|
||||
GameTooltip:SetCraftItem(GetCraftSelectionIndex(), button.reagentIndex)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
|
||||
-- ── Create / Exit (ref l.654-670 — no Create All, no repeat-count input: deviation 8) ───────
|
||||
function BenillaCraftCreateButton_OnClick()
|
||||
-- Bare index → spell id, exactly the ref's own OnClick (ref l.667). An ENCHANT_ITEM recipe
|
||||
-- arms an app-side item-target pick from here — this XML plays no part in that (header
|
||||
-- comment, deviation 11).
|
||||
DoCraft(GetCraftSelectionIndex())
|
||||
end
|
||||
|
||||
function BenillaCraftCancelButton_OnClick()
|
||||
HideUIPanel(getglobal("BenillaCraftFrame"))
|
||||
end
|
||||
|
||||
-- ── The window lifecycle (MerchantFrame.xml/TradeSkillFrame.xml's own shape) ────────────────
|
||||
function BenillaCraftFrame_OnLoad(frame)
|
||||
frame:RegisterEvent("CRAFT_SHOW")
|
||||
frame:RegisterEvent("CRAFT_UPDATE")
|
||||
frame:RegisterEvent("CRAFT_CLOSE")
|
||||
-- SPELLS_CHANGED (ref l.58/92-93): a real, non-Beast-Training event (a newly learned enchant
|
||||
-- rank should refresh the list) — registered for fidelity; forward-compatible if this
|
||||
-- engine doesn't fire it yet (TradeSkillFrame.xml's own UNIT_PORTRAIT_UPDATE posture).
|
||||
-- UNIT_PET_TRAINING_POINTS (ref l.59) is NOT registered — nothing left to drive it
|
||||
-- (deviation 6, Craft_UpdateTrainingPoints doesn't exist in v1).
|
||||
frame:RegisterEvent("SPELLS_CHANGED")
|
||||
frame:RegisterEvent("UNIT_PORTRAIT_UPDATE")
|
||||
|
||||
local scrollFrame = getglobal("BenillaCraftListScrollFrame")
|
||||
end
|
||||
|
||||
-- Fresh session paint (ref CraftFrame_Show, l.26-49 — the header index-select branch is
|
||||
-- dropped, deviation 1; the selection-reset itself is ALSO overridden, deviation 10: this
|
||||
-- keeps a persisted selection instead of always forcing index 1).
|
||||
function BenillaCraftFrame_Show(frame)
|
||||
getglobal("BenillaCraftCreateButton"):Disable()
|
||||
if GetCraftSelectionIndex() == 0 then
|
||||
if GetNumCrafts() > 0 then
|
||||
BenillaCraftFrame_SetSelection(1)
|
||||
end
|
||||
else
|
||||
BenillaCraftFrame_SetSelection(GetCraftSelectionIndex())
|
||||
end
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
|
||||
SetPortraitTexture(getglobal("BenillaCraftFramePortrait"), "player")
|
||||
BenillaCraftFrame_Update()
|
||||
ShowUIPanel(frame)
|
||||
end
|
||||
|
||||
function BenillaCraftFrame_OnEvent(frame)
|
||||
if event == "CRAFT_SHOW" then
|
||||
BenillaCraftFrame_Show(frame)
|
||||
elseif event == "CRAFT_UPDATE" then
|
||||
-- ref l.68-87: re-anchor the selection if it's fallen off a (possibly shorter) list.
|
||||
local selection = GetCraftSelectionIndex()
|
||||
if selection > 1 and selection <= GetNumCrafts() then
|
||||
BenillaCraftFrame_SetSelection(selection)
|
||||
elseif GetNumCrafts() > 0 then
|
||||
BenillaCraftFrame_SetSelection(1)
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
|
||||
end
|
||||
BenillaCraftFrame_Update()
|
||||
elseif event == "CRAFT_CLOSE" then
|
||||
HideUIPanel(frame)
|
||||
elseif event == "SPELLS_CHANGED" then
|
||||
BenillaCraftFrame_Update()
|
||||
elseif event == "UNIT_PORTRAIT_UPDATE" then
|
||||
if arg1 == "player" then
|
||||
SetPortraitTexture(getglobal("BenillaCraftFramePortrait"), "player")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaCraftFrameCloseButton_OnClick()
|
||||
HideUIPanel(getglobal("BenillaCraftFrame"))
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<!--
|
||||
One list row (ref CraftButtonTemplate inherits ClassTrainerSkillButtonTemplate, l.4-28 for the
|
||||
template, l.344-415 for the instances): a 293×16 text row, name + a parenthesized rank subtext
|
||||
column (unlike TradeSkill's own row template, which carries no subname — this file's header
|
||||
comment) — TradeSkillFrame.xml's own BenillaTradeSkillSkillButtonTemplate shape, plus the fixed
|
||||
subtext column (TrainerFrame.xml's own Name/SubText fixed-column precedent, since this engine
|
||||
has no FontString-width-based dynamic anchor). NO OnEnter/OnLeave — Craft's own ref adds no row
|
||||
hover tooltip (header comment, deviation 7).
|
||||
-->
|
||||
<Button name="BenillaCraftSkillButtonTemplate" virtual="true" hidden="true">
|
||||
<Size><AbsDimension x="293" y="16"/></Size>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentSubText" inherits="GameFontNormalSmall" justifyH="LEFT">
|
||||
<Size><AbsDimension x="99" y="16"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="190" y="0"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<!-- THE ROW NAME IS THE BUTTON'S OWN ButtonText (the ref's `Craft<n>Text`), under the base's
|
||||
font TRIO — that is the whole white-on-hover / white-on-selected mechanism, and no script
|
||||
does it: `GameFontHighlight` is white, the engine swaps the highlighted instance in under
|
||||
the cursor, and `LockHighlight()` holds it there for the selected row. It was a
|
||||
`$parentName` FontString until decision 1605, on this file's own deviation 7 note that
|
||||
`Button:SetTextColor`/`LockHighlight` had no engine method — long since untrue.
|
||||
TradeSkillFrame.xml's row template is the twin, and both keep their own column geometry
|
||||
(LEFT+4 / 180×16 here) rather than the base's auto-size, so the switch moved no pixel. -->
|
||||
<ButtonText name="$parentText">
|
||||
<Size><AbsDimension x="180" y="16"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
|
||||
</ButtonText>
|
||||
<NormalFont inherits="GameFontNormal" justifyH="LEFT"/>
|
||||
<HighlightFont inherits="GameFontHighlight" justifyH="LEFT"/>
|
||||
<DisabledFont inherits="GameFontDisable" justifyH="LEFT"/>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
self.subtext = getglobal(self:GetName() .. "SubText")
|
||||
</OnLoad>
|
||||
<OnClick>BenillaCraftSkillButton_OnClick(self, arg1)</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!--
|
||||
One reagent slot. The ref's `CraftItemTemplate` (l.29-53) inherits `QuestItemTemplate` and
|
||||
overrides **only scripts** — same as TradeSkillFrame's `TradeSkillItemTemplate`, so the two
|
||||
windows' slots really are the byte-identical layout this comment always claimed; what they
|
||||
share is QuestItemTemplate's shape, which neither window had until B250 (decision 1595).
|
||||
A 39×39 icon, the `UI-QuestItemNameFrame` plate riding its right edge, the name centred ON the
|
||||
plate, and the "have /need" count on the icon's own BOTTOMRIGHT. This file keeps its OWN copy
|
||||
(StackSplit.xml's "not a cross-file inherits" note), kept in sync by hand.
|
||||
-->
|
||||
<Button name="BenillaCraftReagentTemplate" virtual="true" hidden="true">
|
||||
<Size><AbsDimension x="147" y="41"/></Size>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentIcon">
|
||||
<Size><AbsDimension x="39" y="39"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
|
||||
</Texture>
|
||||
<!-- Declared AFTER the icon (same parent+level ⇒ z-tiebreak): the plate's 128×64
|
||||
texture starts 10px INSIDE the icon's right edge, but its art only begins 11
|
||||
texels in, so the visible plate opens 1px clear of the icon. -->
|
||||
<Texture name="$parentNameFrame" file="Interface\QuestFrame\UI-QuestItemNameFrame">
|
||||
<Size><AbsDimension x="128" y="64"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="$parentIcon" relativePoint="RIGHT"><Offset><AbsDimension x="-10" y="0"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<FontString name="$parentName" inherits="GameFontHighlight" justifyH="LEFT">
|
||||
<Size><AbsDimension x="90" y="36"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="$parentNameFrame"><Offset><AbsDimension x="15" y="0"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentCount" inherits="NumberFontNormal" justifyH="RIGHT">
|
||||
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentIcon"><Offset><AbsDimension x="-4" y="1"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnEnter>BenillaCraftReagent_OnEnter(self)</OnEnter>
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- A panel action button (Create/Exit): the UIPanelButtonTemplate face locally reconstructed
|
||||
(this house dialect ships no such template — TalentFrame.xml's own ENGINE API note),
|
||||
TradeSkillFrame.xml's identical-shaped BenillaTradeSkillPanelButtonTemplate — its OWN local
|
||||
copy per this house's per-window convention (StackSplit.xml's own "not a cross-file inherits"
|
||||
note), not a cross-file reuse of that other window's virtual template. -->
|
||||
<Button name="BenillaCraftPanelButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="80" y="22"/></Size>
|
||||
<NormalFont inherits="GameFontNormal"/>
|
||||
<HighlightFont inherits="GameFontHighlight"/>
|
||||
<DisabledFont inherits="GameFontDisable"/>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
|
||||
<DisabledTexture file="Interface\Buttons\UI-Panel-Button-Disabled"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></DisabledTexture>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
|
||||
</Button>
|
||||
|
||||
<Frame name="BenillaCraftFrame" hidden="true">
|
||||
<!-- The real 384×512 window. No <Anchors> — ShowUIPanel/SetLeftFrame SetPoints it to the left
|
||||
slot (MerchantFrame.xml/TradeSkillFrame.xml's own convention). -->
|
||||
<Size><AbsDimension x="384" y="512"/></Size>
|
||||
<!-- Transcribed for fidelity (ref l.65-67); no loader wiring exists yet for the hit-rect
|
||||
shrink — TalentFrame.xml's own "transcribe the hook even if nothing drives it yet"
|
||||
posture. -->
|
||||
<HitRectInsets><AbsInset left="0" right="34" top="0" bottom="75"/></HitRectInsets>
|
||||
<Layers>
|
||||
<!-- "player" portrait 60×60 TOPLEFT (7,-6) (ref l.70-81), BACKGROUND so the window art's
|
||||
ring frames it. Bound via SetPortraitTexture(region, "player") in Show/OnEvent — not an
|
||||
NPC session (header comment). -->
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="BenillaCraftFramePortrait">
|
||||
<Size><AbsDimension x="60" y="60"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<!-- The four quadrant slabs that ARE the window art (ref l.83-125): ALL FOUR from
|
||||
ClassTrainerFrame's shared art — unlike TradeSkill's own mixed set (its BotLeft comes
|
||||
from TradeSkillFrame's own art instead), Craft's ref uses one uniform art family
|
||||
throughout, transcribed exactly as given. -->
|
||||
<Layer level="BORDER">
|
||||
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-TopLeft"><Size><AbsDimension x="256" y="256"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors></Texture>
|
||||
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-TopRight"><Size><AbsDimension x="128" y="256"/></Size><Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"/></Anchors></Texture>
|
||||
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-BotLeft"><Size><AbsDimension x="256" y="256"/></Size><Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"/></Anchors></Texture>
|
||||
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-BotRight"><Size><AbsDimension x="128" y="256"/></Size><Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"/></Anchors></Texture>
|
||||
<!-- Title: GetCraftName() directly — unlike TradeSkill's "%s"-formatted TRADE_SKILL_TITLE,
|
||||
Craft's ref sets this raw (ref l.101: CraftFrameTitleText:SetText(GetCraftName())).
|
||||
"Enchanting" is a harmless static placeholder until the first paint. -->
|
||||
<FontString name="BenillaCraftFrameTitleText" inherits="GameFontNormal" justifyH="CENTER" text="Enchanting">
|
||||
<Anchors><Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
<!-- The skill-border + horizontal-divider decorative bars (ref l.126-174), sampled from the
|
||||
same two shared ClassTrainer/TradeSkill textures by TexCoord — byte-identical to
|
||||
TradeSkillFrame.xml's own equivalents. -->
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="BenillaCraftSkillBorderLeft" file="Interface\TradeSkillFrame\UI-TradeSkill-SkillBorder">
|
||||
<Size><AbsDimension x="256" y="8"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="63" y="-50"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0" bottom="0.25"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaCraftSkillBorderRight" file="Interface\TradeSkillFrame\UI-TradeSkill-SkillBorder">
|
||||
<Size><AbsDimension x="28" y="8"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftSkillBorderLeft" relativePoint="RIGHT"/></Anchors>
|
||||
<TexCoords left="0" right="0.109375" top="0.25" bottom="0.5"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaCraftHorizontalBarLeft" file="Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar">
|
||||
<Size><AbsDimension x="256" y="16"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="15" y="-221"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0" bottom="0.25"/>
|
||||
</Texture>
|
||||
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar">
|
||||
<Size><AbsDimension x="75" y="16"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftHorizontalBarLeft" relativePoint="RIGHT"/></Anchors>
|
||||
<TexCoords left="0" right="0.29296875" top="0.25" bottom="0.5"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<!-- The FIXED detail-pane FontStrings + header art (ref CraftDetailScrollChildFrame,
|
||||
l.456-537), flattened onto this window's OWN Layers at OVERLAY (the flat-layout gap,
|
||||
this file's header comment) — computed from the ref's ScrollFrame anchor (TOPLEFT
|
||||
20,-234, byte-identical to TradeSkillFrame's own) + each region's own ScrollChild-
|
||||
relative offset. Declaration order matters (same parent+level ⇒ z-tiebreak,
|
||||
TradeSkillFrame.xml's own note): the header art is declared BEFORE ReagentLabel so the
|
||||
"Reagents: " banner text paints over its lower edge, exactly as the reference does.
|
||||
CraftCost (ref l.483-491, the per-recipe training-point cost line) is DROPPED — deviation 6,
|
||||
always blank in v1. -->
|
||||
<Layer level="OVERLAY">
|
||||
<FontString name="BenillaCraftName" inherits="GameFontNormal" justifyH="LEFT">
|
||||
<Size><AbsDimension x="244" y="0"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="70" y="-239"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<!-- ONE concatenated FontString (Craft's own ref, unlike TradeSkill's split
|
||||
Label+Text pair — header comment). -->
|
||||
<FontString name="BenillaCraftRequirements" inherits="GameFontHighlightSmall" justifyH="LEFT">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftName" relativePoint="BOTTOMLEFT"/></Anchors>
|
||||
</FontString>
|
||||
<Texture name="BenillaCraftDetailHeaderLeft" file="Interface\ClassTrainerFrame\UI-ClassTrainer-DetailHeaderLeft">
|
||||
<Size><AbsDimension x="256" y="64"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="20" y="-231"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-DetailHeaderRight">
|
||||
<Size><AbsDimension x="64" y="64"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftDetailHeaderLeft" relativePoint="TOPRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
<!-- The recipe's own description (ref l.516-527) — TradeSkill's detail pane has no
|
||||
analogue for this region (header comment). Its BOTTOMLEFT is the ReagentLabel's
|
||||
DYNAMIC anchor target, re-pointed every SetSelection (SetSelection's own Lua). -->
|
||||
<FontString name="BenillaCraftDescription" inherits="GameFontHighlightSmall" justifyH="LEFT">
|
||||
<Size><AbsDimension x="290" y="0"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="25" y="-284"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<!-- STATIC always-shown label (TradeSkillFrame.xml's own precedent: the ref's
|
||||
Show/Hide-on-reagent-count dance is dropped, every recipe here has ≥1 reagent in
|
||||
practice) — its POSITION is dynamic (SetSelection), its text/visibility is not. -->
|
||||
<FontString name="BenillaCraftReagentLabel" inherits="GameFontNormalSmall" text="Reagents: ">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftDescription" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-10"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- The top-right close X (ref a UIPanelCloseButton at TOPRIGHT -29,-8, l.688-696; the
|
||||
minimize-button art stands in — this house dialect ships no such template,
|
||||
TradeSkillFrame.xml's own precedent). -->
|
||||
<Button name="BenillaCraftFrameCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-29" y="-8"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
|
||||
<Scripts><OnClick>BenillaCraftFrameCloseButton_OnClick()</OnClick></Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- The skill-rank StatusBar (ref CraftRankFrame, l.196-264): blue fill, "name rank/max"
|
||||
text, the UI-Character-Skills-Bar[Border] art — byte-identical to TradeSkillFrame's own.
|
||||
SKILL_LINES_CHANGED self-listen dropped (deviation 4). -->
|
||||
<StatusBar name="BenillaCraftRankFrame" drawLayer="BACKGROUND" minValue="0" maxValue="1" defaultValue="0">
|
||||
<Size><AbsDimension x="268" y="15"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="73" y="-37"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="BenillaCraftRankFrameSkillName" inherits="GameFontNormalSmall">
|
||||
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="6" y="1"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<FontString name="BenillaCraftRankFrameSkillRank" inherits="GameFontHighlightSmall" justifyH="LEFT">
|
||||
<Size><AbsDimension x="128" y="0"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftRankFrameSkillName" relativePoint="RIGHT"><Offset><AbsDimension x="13" y="0"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="BenillaCraftRankFrameBackground"><Color r="1.0" g="1.0" b="1.0" a="0.2"/></Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button name="BenillaCraftRankFrameBorder">
|
||||
<Size><AbsDimension x="281" y="32"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\PaperDollInfoFrame\UI-Character-Skills-BarBorder"/>
|
||||
</Button>
|
||||
</Frames>
|
||||
<BarTexture name="BenillaCraftRankFrameBar" file="Interface\PaperDollInfoFrame\UI-Character-Skills-Bar"/>
|
||||
<BarColor r="0.25" g="0.25" b="0.75"/>
|
||||
</StatusBar>
|
||||
|
||||
<!-- The expand/collapse-all tab (ref CraftExpandButtonFrame, l.265-325): verbatim
|
||||
geometry/art — TradeSkillFrame.xml's own identical tab frame, EXCEPT the button itself
|
||||
is 22×22 here (Craft's own ref value, not TradeSkill's 40×22 — each window's own ref,
|
||||
not copy-pasted). TrainerFrame.xml's own BenillaTrainerCollapseAllButton is the
|
||||
reconstruction precedent this mirrors (standalone Button, not this file's row template —
|
||||
header comment, deviation 1). Always ends up Hidden in v1 (Update()'s own tail, header
|
||||
comment) — ships for fidelity, not yet reachable. -->
|
||||
<Frame name="BenillaCraftExpandButtonFrame">
|
||||
<Size><AbsDimension x="54" y="32"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="15" y="-71"/></Offset></Anchor></Anchors>
|
||||
<Layers><Layer level="BACKGROUND">
|
||||
<Texture name="BenillaCraftExpandTabLeft" file="Interface\ClassTrainerFrame\UI-ClassTrainer-ExpandTab-Left"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors></Texture>
|
||||
<Texture name="BenillaCraftExpandTabMiddle" file="Interface\QuestFrame\UI-QuestLogSortTab-Middle"><Size><AbsDimension x="38" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="BenillaCraftExpandTabLeft" relativePoint="RIGHT"><Offset><AbsDimension x="0" y="6"/></Offset></Anchor></Anchors></Texture>
|
||||
<Texture file="Interface\QuestFrame\UI-QuestLogSortTab-Right"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="BenillaCraftExpandTabMiddle" relativePoint="RIGHT"/></Anchors></Texture>
|
||||
</Layer></Layers>
|
||||
<Frames>
|
||||
<Button name="BenillaCraftCollapseAllButton" text="All">
|
||||
<Size><AbsDimension x="22" y="22"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftExpandTabLeft" relativePoint="RIGHT"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-MinusButton-Up"><Size><AbsDimension x="16" y="16"/></Size><Anchors><Anchor point="LEFT" relativePoint="LEFT"/></Anchors></NormalTexture>
|
||||
<HighlightTexture file="Interface\Buttons\UI-PlusButton-Hilight" alphaMode="ADD"><Size><AbsDimension x="16" y="16"/></Size><Anchors><Anchor point="LEFT" relativePoint="LEFT"/></Anchors></HighlightTexture>
|
||||
<ButtonText><Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="18" y="0"/></Offset></Anchor></Anchors></ButtonText>
|
||||
<NormalFont inherits="GameFontNormalSmall" justifyH="LEFT"/>
|
||||
<Scripts>
|
||||
<OnClick>BenillaCraftCollapseAllButton_OnClick(self)</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- The persistent selection highlight (ref CraftHighlightFrame, l.331-343): 293×16,
|
||||
ARTWORK-layer UI-Listbox-Highlight2. Declared BEFORE the rows so the row text paints
|
||||
OVER the glow (TradeSkillFrame.xml's own z-tiebreak note). Vertex-colored by the
|
||||
selected recipe's CraftTypeColor (SetSelection), positioned onto the selected visible
|
||||
row (Update). -->
|
||||
<Frame name="BenillaCraftHighlightFrame" hidden="true">
|
||||
<Size><AbsDimension x="293" y="16"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
|
||||
<Layers><Layer level="ARTWORK">
|
||||
<Texture name="BenillaCraftHighlight" file="Interface\Buttons\UI-Listbox-Highlight2">
|
||||
<Anchors><Anchor point="TOPLEFT"/><Anchor point="BOTTOMRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
</Layer></Layers>
|
||||
</Frame>
|
||||
|
||||
<!-- 8 list rows (ref Craft1..8, l.344-415): 293×16, row1 TOPLEFT (22,-96); rows 2-8 chain
|
||||
TOPLEFT→prior BOTTOMLEFT with no gap — byte-identical to TradeSkillFrame's own. -->
|
||||
<Button name="BenillaCraft1" inherits="BenillaCraftSkillButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-96"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BenillaCraft2" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft1" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="BenillaCraft3" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft2" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="BenillaCraft4" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft3" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="BenillaCraft5" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft4" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="BenillaCraft6" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft5" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="BenillaCraft7" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft6" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="BenillaCraft8" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft7" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
|
||||
<!-- The faux-scroll frame (FauxScrollFrameTemplate, ScrollTemplates.xml): invisible
|
||||
container over the 8 rows; only its right-hand Slider scroll bar renders. Sized to the
|
||||
8-row span (CRAFTS_DISPLAYED × CRAFT_SKILL_HEIGHT), TradeSkillFrame.xml's own
|
||||
same-TOPLEFT-as-row1 convention. updateFunc set in BenillaCraftFrame_OnLoad. -->
|
||||
<ScrollFrame name="BenillaCraftListScrollFrame" inherits="FauxScrollFrameTemplate">
|
||||
<Size><AbsDimension x="293" y="128"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-96"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<!-- The reference's kit writes `frame.offset` ONLY from here; its
|
||||
`updateFunc` field does not exist, so without this the bar moves and
|
||||
the list does not follow (1868). -->
|
||||
<OnVerticalScroll>BenillaCraftList_OnVerticalScroll()</OnVerticalScroll>
|
||||
</Scripts>
|
||||
</ScrollFrame>
|
||||
|
||||
<!-- The detail icon (ref CraftIcon, l.540-577): a 37×37 button over a 64×64 UI-EmptySlot
|
||||
ring (TrainerFrame.xml's own BenillaTrainerSkillIcon shape, TradeSkillFrame.xml's own
|
||||
identical reuse) at the flattened header art's top-left. No made-count overlay — Craft
|
||||
has no min/max-made concept (craft.rs's own module doc), unlike TradeSkill's own icon. -->
|
||||
<Button name="BenillaCraftIcon" hidden="true">
|
||||
<Size><AbsDimension x="37" y="37"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="28" y="-237"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture file="Interface\Buttons\UI-EmptySlot"><Size><AbsDimension x="64" y="64"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="-13" y="13"/></Offset></Anchor></Anchors></Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnEnter>BenillaCraftIcon_OnEnter(self)</OnEnter>
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Up to 8 reagents, the ref's own 2-column grid + anchor chain (l.578-649): odd slots
|
||||
chain down the left column (TOPLEFT→prior BOTTOMLEFT, -2 gap), even slots sit LEFT of
|
||||
the odd slot in the same row. Reagent7 chains off Reagent6 (not Reagent5) — a literal
|
||||
ref quirk shared with TradeSkillFrame's own reagent grid (this file's header comment,
|
||||
deviation 9's sibling note): harmless, since 6's row-mate LEFT anchor to 5 already puts
|
||||
it at 5's own row height. Reagent1's own offset is (0,-3) here — Craft's ref literal
|
||||
value, NOT TradeSkill's own (-5,-3) (transcribed from each window's own ref, not
|
||||
copy-pasted). -->
|
||||
<Button name="BenillaCraftReagent1" inherits="BenillaCraftReagentTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftReagentLabel" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 1)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaCraftReagent2" inherits="BenillaCraftReagentTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftReagent1" relativePoint="RIGHT"/></Anchors>
|
||||
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 2)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaCraftReagent3" inherits="BenillaCraftReagentTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftReagent1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 3)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaCraftReagent4" inherits="BenillaCraftReagentTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftReagent3" relativePoint="RIGHT"/></Anchors>
|
||||
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 4)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaCraftReagent5" inherits="BenillaCraftReagentTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftReagent3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 5)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaCraftReagent6" inherits="BenillaCraftReagentTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftReagent5" relativePoint="RIGHT"/></Anchors>
|
||||
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 6)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaCraftReagent7" inherits="BenillaCraftReagentTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftReagent6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 7)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaCraftReagent8" inherits="BenillaCraftReagentTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftReagent7" relativePoint="RIGHT"/></Anchors>
|
||||
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 8)</OnLoad></Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- The action row (ref l.654-696): Create / Exit only — NO Create All, NO -/+, NO
|
||||
repeat-count EditBox (deviation 8). Positions byte-identical to TradeSkillFrame's own
|
||||
Create/Cancel buttons (ref l.658-663/676-681 vs Craft's own l.658-663/675-680 — the
|
||||
SAME two constants in both refs). -->
|
||||
<Button name="BenillaCraftCreateButton" inherits="BenillaCraftPanelButtonTemplate" text="Create">
|
||||
<Anchors><Anchor point="CENTER" relativeTo="BenillaCraftFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="224" y="-422"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnClick>BenillaCraftCreateButton_OnClick()</OnClick></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaCraftCancelButton" inherits="BenillaCraftPanelButtonTemplate" text="Exit">
|
||||
<Anchors><Anchor point="CENTER" relativeTo="BenillaCraftFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="305" y="-422"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnClick>BenillaCraftCancelButton_OnClick()</OnClick></Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<!-- The window lifecycle (MerchantFrame.xml/TradeSkillFrame.xml's own shape): OnHide calls
|
||||
CloseCraft() for ANY hide reason. The list wheel lives on the WINDOW (the engine bubbles a
|
||||
spin up the parent chain). -->
|
||||
<Scripts>
|
||||
<OnLoad>BenillaCraftFrame_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaCraftFrame_OnEvent(self)</OnEvent>
|
||||
<OnMouseWheel>BenillaCraftFrame_OnMouseWheel(arg1)</OnMouseWheel>
|
||||
<OnShow>PlaySound("igCharacterInfoOpen")</OnShow>
|
||||
<OnHide>CloseCraft(); PlaySound("igCharacterInfoClose")</OnHide>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
@@ -1,318 +0,0 @@
|
||||
<Ui>
|
||||
<!--
|
||||
The dressing room (decision 1060) — ref `DressUpFrame.xml` / `DressUpFrame.lua`, quoted.
|
||||
CTRL-left-click an item anywhere (bags, the paper doll, a chat link, the bank) and it lands
|
||||
on your own character here, over the gear you are actually wearing. The window's own
|
||||
instruction line says exactly that, in Blizzard's words:
|
||||
`DRESSUP_FRAME_INSTRUCTIONS` = "CTRL-Left Click additional items to display them on your
|
||||
character" (ref-GlobalStrings.lua:944) — which is also where the modifier is settled: CTRL
|
||||
previews, SHIFT posts the link to chat (`ContainerFrame.lua:565-571`).
|
||||
|
||||
THE MODEL PANE is a booth-texture stand-in, not the ref's live `<DressUpModel>` widget —
|
||||
the settled doctrine (0105/0118) and exactly what the character window's own pane does
|
||||
(`CharacterFrame.xml`'s header). So the ref's three model METHODS have no widget to live on
|
||||
and become the engine globals `BenillaDressUpModel_Dress/TryOn/Close/SetFacing`
|
||||
(`benilla-ui/src/script/dressup.rs` has the why); `DressUpItem`/`DressUpItemLink` keep their
|
||||
real global names, because every ctrl-click site in the UI calls those.
|
||||
|
||||
Departures, each named:
|
||||
- No drag (the ref is `movable` with StartMoving/StopMovingOrSizing): benilla has no
|
||||
user-moved-frame affordance yet — the window sits in the left UIPanel slot, which the ref
|
||||
registers it into too (`UIParent.lua:41`, transcribed in UiPanels.xml).
|
||||
- `DressUpModel:SetUnit("player")` and `:Dress()` are ONE verb here
|
||||
(`BenillaDressUpModel_Dress`): our model is always the player's, so "point it at the
|
||||
player" and "put the player's own gear back on it" are the same effect.
|
||||
- The ref's `scale="2.0"` on the model widget is a 3D-widget zoom with no meaning for a
|
||||
baked pane — the booth frames the body from its own bounds (`framing::body_frame`), the
|
||||
same law the paper doll is framed by. The bake is framed at THIS pane's 316×351 aspect,
|
||||
not square, or the UI's stretch would leave every character 11% too tall (decision 1069).
|
||||
- The pane ANIMATES, as the ref's live widget does — Stand looping (decision 1069; the look
|
||||
call 0822 §5 left open, made by the director). Its booth is TRANSPARENT for the same
|
||||
reason a widget is: the dark room behind the character is the `DressUpBackground-<Race>`
|
||||
art below, and Reset/Close sit on that art *inside* this pane's rect (both anchor sets
|
||||
transcribed byte-identically from the ref, which is why the overlap is the ref's too).
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- GlobalStrings this window uses (ref-GlobalStrings.lua, cited per line; the
|
||||
-- `X = X or "..."` idiom is this codebase's convention — CharacterFrame.xml/InspectFrame.xml).
|
||||
DRESSUP_FRAME = DRESSUP_FRAME or "Dressing Room" -- :943
|
||||
DRESSUP_FRAME_INSTRUCTIONS = DRESSUP_FRAME_INSTRUCTIONS
|
||||
or "CTRL-Left Click additional items to display them on your character" -- :944
|
||||
RESET = RESET or "Reset" -- :3340
|
||||
CLOSE = CLOSE or "Close" -- :760
|
||||
|
||||
-- ref DressUpFrame.lua:2-8. The `not IsVisible()` gate is the reference's own state reset:
|
||||
-- opening the room fresh re-dresses the model in the player's real gear, and only then
|
||||
-- tries the clicked item on. Both halves ride ONE ordered intent queue app-side, so the
|
||||
-- reset can never overtake the try-on (dressup.rs's module doc).
|
||||
function DressUpItem(item)
|
||||
if not DressUpFrame:IsVisible() then
|
||||
ShowUIPanel(DressUpFrame)
|
||||
BenillaDressUpModel_Dress() -- ref: DressUpModel:SetUnit("player")
|
||||
end
|
||||
BenillaDressUpModel_TryOn(item) -- ref: DressUpModel:TryOn(item)
|
||||
end
|
||||
|
||||
-- ref DressUpFrame.lua:10-16, verbatim: pull the bare item id out of the `|Hitem:<id>:…`
|
||||
-- payload and hand THAT to TryOn. Every ctrl-click site in the UI calls this one function,
|
||||
-- which is why it keeps the reference's global name.
|
||||
function DressUpItemLink(link)
|
||||
if not link then
|
||||
return
|
||||
end
|
||||
local item = gsub(link, ".*item:(%d+).*", "%1", 1)
|
||||
DressUpItem(tonumber(item))
|
||||
end
|
||||
|
||||
-- ref DressUpFrame.lua:18-32 including its own "-- HACK!!!" comment: gnomes borrow the
|
||||
-- dwarf backdrop and trolls the orc one (there is no Gnome/Troll art), and an unknown race
|
||||
-- falls back to Orc. Quoted, not corrected.
|
||||
function DressUpTexturePath()
|
||||
local race, fileName = UnitRace("player")
|
||||
if fileName == "Gnome" or fileName == "GNOME" then
|
||||
fileName = "Dwarf"
|
||||
elseif fileName == "Troll" or fileName == "TROLL" then
|
||||
fileName = "Orc"
|
||||
end
|
||||
if not fileName then
|
||||
fileName = "Orc"
|
||||
end
|
||||
return "Interface\\DressUpFrame\\DressUpBackground-" .. fileName
|
||||
end
|
||||
|
||||
-- ref DressUpFrame.lua:34-40. Called on OnShow as well as OnLoad: at load time the app has
|
||||
-- usually not pushed the player's unit snapshot yet, so `UnitRace` answers nil and the ref's
|
||||
-- own fallback would freeze an Orc backdrop onto every character (the same one-tick nil the
|
||||
-- character window's level line documents).
|
||||
function SetDressUpBackground()
|
||||
local texture = DressUpTexturePath()
|
||||
DressUpBackgroundTopLeft:SetTexture(texture .. 1)
|
||||
DressUpBackgroundTopRight:SetTexture(texture .. 2)
|
||||
DressUpBackgroundBotLeft:SetTexture(texture .. 3)
|
||||
DressUpBackgroundBotRight:SetTexture(texture .. 4)
|
||||
end
|
||||
|
||||
-- The pane's facing — the paper doll's pair of handlers, over this room's own booth yaw
|
||||
-- (ref Model_OnLoad/Model_RotateLeft/Model_RotateRight/Model_OnUpdate, UIParent.lua:1421-1462;
|
||||
-- CharacterFrame.xml carries the full note on the ref's sign quirk, quoted here too).
|
||||
function BenillaDressUpModel_OnLoad(frame)
|
||||
frame.facing = 0.61 -- ref UIParent.lua:1422, the literal default facing
|
||||
BenillaDressUpModel_SetFacing(frame.facing)
|
||||
end
|
||||
|
||||
function BenillaDressUpModel_RotateLeft(frame)
|
||||
frame.facing = frame.facing - 0.03
|
||||
BenillaDressUpModel_SetFacing(frame.facing)
|
||||
PlaySound("igInventoryRotateCharacter")
|
||||
end
|
||||
|
||||
function BenillaDressUpModel_RotateRight(frame)
|
||||
frame.facing = frame.facing + 0.03
|
||||
BenillaDressUpModel_SetFacing(frame.facing)
|
||||
PlaySound("igInventoryRotateCharacter")
|
||||
end
|
||||
|
||||
-- The held-button spin (ref Model_OnUpdate) — half a turn per second while a rotate button
|
||||
-- reads PUSHED, the ref's own opposite-sign held branches included.
|
||||
function BenillaDressUpModel_OnUpdate(frame, elapsed)
|
||||
local step = elapsed * 2 * PI * ROTATIONS_PER_SECOND
|
||||
if getglobal(frame:GetName() .. "RotateLeftButton"):GetButtonState() == "PUSHED" then
|
||||
frame.facing = frame.facing + step
|
||||
if frame.facing < 0 then
|
||||
frame.facing = frame.facing + (2 * PI)
|
||||
end
|
||||
BenillaDressUpModel_SetFacing(frame.facing)
|
||||
end
|
||||
if getglobal(frame:GetName() .. "RotateRightButton"):GetButtonState() == "PUSHED" then
|
||||
frame.facing = frame.facing - step
|
||||
if frame.facing > (2 * PI) then
|
||||
frame.facing = frame.facing - (2 * PI)
|
||||
end
|
||||
BenillaDressUpModel_SetFacing(frame.facing)
|
||||
end
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<!-- A panel action button (Reset/Close): the ref's UIPanelButtonTemplate face, locally
|
||||
reconstructed — this house dialect ships no such template, and each window keeps its own
|
||||
copy (CraftFrame.xml's identical note). -->
|
||||
<Button name="BenillaDressUpPanelButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="80" y="22"/></Size>
|
||||
<NormalFont inherits="GameFontNormal"/>
|
||||
<HighlightFont inherits="GameFontHighlight"/>
|
||||
<DisabledFont inherits="GameFontDisable"/>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
|
||||
<DisabledTexture file="Interface\Buttons\UI-Panel-Button-Disabled"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></DisabledTexture>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
|
||||
</Button>
|
||||
|
||||
<Frame name="DressUpFrame" enableMouse="true" parent="UIParent" toplevel="true" hidden="true">
|
||||
<!-- The ref's 384×512 window. No <Anchors>: ShowUIPanel seats it in the left slot, this
|
||||
house's convention for every panel window (MerchantFrame.xml/CraftFrame.xml). -->
|
||||
<Size><AbsDimension x="384" y="512"/></Size>
|
||||
<HitRectInsets><AbsInset left="0" right="30" top="0" bottom="45"/></HitRectInsets>
|
||||
<Layers>
|
||||
<!-- The "player" portrait, 60×60 at TOPLEFT (7,-6) (ref l.20-33) — BACKGROUND so the
|
||||
window art's ring frames it; bound in OnShow, like every other portrait here. -->
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="DressUpFramePortrait">
|
||||
<Size><AbsDimension x="60" y="60"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<!-- The window art: the general-panel top pair + the skill-frame bottom pair, the ref's
|
||||
own four slabs (l.34-72). -->
|
||||
<Layer level="ARTWORK">
|
||||
<Texture file="Interface\PaperDollInfoFrame\UI-Character-General-TopLeft">
|
||||
<Size><AbsDimension x="256" y="256"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||||
</Texture>
|
||||
<Texture file="Interface\PaperDollInfoFrame\UI-Character-General-TopRight">
|
||||
<Size><AbsDimension x="128" y="256"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="256" y="0"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<Texture file="Interface\PaperDollInfoFrame\SkillFrame-BotLeft">
|
||||
<Size><AbsDimension x="256" y="256"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-256"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<Texture file="Interface\PaperDollInfoFrame\SkillFrame-BotRight">
|
||||
<Size><AbsDimension x="128" y="256"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="256" y="-256"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<FontString name="DressUpFrameTitleText" inherits="GameFontHighlight" text="DRESSUP_FRAME">
|
||||
<Size><AbsDimension x="300" y="14"/></Size>
|
||||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<FontString name="DressUpFrameDescriptionText" inherits="GameFontNormalSmall" text="DRESSUP_FRAME_INSTRUCTIONS">
|
||||
<Size><AbsDimension x="260" y="36"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativeTo="DressUpFrameTitleText" relativePoint="BOTTOM"><Offset><AbsDimension x="10" y="-22"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
<!-- The race backdrop behind the model, four quadrants (ref l.73-118); the files are
|
||||
chosen per race by SetDressUpBackground above. -->
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="DressUpBackgroundTopLeft">
|
||||
<Size><AbsDimension x="256" y="255"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="22" y="-76"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<Texture name="DressUpBackgroundTopRight">
|
||||
<Size><AbsDimension x="62" y="255"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="DressUpBackgroundTopLeft" relativePoint="TOPRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
<Texture name="DressUpBackgroundBotLeft">
|
||||
<Size><AbsDimension x="256" y="128"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="DressUpBackgroundTopLeft" relativePoint="BOTTOMLEFT"/></Anchors>
|
||||
</Texture>
|
||||
<Texture name="DressUpBackgroundBotRight">
|
||||
<Size><AbsDimension x="62" y="128"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="DressUpBackgroundTopLeft" relativePoint="BOTTOMRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- The corner close X: the ref's UIPanelCloseButton seat (CENTER of TOPRIGHT, -46,-25 —
|
||||
ref l.120-127) on the minimize-button art every close button in this house uses. -->
|
||||
<Button name="DressUpFrameCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-46" y="-25"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnClick>HideUIPanel(DressUpFrame)</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<!-- Close (ref l.128-146) and Reset (l.147-165, chained off Close's LEFT). Reset is the
|
||||
ref's `DressUpModel:Dress()` + its gsTitleOptionOK click. -->
|
||||
<Button name="DressUpFrameCancelButton" inherits="BenillaDressUpPanelButtonTemplate" text="CLOSE">
|
||||
<Anchors><Anchor point="CENTER" relativePoint="TOPLEFT"><Offset><AbsDimension x="305" y="-422"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnClick>HideUIPanel(DressUpFrame)</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="DressUpFrameResetButton" inherits="BenillaDressUpPanelButtonTemplate" text="RESET">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="DressUpFrameCancelButton" relativePoint="LEFT"/></Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
BenillaDressUpModel_Dress()
|
||||
PlaySound("gsTitleOptionOK")
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- The model pane (ref the <DressUpModel> widget, l.166-207): 316×351 anchored BOTTOM
|
||||
(-11,+85). A plain Frame sampling the "dressup" booth bake (header). -->
|
||||
<Frame name="DressUpModelFrame">
|
||||
<Size><AbsDimension x="316" y="351"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="-11" y="85"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
<!-- BACKGROUND, not ARTWORK — the booth quad is an OPAQUE stand-in for a widget
|
||||
the reference renders outside the layer batches, so it must sit as low as a
|
||||
region goes or it swallows same-level siblings (decision 1070; the pet
|
||||
page's diet icon is where that bit). This window is the one where the
|
||||
change is visible: the CLOSE/RESET buttons are declared BEFORE the pane and
|
||||
overlap its lower edge, so at ARTWORK the pane was painting over them. -->
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentTexture">
|
||||
<Size><AbsDimension x="316" y="351"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- Rotate buttons (ref l.183-206): 35×35 at the WINDOW's TOPLEFT (21,-75), left
|
||||
then right chained off its TOPRIGHT. Both edges registered, so one tap fires
|
||||
OnClick twice and holding hands the spin to the pane's OnUpdate — the ref's
|
||||
own tap-and-hold (decision 0638 §3). -->
|
||||
<Button name="DressUpModelFrameRotateLeftButton">
|
||||
<Size><AbsDimension x="35" y="35"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="DressUpFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="21" y="-75"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-RotationLeft-Button-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-RotationLeft-Button-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\ButtonHilight-Round" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
|
||||
<OnClick>BenillaDressUpModel_RotateLeft(getglobal("DressUpModelFrame"))</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="DressUpModelFrameRotateRightButton">
|
||||
<Size><AbsDimension x="35" y="35"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="DressUpModelFrameRotateLeftButton" relativePoint="TOPRIGHT"/></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-RotationRight-Button-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-RotationRight-Button-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\ButtonHilight-Round" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
|
||||
<OnClick>BenillaDressUpModel_RotateRight(getglobal("DressUpModelFrame"))</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
BenillaDressUpModel_OnLoad(self)
|
||||
BenillaSetBoothTexture(getglobal(self:GetName() .. "Texture"), "dressup")
|
||||
</OnLoad>
|
||||
<OnUpdate>BenillaDressUpModel_OnUpdate(self, arg1)</OnUpdate>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>SetDressUpBackground()</OnLoad>
|
||||
<OnShow>
|
||||
SetDressUpBackground()
|
||||
SetPortraitTexture(DressUpFramePortrait, "player")
|
||||
PlaySound("igCharacterInfoOpen")
|
||||
</OnShow>
|
||||
<!-- Closing empties the booth (dressup.rs's `Close` intent): the ref's widget keeps its
|
||||
state while hidden, but its next DressUpItem re-issues SetUnit("player") *because*
|
||||
the frame wasn't visible, so that state is never observable. -->
|
||||
<OnHide>
|
||||
BenillaDressUpModel_Close()
|
||||
PlaySound("igCharacterInfoClose")
|
||||
</OnHide>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
@@ -1,567 +0,0 @@
|
||||
<Ui>
|
||||
<!--
|
||||
The GameTooltip (decision 0274): a real <GameTooltip> engine widget — the line stack,
|
||||
SetOwner anchor law, auto-size + right-flush, IsOwned, FadeOut, and the OnTooltipCleared/
|
||||
OnTooltipAddMoney script protocol all live engine-side (benilla-ui script/tooltip.rs); the
|
||||
line pairs are engine-created named regions (GameTooltipTextLeftN/RightN — real globals,
|
||||
like the template pairs reference Lua addresses by name). This file declares the two
|
||||
TEMPLATES and their instances: the plate (backdrop art + colors QUOTED from
|
||||
ref-GameTooltip.xml/.lua, line cites inline), the money coin trio, and the Lua that is
|
||||
genuinely FrameXML's in the real client (GameTooltip_SetDefaultAnchor,
|
||||
GameTooltip_AddNewbieTip, SetTooltipMoney) — plus the P0 item-stat renderer, which
|
||||
moves engine-side in 0274 P1 with the full template feed.
|
||||
|
||||
The frame's global name is the real one — GameTooltip — so transcribed reference code
|
||||
(MinimapCluster's zone hover, UnitFrame's TextLeft1 recolor) works verbatim.
|
||||
|
||||
The reference splits this across two files — Interface\FrameXML\GameTooltipTemplate.xml
|
||||
holds the virtual GameTooltipTemplate (l.3-627) and ShoppingTooltipTemplate (l.628-1217);
|
||||
GameTooltip.xml <Include>s it and declares only the instances. Both templates live here
|
||||
instead, ABOVE their instances, because that is the whole of their reach: the tooltip file
|
||||
already loads before every window that hovers, and keeping them in one file means the
|
||||
template is registered by the time the instances (and, later, ItemRef.xml's) expand it
|
||||
without a second manifest entry to keep in order. Same registry, same splice, one file.
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- Tooltip default colours (VERIFIED, ref-GameTooltip.lua l.3-4). The border is white, the
|
||||
-- background the dark blue tint; applied to the Backdrop in OnLoad exactly as the real
|
||||
-- GameTooltip_OnLoad does (ref-GameTooltip.lua l.79-82).
|
||||
TOOLTIP_DEFAULT_COLOR = { r = 1, g = 1, b = 1 }
|
||||
TOOLTIP_DEFAULT_BACKGROUND_COLOR = { r = 0.09, g = 0.09, b = 0.19 }
|
||||
-- GameTooltip_SetDefaultAnchor's position math (ref-GameTooltip.lua l.6,73-77): the
|
||||
-- bottom-right screen corner, -CONTAINER_OFFSET_X - 13 in x, CONTAINER_OFFSET_Y in y.
|
||||
DEFAULT_TOOLTIP_POSITION = -13
|
||||
-- The shopping-compare pair count (ref GameTooltip.lua l.7; PaperDollFrame's listener
|
||||
-- bounds arg2 by it).
|
||||
NUM_SHOPPING_TOOLTIPS = 2
|
||||
|
||||
-- "Show Detailed Tooltips" — ref UIOptionsFrame.lua l.100-101, inside UIOptionsFrame_Init:
|
||||
-- SHOW_NEWBIE_TIPS = "1"; RegisterForSave("SHOW_NEWBIE_TIPS");
|
||||
-- It is a FrameXML *uvar*, not a CVar, and 1.12 ships it ON — so the detailed second line
|
||||
-- IS the default reference tooltip, not an opt-in. (An earlier note in this corpus had this
|
||||
-- backwards; decision 0661.) The options window's Interface page drives it now
|
||||
-- (decision 1136), and the assignment below is the ONLY statement of the default: the saved
|
||||
-- chunk executes over it at load (1128) and the row captures it as its Defaults value at
|
||||
-- OnLoad — which runs first — so "1" is written down in exactly one place, here.
|
||||
SHOW_NEWBIE_TIPS = "1"
|
||||
RegisterForSave("SHOW_NEWBIE_TIPS")
|
||||
|
||||
-- The bracketed name out of an item hyperlink "|cff…|Hitem:117|h[Tough Jerky]|h|r"; if the
|
||||
-- string has no bracket it is already a plain name (the merchant/loot rows hand plain names).
|
||||
function BenillaTooltip_ItemName(link)
|
||||
if not link then return nil end
|
||||
local _, _, name = strfind(link, "%[(.-)%]")
|
||||
return name or link
|
||||
end
|
||||
|
||||
-- The shared quality→colour table, `ITEM_QUALITY_COLORS` — Fonts.xml's, matching
|
||||
-- `ui_items.rs`'s `quality_color` and the client's `GetItemQualityColor`. Unknown/nil
|
||||
-- quality falls back to common white. All FrameXML files load before any hover fires, so
|
||||
-- the table is defined by the time this runs regardless of file order.
|
||||
--
|
||||
-- This read `BENILLA_LOOT_QUALITY_COLORS` until 1751 took the loot window to the player's
|
||||
-- chain. That name was only ever an alias our own `LootFrame.xml` set to this same table
|
||||
-- ("the local name kept for this file's existing readers"), and the stock file does not
|
||||
-- set it — so the alias went from harmless indirection to a nil the moment its definer
|
||||
-- stopped shipping. The `t and` guard would have made that silent: every tooltip would
|
||||
-- have quietly gone white.
|
||||
function BenillaTooltip_QualityColor(quality)
|
||||
local t = ITEM_QUALITY_COLORS
|
||||
local c = (t and (t[quality] or t[1])) or { r = 1, g = 1, b = 1 }
|
||||
return c.r, c.g, c.b
|
||||
end
|
||||
|
||||
-- Copper → "Ng Ms Kc" text for a price/value line (only the non-zero high coins, always the
|
||||
-- copper). math.floor if present, else a truncation fallback.
|
||||
function BenillaTooltip_MoneyString(copper)
|
||||
copper = copper or 0
|
||||
local mfloor = (math and math.floor) or function(x) return x - (x % 1) end
|
||||
local g = mfloor(copper / 10000)
|
||||
local s = mfloor((copper - g * 10000) / 100)
|
||||
local c = copper - g * 10000 - s * 100
|
||||
local out = ""
|
||||
if g > 0 then out = g .. "g " end
|
||||
if s > 0 or g > 0 then out = out .. s .. "s " end
|
||||
return out .. c .. "c"
|
||||
end
|
||||
|
||||
-- The default anchor (ref-GameTooltip.lua l.73-77, quoted): owner with no auto-anchor,
|
||||
-- then the bottom-right screen corner, above the bag row - CONTAINER_OFFSET_X/Y are the
|
||||
-- manage pass's live values (UIParent.xml). The engine's ANCHOR_NONE drops the previous
|
||||
-- owner anchor (tooltip.rs SetOwner), so no ClearAllPoints - the ref has none either.
|
||||
function GameTooltip_SetDefaultAnchor(tooltip, parent)
|
||||
tooltip:SetOwner(parent, "ANCHOR_NONE")
|
||||
-- Read the two managed globals FRESH, and fall back AT USE rather than assigning a
|
||||
-- copy: this file used to open with `CONTAINER_OFFSET_X = CONTAINER_OFFSET_X or 0`,
|
||||
-- which is a second written statement of numbers UIParent.xml owns. Harmless in the
|
||||
-- shipped load order (UIParent.xml is manifest 42, this file later) but it is the same
|
||||
-- shape as the shadow pair that froze the bag stack at the no-bars corner, so the law
|
||||
-- of 1499 forbids it outright: nothing outside UIParent.xml assigns a managed offset.
|
||||
-- The `or` defaults below cover the tooltip-only harnesses that load no UIParent.
|
||||
local offsetX = CONTAINER_OFFSET_X or 0
|
||||
local offsetY = CONTAINER_OFFSET_Y or 70
|
||||
tooltip:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMRIGHT",
|
||||
-offsetX + DEFAULT_TOOLTIP_POSITION, offsetY)
|
||||
tooltip.default = 1
|
||||
end
|
||||
|
||||
-- The two-line UI-element tooltip (spec: ref-GameTooltip.lua l.107-123): a white label,
|
||||
-- then the long explanation wrapped underneath in NORMAL_FONT_COLOR — the whole reason a
|
||||
-- 1.12 hover over the ping meter or a micro button is a paragraph and not four words.
|
||||
-- With detailed tips on it seats at the DEFAULT corner, not beside the owner; with them off
|
||||
-- it collapses to the bare label at the owner's right. `noNormalText` says "the explanation
|
||||
-- is the whole tooltip" (the XP bar) — with tips off such a hover shows nothing at all.
|
||||
--
|
||||
-- Signature note: the ref reads the firing frame from the `this` global; the corpus
|
||||
-- convention is to keep the ref NAME and take that frame as a leading parameter
|
||||
-- (GameTooltip_OnHide(self) — same shift; GameTooltip_ClearMoney went back to the reference's no-argument form in 1937). Reference code
|
||||
-- pasted in verbatim must therefore have the owner threaded in as arg 1.
|
||||
-- **The reference's exact signature, because a signature IS api (1199).** Ours used to take
|
||||
-- the owner as arg 1; the reference takes none and reads `this`. Outfitter calls it the
|
||||
-- reference's way (`OutfitterMinimapButton:OnEnter` -> `GameTooltip_AddNewbieTip(text, ...)`),
|
||||
-- so its TEXT landed in our owner slot and `SetOwner` raised
|
||||
-- "bad argument #2: error converting Lua string to table". Found by the use-probe, not by
|
||||
-- reading: it only fires when something hovers.
|
||||
--
|
||||
-- Our own callers mostly hold the owner in a local that is NOT the firing frame (`bar`,
|
||||
-- `button`, `frame`), so they use the explicit-owner variant below rather than `this`.
|
||||
function GameTooltip_AddNewbieTip(normalText, r, g, b, newbieText, noNormalText)
|
||||
BenillaTooltip_AddNewbieTipFor(this, normalText, r, g, b, newbieText, noNormalText)
|
||||
end
|
||||
|
||||
-- The explicit-owner form our FrameXML uses, and where the behaviour above actually lives:
|
||||
-- the owner is threaded in rather than read off `this`. Kept under a Benilla name so it can
|
||||
-- never be mistaken for a reference global an addon might call or replace.
|
||||
function BenillaTooltip_AddNewbieTipFor(owner, normalText, r, g, b, newbieText, noNormalText)
|
||||
-- Detailed tips OFF is the short shape: the label alone, beside whatever it labels.
|
||||
-- A caller that says its label is worthless on its own then has nothing left to show.
|
||||
if SHOW_NEWBIE_TIPS ~= "1" then
|
||||
if not noNormalText then
|
||||
GameTooltip:SetOwner(owner, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetText(normalText, r, g, b)
|
||||
end
|
||||
return
|
||||
end
|
||||
-- Detailed tips ON: a paragraph, and paragraphs go to the screen corner instead of
|
||||
-- crowding the thing they explain.
|
||||
GameTooltip_SetDefaultAnchor(GameTooltip, owner)
|
||||
if normalText then
|
||||
-- Label in the caller's colour; the explanation under it, wrapped, always in
|
||||
-- NORMAL_FONT_COLOR — the two lines are deliberately not the same colour.
|
||||
GameTooltip:SetText(normalText, r, g, b)
|
||||
GameTooltip:AddLine(newbieText, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
|
||||
else
|
||||
-- No label, so the explanation IS the tooltip: it takes the caller's colour, plus
|
||||
-- SetText's alpha and wrap flags (args 5 and 6) — the wrap the second line above
|
||||
-- would otherwise have carried.
|
||||
GameTooltip:SetText(newbieText, r, g, b, 1, 1)
|
||||
end
|
||||
GameTooltip:Show()
|
||||
end
|
||||
|
||||
-- ref GameTooltip_OnHide (ref-GameTooltip.lua l.100-104, quoted): the plate colours
|
||||
-- reset for the next owner and the default-corner flag drops. The ref's OnHide also
|
||||
-- hides ShoppingTooltip1/2 - that pair lands with the compare-tooltip slice (0274 P4).
|
||||
--
|
||||
-- `self or this`: the corpus calls this the REFERENCE way, with no argument at all
|
||||
-- (TipBuddy.xml l.1412, atsw.xml l.3420, and TipBuddy.lua l.2520 which REPLACES the global
|
||||
-- and calls the saved original bare). The signature note above explains the corpus-wide
|
||||
-- shift to a leading parameter; nothing lets us edit an addon, and `this` is exactly what
|
||||
-- the reference reads here, so the two spellings mean the same thing.
|
||||
function GameTooltip_OnHide(self)
|
||||
self = self or this
|
||||
self:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
|
||||
self:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
|
||||
self.default = nil
|
||||
end
|
||||
|
||||
-- The embedded money row — the ref's SetTooltipMoney (ref-GameTooltip.lua l.84-92) over our
|
||||
-- shared coin kit (BenillaMoney_Set, MerchantFrame.xml): a blank line, the coin trio seated
|
||||
-- at the money line's LEFT +4, and the money's width as the tooltip's minimum width. The
|
||||
-- engine fires OnTooltipAddMoney with the computed price (wow-re ui/scratch/tooltip-money.md
|
||||
-- 0x52b650@0x52e376: the engine computes, FrameXML renders) and this is its handler; Lua
|
||||
-- call sites (repair cost) call it directly, exactly like the ref's ContainerFrame does.
|
||||
-- ref GameTooltip.lua:84-92, verbatim, on the reference's own widget (1937). The money row
|
||||
-- is the $parentMoneyFrame SmallMoneyFrameTemplate instance below, not a coin trio.
|
||||
function SetTooltipMoney(frame, money)
|
||||
frame:AddLine(" ", 1.0, 1.0, 1.0)
|
||||
local numLines = frame:NumLines()
|
||||
local moneyFrame = getglobal(frame:GetName() .. "MoneyFrame")
|
||||
moneyFrame:SetPoint("LEFT", frame:GetName() .. "TextLeft" .. numLines, "LEFT", 4, 0)
|
||||
moneyFrame:Show()
|
||||
MoneyFrame_Update(moneyFrame:GetName(), money)
|
||||
frame:SetMinimumWidth(moneyFrame:GetWidth())
|
||||
end
|
||||
|
||||
-- The money row clears whenever the content does (the ref wires GameTooltip_ClearMoney to
|
||||
-- OnTooltipCleared; the engine fires it on ClearLines/SetOwner/Hide). BenillaMoney_Clear
|
||||
-- ships in MerchantFrame.xml (the money kit's home) — guard for engine tests that load
|
||||
-- this file alone; in the app every file is loaded.
|
||||
-- ref GameTooltip.lua:94-99, verbatim: reads `this`, takes no argument. The one XML
|
||||
-- caller (OnTooltipCleared) is a handler, so `this` IS the tooltip.
|
||||
function GameTooltip_ClearMoney()
|
||||
local moneyFrame = getglobal(this:GetName() .. "MoneyFrame")
|
||||
if moneyFrame then
|
||||
moneyFrame:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- The item content channels (SetItemById/SetBagItem/SetMerchantItem/SetBuybackItem) are
|
||||
-- ENGINE methods on the GameTooltip widget (benilla-ui script/tooltip_item.rs, decision
|
||||
-- 0274 P1): one shared renderer — the real client's own shape (0x52b650 behind 8 of the
|
||||
-- 9 Set*Item bindings, wow-re ui/scratch/tooltip-money.md; decision 0126's one-renderer
|
||||
-- law) — over the full item-template store, with the red failed-requirement law and the
|
||||
-- engine-fired sell-price money row (merchant open + real instance + repair off).
|
||||
|
||||
-- The reaction color table (ref ReputationFrame.lua l.3-12, quoted) — the unit-name
|
||||
-- recolor's palette; guarded so a later reputation window's own definition wins.
|
||||
FACTION_BAR_COLORS = FACTION_BAR_COLORS or {
|
||||
[1] = { r = 0.8, g = 0.3, b = 0.22 },
|
||||
[2] = { r = 0.8, g = 0.3, b = 0.22 },
|
||||
[3] = { r = 0.75, g = 0.27, b = 0 },
|
||||
[4] = { r = 0.9, g = 0.7, b = 0 },
|
||||
[5] = { r = 0, g = 0.6, b = 0.1 },
|
||||
[6] = { r = 0, g = 0.6, b = 0.1 },
|
||||
[7] = { r = 0, g = 0.6, b = 0.1 },
|
||||
[8] = { r = 0, g = 0.6, b = 0.1 },
|
||||
}
|
||||
|
||||
-- GameTooltip_UnitColor (ref-GameTooltip.lua l.8-71): creatures color by reaction;
|
||||
-- players read white (the ref's own shipping state — its hostile/PvP player branches
|
||||
-- are the FACTION_BAR_COLORS reds/greens, kept for when PvP states stream). The engine
|
||||
-- prints the name gold and fires UPDATE_MOUSEOVER_UNIT; this recolors line 1 (the
|
||||
-- byte-verified split — the name COLOR is FrameXML's, decision 0276).
|
||||
function GameTooltip_UnitColor(unit)
|
||||
local reaction = UnitReaction(unit, "player")
|
||||
if reaction and FACTION_BAR_COLORS[reaction] then
|
||||
local c = FACTION_BAR_COLORS[reaction]
|
||||
return c.r, c.g, c.b
|
||||
end
|
||||
return 1.0, 1.0, 1.0
|
||||
end
|
||||
|
||||
-- The health bar's own fill colour (spec: ref-HealthBar.lua, the whole file).
|
||||
--
|
||||
-- Two colourings in one handler. `smooth` asks for the health RAMP — green at the top of
|
||||
-- the range, red at the bottom, and between them a two-segment fade that pins one channel
|
||||
-- at full and slides the other, so the halfway mark reads solid yellow. Without it the bar
|
||||
-- is flat green whatever it reads. Blue is 0 either way, which is what keeps the fade
|
||||
-- inside the red-yellow-green wedge and never lets it wash out.
|
||||
--
|
||||
-- **Nothing passes `smooth` — not us and not the reference.** Verified: our only caller is
|
||||
-- the status bar's `<OnValueChanged>` below (one argument), and 1.12's own two callers are
|
||||
-- ref-GameTooltipTemplate.xml l.593 and ref-UnitFrame.lua l.181, both of which forward
|
||||
-- `value` alone. So the ramp has never coloured a bar in this game; it is kept because it
|
||||
-- is the contract an addon replacing a unit frame may still call with two arguments.
|
||||
--
|
||||
-- A value outside the bar's own range leaves the colour untouched rather than being clamped
|
||||
-- to an end of the ramp — the reference's own guard, kept as the reference has it.
|
||||
function HealthBar_OnValueChanged(value, smooth)
|
||||
if not value then return end
|
||||
local bar = this
|
||||
local min, max = bar:GetMinMaxValues()
|
||||
if value < min or value > max then return end
|
||||
local r, g = 0.0, 1.0
|
||||
if smooth then
|
||||
local filled = 0
|
||||
if max > min then filled = (value - min) / (max - min) end
|
||||
if filled > 0.5 then
|
||||
r, g = (1.0 - filled) * 2, 1.0
|
||||
else
|
||||
r, g = 1.0, filled * 2
|
||||
end
|
||||
end
|
||||
bar:SetStatusBarColor(r, g, 0.0)
|
||||
end
|
||||
|
||||
-- `self or this` for the same reason as GameTooltip_OnHide: an addon inheriting
|
||||
-- GameTooltipTemplate re-declares <OnLoad> to add its own line and opens it with the
|
||||
-- reference's bare `GameTooltip_OnLoad();` (TipBuddy.xml l.1402, atsw.xml l.3417) — a nil
|
||||
-- self there is a load ERROR, and the plate it was about to tint never gets its colours.
|
||||
-- The body is the ref's two lines and NOTHING else. It used to end with a `self:Hide()` we
|
||||
-- had added, which the reference (l.79-82, the whole function) does not have — inert for
|
||||
-- our four tooltips, since every one of them carries `hidden="true"` and the loader's
|
||||
-- attribute pass runs Hide() long before OnLoad fires, but NOT inert for an addon: two of
|
||||
-- the 27 declare `hidden="false"` on purpose (PowerAuras.xml l.20, SimpleActionSets.xml
|
||||
-- l.349) and never call Show(), so the extra line hid a tooltip the real client leaves up.
|
||||
-- A helper that quietly does more than the reference's is exactly the kind of thing that
|
||||
-- only shows up when someone else's code calls it.
|
||||
function GameTooltip_OnLoad(self)
|
||||
self = self or this
|
||||
-- The plate colours (ref GameTooltip_OnLoad, ref-GameTooltip.lua l.79-82).
|
||||
self:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
|
||||
self:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<!--
|
||||
GameTooltipTemplate (ref-GameTooltipTemplate.xml l.3-627) — the shared tooltip SHAPE, and
|
||||
the single most-wanted template in the addon corpus: 27 addons name it in an XML
|
||||
`inherits=` (TipBuddy, AtlasLoot, CT_BarMod, MikScrollingBattleText, TheoryCraft, …) and
|
||||
two more reach it through CreateFrame("GameTooltip", name, nil, "GameTooltipTemplate").
|
||||
Every one of those declarations is a <GameTooltip> element, so the template's job is to
|
||||
hand them the PLATE and the STATUS BAR; the line pairs they then read
|
||||
(`getglobal(tip:GetName().."TextLeft1")`) are engine-created per instance and can never
|
||||
carry the template's name.
|
||||
|
||||
What is here is what the reference's template carries and all our instances shared, moved
|
||||
verbatim from the GameTooltip instance below: the backdrop plate, the Thicken deviation
|
||||
(its rationale note travels WITH it — this is the shared plate, not one window's), the
|
||||
$parentStatusBar, and the OnLoad/OnHide pair (ref l.611-616).
|
||||
|
||||
What is deliberately NOT here, and stays on the GameTooltip instance:
|
||||
- The money row and the OnTooltipAddMoney/OnTooltipCleared pair. Since 1937 the row IS the
|
||||
reference's widget (a $parentMoneyFrame SmallMoneyFrameTemplate), but it still lives on
|
||||
the GameTooltip INSTANCE, where the reference's template carries it (l.598-608) so that
|
||||
ItemRefTooltip and every addon tooltip get the coin line too. That hoist is the remaining
|
||||
divergence here. It used to wait for the director; the 2026-09-03 ruling (faithful wins)
|
||||
resolves it, and it is its own unit because 27 corpus addons inherit this template.
|
||||
- OnTooltipSetDefaultAnchor (ref l.617-619). The engine fires it by name on `GameTooltip`
|
||||
alone (script/tooltip_unit.rs looks the frame up), so on any other instance it is dead
|
||||
weight.
|
||||
`clampedToScreen="true"` is the reference's own attribute and is a no-op for us: decision
|
||||
0352 made the flag default-ON for every GameTooltip-kind frame, because the reference plate
|
||||
observably clamps though its XML never sets it. Kept because it is what the reference says.
|
||||
-->
|
||||
<GameTooltip name="GameTooltipTemplate" frameStrata="TOOLTIP" clampedToScreen="true" hidden="true" virtual="true">
|
||||
<!-- The tooltip plate (ref-GameTooltipTemplate.xml l.4-14 / ref-GameTooltip.xml l.33-43):
|
||||
UI-Tooltip-Background tiled, UI-Tooltip-Border edge, EdgeSize 16, TileSize 16, insets
|
||||
5/5/5/5. Colours are set in OnLoad (SetBackdropColor/BorderColor) — no <Color> here, so
|
||||
the plate starts ctor-white and OnLoad tints it. -->
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
|
||||
<EdgeSize><AbsValue val="16"/></EdgeSize>
|
||||
<TileSize><AbsValue val="16"/></TileSize>
|
||||
<BackgroundInsets><AbsInset left="5" right="5" top="5" bottom="5"/></BackgroundInsets>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<!-- DELIBERATE deviation (director's call): the real plate is UI-Tooltip-Background's
|
||||
baked 187/255 alpha (~73% opaque — measured from the BLP; the tint can only
|
||||
lower it) and read too see-through here. This solid fill in the same navy,
|
||||
inset to the backdrop's 5px background insets, thickens the plate to ~84%
|
||||
against the world. The `a` is the knob; delete the region for the raw quote. -->
|
||||
<Texture name="$parentThicken">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="5" y="-5"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-5" y="5"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="0.09" g="0.09" b="0.19" a="0.4"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<!-- **The 30 pre-declared line pairs, exactly as ref-GameTooltipTemplate.xml l.17-627.**
|
||||
|
||||
They exist so `GameTooltipTextLeft1` is a REAL GLOBAL FROM LOAD, hidden, before any
|
||||
line has been added. Ours were engine-created purely on demand, so cold they were
|
||||
nil — and the corpus pattern that breaks on is a GUARD, not a read:
|
||||
`Participant/Resurrection.lua:294` and `CT_RaidAssist/CT_RADetectSpells.lua:47` both
|
||||
say `GameTooltipTextLeft1:IsVisible()` to ask whether the tooltip is showing
|
||||
anything. On the reference that is safe and answers false; for us it raised on the
|
||||
check itself. 7 corpus addons index the name (decision 1220).
|
||||
|
||||
The engine ADOPTS these (`script::tooltip::ensure_lines` — the same path
|
||||
ShoppingTooltipTemplate's ladder already uses): their faces ride, the engine owns
|
||||
justify, visibility and anchors, so no anchors are declared here. Faces match what
|
||||
`ensure_lines` picks for a created line — header for pair 1, GameTooltipText after —
|
||||
which is why this changes nothing on screen. `NumLines()` is `num_lines`, not the
|
||||
pool size, and `layout_tooltips` sizes from `num_lines`, so a declared-but-unfilled
|
||||
pair adds no height and no width. -->
|
||||
<FontString name="$parentTextLeft1" inherits="GameTooltipHeaderText" hidden="true"/>
|
||||
<FontString name="$parentTextRight1" inherits="GameTooltipHeaderText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft2" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight2" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft3" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight3" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft4" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight4" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft5" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight5" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft6" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight6" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft7" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight7" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft8" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight8" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft9" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight9" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft10" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight10" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft11" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight11" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft12" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight12" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft13" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight13" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft14" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight14" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft15" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight15" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft16" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight16" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft17" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight17" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft18" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight18" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft19" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight19" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft20" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight20" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft21" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight21" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft22" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight22" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft23" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight23" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft24" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight24" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft25" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight25" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft26" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight26" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft27" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight27" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft28" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight28" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft29" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight29" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextLeft30" inherits="GameTooltipText" hidden="true"/>
|
||||
<FontString name="$parentTextRight30" inherits="GameTooltipText" hidden="true"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- The mouseover health bar (ref-GameTooltipTemplate.xml l.575-597, quoted): 8px
|
||||
tall, hanging below the plate, UI-TargetingFrame-BarFill, colored by
|
||||
HealthBar_OnValueChanged. The ENGINE drives min/max/value from the live unit
|
||||
snapshot (the byte law's HEALTH watcher — SetUnit + every set_unit push), finding
|
||||
it by the `<name>StatusBar` global — so it has to be the TEMPLATE's, not one
|
||||
instance's: TipBuddy replaces the whole unit tooltip and both `parent=` and
|
||||
`relativeTo=` a `TipBuddyTooltipStatusBar` it never declares
|
||||
(TipBuddy.xml l.2320, l.2591) — a name only this element can publish. -->
|
||||
<StatusBar name="$parentStatusBar" hidden="true">
|
||||
<Size><AbsDimension x="0" y="8"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-2" y="-1"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnValueChanged>HealthBar_OnValueChanged(arg1)</OnValueChanged>
|
||||
</Scripts>
|
||||
<BarTexture name="$parentTexture" file="Interface\TargetingFrame\UI-TargetingFrame-BarFill"/>
|
||||
</StatusBar>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<!-- ref-GameTooltipTemplate.xml l.611-616. An instance that needs more re-declares the
|
||||
handler and calls the same helper first (the reference's own shape, and ours:
|
||||
GameTooltip adds its event registration, ItemRefTooltip its padding) — the loader
|
||||
walks every <Scripts> block in splice order, so the instance's SetScript lands
|
||||
last and wins. -->
|
||||
<OnLoad>GameTooltip_OnLoad(self)</OnLoad>
|
||||
<OnHide>GameTooltip_OnHide(this)</OnHide>
|
||||
</Scripts>
|
||||
</GameTooltip>
|
||||
|
||||
<GameTooltip name="GameTooltip" inherits="GameTooltipTemplate" parent="UIParent" frameStrata="TOOLTIP" hidden="true">
|
||||
<!-- A default size so the plate resolves the first frame it is shown; the engine's
|
||||
auto-size pass takes over once the lines are measured (one frame's latency). NO
|
||||
load-time anchor (the ref instance carries none): every show path seats the plate
|
||||
first - SetOwner's anchor law for owned hovers, OnTooltipSetDefaultAnchor for world
|
||||
hovers - and a leftover static anchor is exactly what parked the world tooltip at
|
||||
screen center when the default-anchor script wasn't wired. -->
|
||||
<Size><AbsDimension x="120" y="32"/></Size>
|
||||
<Frames>
|
||||
<!-- The reference's own money row (GameTooltipTemplate.xml:598-607): ONE SmallMoneyFrameTemplate
|
||||
instance, hidden until SetTooltipMoney seats and shows it. Replaces our three hand-built
|
||||
$parentMoneyCoin1..3 — the System B shape 1937 retires from this window. -->
|
||||
<Frame name="$parentMoneyFrame" inherits="SmallMoneyFrameTemplate" hidden="true">
|
||||
<Scripts><OnLoad>SmallMoneyFrame_OnLoad(); MoneyFrame_SetType("STATIC");</OnLoad></Scripts>
|
||||
</Frame>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
GameTooltip_OnLoad(self)
|
||||
self:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
|
||||
</OnLoad>
|
||||
<!-- The engine renders the mouseover unit's lines gold and fires this; the recolor by
|
||||
reaction is FrameXML's half (ref-GameTooltip.xml l.12-16, quoted). -->
|
||||
<OnEvent>
|
||||
if event == "UPDATE_MOUSEOVER_UNIT" then
|
||||
getglobal(this:GetName() .. "TextLeft1"):SetTextColor(GameTooltip_UnitColor("mouseover"))
|
||||
end
|
||||
</OnEvent>
|
||||
<!-- The default corner for world hovers (ref-GameTooltipTemplate.xml l.617-619,
|
||||
quoted): the engine fires this before rendering a mouseover unit/object, and
|
||||
the handler seats the plate at the screen's bottom-right. THE wiring whose
|
||||
absence parked the world tooltip on the character. -->
|
||||
<OnTooltipSetDefaultAnchor>GameTooltip_SetDefaultAnchor(this, UIParent)</OnTooltipSetDefaultAnchor>
|
||||
<!-- ref GameTooltip.xml OnHide (l.18-22, quoted): the color reset + the compare pair
|
||||
drops with its anchor host. -->
|
||||
<OnHide>
|
||||
GameTooltip_OnHide(this)
|
||||
ShoppingTooltip1:Hide()
|
||||
ShoppingTooltip2:Hide()
|
||||
</OnHide>
|
||||
<!-- The engine-fired money protocol (ref-GameTooltipTemplate.xml l.620-625): the engine
|
||||
computes a price and fires OnTooltipAddMoney(arg1 = copper); OnTooltipCleared fires
|
||||
whenever the content clears (ClearLines/SetOwner/Hide) and drops the coin row. -->
|
||||
<OnTooltipAddMoney>SetTooltipMoney(this, arg1)</OnTooltipAddMoney>
|
||||
<OnTooltipCleared>GameTooltip_ClearMoney()</OnTooltipCleared>
|
||||
</Scripts>
|
||||
</GameTooltip>
|
||||
|
||||
<!--
|
||||
ShoppingTooltipTemplate (ref-GameTooltipTemplate.xml l.628-1217) — the shopping-compare
|
||||
pair's own template, a SEPARATE virtual from GameTooltipTemplate exactly as the reference
|
||||
has it (its instances, ref-GameTooltip.xml l.24-25, inherit this and not the main one).
|
||||
It repeats the plate rather than inheriting it because the reference's does, and because
|
||||
inheriting GameTooltipTemplate would hand the compare plates its OnHide, its status bar and
|
||||
(later) its money row — none of which the compare cut has.
|
||||
|
||||
The declared TextLeft/Right1-4 pairs are this template's own small-font ladder, faces
|
||||
quoted verbatim (including its odd GameFontNormal cells) — the engine ADOPTS declared pairs
|
||||
and clones the last pair's faces for lines it grows past them (tooltip.rs ensure_lines).
|
||||
Same plate as the main tooltip, including the Thicken deviation (its rationale is on
|
||||
GameTooltipTemplate above); no coin trio — the compare cut ends before money.
|
||||
|
||||
No addon in the corpus names this template (0 of 218, against GameTooltipTemplate's 27), so
|
||||
it earns its keep as OUR de-duplication, not as an addon surface: the two instances below
|
||||
were byte-identical apart from the name.
|
||||
-->
|
||||
<GameTooltip name="ShoppingTooltipTemplate" frameStrata="TOOLTIP" hidden="true" virtual="true">
|
||||
<Size><AbsDimension x="120" y="32"/></Size>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
|
||||
<EdgeSize><AbsValue val="16"/></EdgeSize>
|
||||
<TileSize><AbsValue val="16"/></TileSize>
|
||||
<BackgroundInsets><AbsInset left="5" right="5" top="5" bottom="5"/></BackgroundInsets>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentThicken">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="5" y="-5"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-5" y="5"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="0.09" g="0.09" b="0.19" a="0.4"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentTextLeft1" inherits="GameFontNormalSmall" hidden="true"/>
|
||||
<FontString name="$parentTextRight1" inherits="GameFontNormal" hidden="true"/>
|
||||
<FontString name="$parentTextLeft2" inherits="GameFontNormal" hidden="true"/>
|
||||
<FontString name="$parentTextRight2" inherits="GameFontHighlightSmall" hidden="true"/>
|
||||
<FontString name="$parentTextLeft3" inherits="GameFontHighlightSmall" hidden="true"/>
|
||||
<FontString name="$parentTextRight3" inherits="GameFontHighlightSmall" hidden="true"/>
|
||||
<FontString name="$parentTextLeft4" inherits="GameTooltipTextSmall" hidden="true"/>
|
||||
<FontString name="$parentTextRight4" inherits="GameFontHighlightSmall" hidden="true"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>GameTooltip_OnLoad(self)</OnLoad>
|
||||
</Scripts>
|
||||
</GameTooltip>
|
||||
|
||||
<!-- The shopping-compare pair (ref GameTooltip.xml l.24-25, quoted down to the one-line shape):
|
||||
two more GameTooltip widgets the engine seats on the paperdoll slot when
|
||||
SHOW_COMPARE_TOOLTIP fires (CharacterFrame.xml's listener). -->
|
||||
<GameTooltip name="ShoppingTooltip1" inherits="ShoppingTooltipTemplate" parent="UIParent" frameStrata="TOOLTIP" hidden="true"/>
|
||||
<GameTooltip name="ShoppingTooltip2" inherits="ShoppingTooltipTemplate" parent="UIParent" frameStrata="TOOLTIP" hidden="true"/>
|
||||
</Ui>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -185,12 +185,12 @@
|
||||
return button.label
|
||||
end
|
||||
|
||||
-- ref MainMenuBarMicroButton's OnEnter (l.13), verbatim bar the owner shift:
|
||||
-- BenillaTooltip_AddNewbieTipFor(this.tooltipText, 1.0, 1.0, 1.0, this.newbieText)
|
||||
-- ref MainMenuBarMicroButton's OnEnter (l.13), verbatim — the reference's helper reads
|
||||
-- the firing frame off `this`, which is `button` here (the XML hands it `self`):
|
||||
-- GameTooltip_AddNewbieTip(this.tooltipText, 1.0, 1.0, 1.0, this.newbieText);
|
||||
function BenillaMicroButton_OnEnter(button)
|
||||
if not GameTooltip_AddNewbieTip then return end -- engine tests load file subsets
|
||||
BenillaTooltip_AddNewbieTipFor(button, BenillaMicroButton_TooltipText(button), 1.0, 1.0, 1.0,
|
||||
button.newbieText)
|
||||
GameTooltip_AddNewbieTip(BenillaMicroButton_TooltipText(button), 1.0, 1.0, 1.0, button.newbieText)
|
||||
end
|
||||
|
||||
-- Is a panel up? `getglobal` because every panel this asks about loads AFTER this file —
|
||||
|
||||
@@ -216,6 +216,18 @@
|
||||
-- load — compares against the string, so on a fresh 1.12 install the row paints unchecked
|
||||
-- and the auto-watch is dead until the box is clicked; the string ships the advertised
|
||||
-- default as the real one. A recorded deviation (1804's rule), not a quiet one.
|
||||
-- "Show Detailed Tooltips" — ref UIOptionsFrame.lua l.100-101, inside UIOptionsFrame_Init.
|
||||
-- A FrameXML uvar, not a CVar, and 1.12 ships it ON: the explanatory second line IS the
|
||||
-- default reference tooltip, not an opt-in (0661). It lived in our GameTooltip.xml until
|
||||
-- the tooltip went stock (1968); the reference keeps it here, with the Interface page.
|
||||
SHOW_NEWBIE_TIPS = "1"
|
||||
RegisterForSave("SHOW_NEWBIE_TIPS")
|
||||
-- "Hide World PvP Objectives" — ref UIOptionsFrame.lua's `HIDE_OUTDOOR_WORLD_STATE = "0"`,
|
||||
-- the gate the stock WorldStateAlwaysUpFrame_Update opens the outdoor readout on
|
||||
-- (`== "0" or instanceType == "pvp"`). Here since that frame went stock (1972); a nil
|
||||
-- reads as hidden, which is how the tower counters would vanish outdoors.
|
||||
HIDE_OUTDOOR_WORLD_STATE = "0"
|
||||
RegisterForSave("HIDE_OUTDOOR_WORLD_STATE")
|
||||
QUEST_FADING_DISABLE = "0"
|
||||
RegisterForSave("QUEST_FADING_DISABLE")
|
||||
AUTO_QUEST_WATCH = "1"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,12 @@
|
||||
this:RegisterEvent("VARIABLES_LOADED")
|
||||
this:RegisterEvent("TRAINER_SHOW")
|
||||
this:RegisterEvent("TRAINER_CLOSED")
|
||||
this:RegisterEvent("AUCTION_HOUSE_SHOW")
|
||||
this:RegisterEvent("AUCTION_HOUSE_CLOSED")
|
||||
this:RegisterEvent("TRADE_SKILL_SHOW")
|
||||
this:RegisterEvent("TRADE_SKILL_CLOSE")
|
||||
this:RegisterEvent("CRAFT_SHOW")
|
||||
this:RegisterEvent("CRAFT_CLOSE")
|
||||
this:RegisterEvent("GUILD_INVITE_REQUEST")
|
||||
this:RegisterEvent("GUILD_INVITE_CANCEL")
|
||||
this:RegisterEvent("MIRROR_TIMER_START")
|
||||
@@ -335,17 +341,22 @@
|
||||
return loaded
|
||||
end
|
||||
|
||||
-- ref UIParent.lua:223, whose slot this is — immediately above `UIParent_OnEvent`, the
|
||||
-- same neighbour it has there. It moved here from our own InspectFrame.xml when 1832
|
||||
-- retired that file: the name belongs to UIParent in the reference, and the window it
|
||||
-- opens is now the stock LoadOnDemand `Blizzard_InspectUI` off the player's chain.
|
||||
--
|
||||
-- The reference's body is `InspectFrame_LoadUI()` and then a guarded `InspectFrame_Show`.
|
||||
-- We have no load-on-demand — benilla.toc loads that addon with everything else — so the
|
||||
-- LoadUI call has nothing to do and is left out rather than stubbed (1203). The guard is
|
||||
-- kept exactly as the reference writes it, and it is not decoration: it is what makes this
|
||||
-- safe in the ~25 per-window harnesses that load UIParent.xml without the inspect window.
|
||||
-- ref UIParent.lua l.170-172 and l.223-228, verbatim — the inspect window's loader and its
|
||||
-- opener, immediately above `UIParent_OnEvent` as there. They moved here from our own
|
||||
-- InspectFrame.xml when 1832 retired that file: the names belong to UIParent in the
|
||||
-- reference. The window is the stock `Blizzard_InspectUI`, a LoadOnDemand registry row
|
||||
-- off the player's chain (`ui_script::addons`, 1957) that `InspectFrame_LoadUI` loads on
|
||||
-- the first call — the reference's own timing since 1967; from 1832 until then benilla.toc
|
||||
-- listed the addon with everything else and the call was left out rather than stubbed
|
||||
-- (1203). The guard is the reference's own, and it is not decoration: a harness that loads
|
||||
-- UIParent.xml with no addon seated gets the reference's "failed to load" message from
|
||||
-- `UIParentLoadAddOn` above and then, through this guard, nothing else.
|
||||
function InspectFrame_LoadUI()
|
||||
UIParentLoadAddOn("Blizzard_InspectUI");
|
||||
end
|
||||
|
||||
function InspectUnit(unit)
|
||||
InspectFrame_LoadUI();
|
||||
if ( InspectFrame_Show ) then
|
||||
InspectFrame_Show(unit);
|
||||
end
|
||||
@@ -357,14 +368,19 @@
|
||||
-- addon, so deleting our window file would have taken the name with it.
|
||||
--
|
||||
-- The LEVEL GATE is the reference's, and it is real behaviour rather than a guard: below
|
||||
-- level 10 the key and the micro button do nothing at all. `TalentFrame_LoadUI` is left
|
||||
-- out (we load the addon eagerly, 1203 — build it or leave it out, never stub it); the
|
||||
-- `TalentFrame_Toggle` guard is kept verbatim, and it is what lets the ~25 harnesses that
|
||||
-- load UIParent.xml without the talent window do so safely.
|
||||
-- level 10 the key and the micro button do nothing at all — the addon is not even loaded.
|
||||
-- `TalentFrame_LoadUI` (ref l.185-187) loads the LoadOnDemand row the first time (1967;
|
||||
-- until then benilla.toc listed the addon and the call was left out, 1203). The
|
||||
-- `TalentFrame_Toggle` guard is verbatim too.
|
||||
function TalentFrame_LoadUI()
|
||||
UIParentLoadAddOn("Blizzard_TalentUI");
|
||||
end
|
||||
|
||||
function ToggleTalentFrame()
|
||||
if ( UnitLevel("player") < 10 ) then
|
||||
return;
|
||||
end
|
||||
TalentFrame_LoadUI();
|
||||
if ( TalentFrame_Toggle ) then
|
||||
TalentFrame_Toggle();
|
||||
end
|
||||
@@ -387,14 +403,80 @@
|
||||
UIParentLoadAddOn("Blizzard_TrainerUI")
|
||||
end
|
||||
|
||||
-- ref UIParent.lua l.154-156, verbatim — the auction house's loader, reached from the
|
||||
-- AUCTION_HOUSE_SHOW arm below; `Blizzard_AuctionUI` is a registry row off the chain (1971).
|
||||
function AuctionFrame_LoadUI()
|
||||
UIParentLoadAddOn("Blizzard_AuctionUI");
|
||||
end
|
||||
|
||||
-- ref UIParent.lua l.158-160 and l.216-221, verbatim — the battlefield minimap's loader
|
||||
-- and its toggle (the TOGGLEBATTLEFIELDMINIMAP binding); the stock WorldStateFrame.lua
|
||||
-- calls the loader when a battleground's world states arrive (1972). The addon is a
|
||||
-- registry row off the chain like the others; its pane is a `<Minimap>`-kind widget this
|
||||
-- engine draws nothing into yet.
|
||||
function BattlefieldMinimap_LoadUI()
|
||||
UIParentLoadAddOn("Blizzard_BattlefieldMinimap");
|
||||
end
|
||||
|
||||
function ToggleBattlefieldMinimap()
|
||||
BattlefieldMinimap_LoadUI();
|
||||
if ( BattlefieldMinimap_Toggle ) then
|
||||
BattlefieldMinimap_Toggle();
|
||||
end
|
||||
end
|
||||
|
||||
-- ref UIParent.lua l.166-168 and l.190-192, verbatim — the crafting windows' loaders,
|
||||
-- reached from the TRADE_SKILL_SHOW / CRAFT_SHOW arms below; `Blizzard_TradeSkillUI` and
|
||||
-- `Blizzard_CraftUI` are registry rows off the chain (1973).
|
||||
function CraftFrame_LoadUI()
|
||||
UIParentLoadAddOn("Blizzard_CraftUI");
|
||||
end
|
||||
|
||||
function TradeSkillFrame_LoadUI()
|
||||
UIParentLoadAddOn("Blizzard_TradeSkillUI");
|
||||
end
|
||||
|
||||
-- ref UIParent.lua's own `BuildColoredListString`, verbatim — the "Requires: X, Y" line
|
||||
-- both crafting addons build their tool/reagent lists with (a missing tool in red). It
|
||||
-- lived as a guarded global in our TradeSkillFrame.xml until 1973 retired that file.
|
||||
function BuildColoredListString(...)
|
||||
if ( arg.n == 0 ) then
|
||||
return nil;
|
||||
end
|
||||
|
||||
-- Takes input where odd items are the text and even items determine whether the arg should be colored or not
|
||||
local string;
|
||||
if ( arg[2] ) then
|
||||
string = arg[1];
|
||||
else
|
||||
string = RED_FONT_COLOR_CODE.. arg[1]..FONT_COLOR_CODE_CLOSE;
|
||||
end
|
||||
for i=3, arg.n, 2 do
|
||||
if ( arg[i+1] ) then
|
||||
-- If meets the condition
|
||||
string = string..", "..arg[i];
|
||||
else
|
||||
-- If doesn't meet the condition
|
||||
string = string..", "..RED_FONT_COLOR_CODE..arg[i]..FONT_COLOR_CODE_CLOSE;
|
||||
end
|
||||
end
|
||||
|
||||
return string;
|
||||
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.
|
||||
--
|
||||
-- `MacroFrame_LoadUI` is left out: the addon loads eagerly from benilla.toc, so there is
|
||||
-- nothing for it to do, and 1203 says leave it out rather than stub it. The
|
||||
-- `MacroFrame_LoadUI` (ref l.176-178) loads the LoadOnDemand row the first time (1967;
|
||||
-- until then benilla.toc listed the addon and the call was left out, 1203). The
|
||||
-- `MacroFrame_Show` guard is the reference's own.
|
||||
function MacroFrame_LoadUI()
|
||||
UIParentLoadAddOn("Blizzard_MacroUI");
|
||||
end
|
||||
|
||||
function ShowMacroFrame()
|
||||
MacroFrame_LoadUI();
|
||||
if ( MacroFrame_Show ) then
|
||||
MacroFrame_Show();
|
||||
end
|
||||
@@ -408,8 +490,9 @@
|
||||
-- `RAID_ROSTER_UPDATE` and again at `PLAYER_LOGIN` with a raid up (l.34, l.39), so its
|
||||
-- absence is a raise every time the roster moves. It is a FrameXML function, not an engine
|
||||
-- verb, and this file is our stand-in for `UIParent.lua` — the same relationship the three
|
||||
-- above have. `UIParentLoadAddOn` is just above, and it no-ops on an addon that is
|
||||
-- already loaded, which ours is: benilla.toc takes `Blizzard_RaidUI` eagerly (1874).
|
||||
-- above have. `UIParentLoadAddOn` is just above; the addon is a LoadOnDemand registry
|
||||
-- row (1957) that this loads on the first roster update — the reference's timing since
|
||||
-- 1967 (1874 had listed it in benilla.toc, so this was a no-op on a loaded addon).
|
||||
function RaidFrame_LoadUI()
|
||||
UIParentLoadAddOn("Blizzard_RaidUI");
|
||||
end
|
||||
@@ -448,6 +531,48 @@
|
||||
end
|
||||
return
|
||||
end
|
||||
-- The auction arms (ref UIParent.lua l.575-587), verbatim — the addon loads on the first
|
||||
-- auctioneer and its own `AuctionFrame_Show` reads the hello the app applied (1971).
|
||||
if ( event == "AUCTION_HOUSE_SHOW" ) then
|
||||
AuctionFrame_LoadUI();
|
||||
if ( AuctionFrame_Show ) then
|
||||
AuctionFrame_Show();
|
||||
end
|
||||
return;
|
||||
end
|
||||
if ( event == "AUCTION_HOUSE_CLOSED" ) then
|
||||
if ( AuctionFrame_Hide ) then
|
||||
AuctionFrame_Hide();
|
||||
end
|
||||
return;
|
||||
end
|
||||
-- The trade skill and craft arms (ref UIParent.lua l.605-632), verbatim (1973).
|
||||
if ( event == "TRADE_SKILL_SHOW" ) then
|
||||
TradeSkillFrame_LoadUI();
|
||||
if ( TradeSkillFrame_Show ) then
|
||||
TradeSkillFrame_Show();
|
||||
end
|
||||
return;
|
||||
end
|
||||
if ( event == "TRADE_SKILL_CLOSE" ) then
|
||||
if ( TradeSkillFrame_Hide ) then
|
||||
TradeSkillFrame_Hide();
|
||||
end
|
||||
return;
|
||||
end
|
||||
if ( event == "CRAFT_SHOW" ) then
|
||||
CraftFrame_LoadUI();
|
||||
if ( CraftFrame_Show ) then
|
||||
CraftFrame_Show();
|
||||
end
|
||||
return;
|
||||
end
|
||||
if ( event == "CRAFT_CLOSE" ) then
|
||||
if ( CraftFrame_Hide ) then
|
||||
CraftFrame_Hide();
|
||||
end
|
||||
return;
|
||||
end
|
||||
-- The guild invitation (ref UIParent.lua l.296-303, verbatim): the dialog rode a driver
|
||||
-- frame in our FriendsFrame.xml until that file became the reference's (1959).
|
||||
if ( event == "GUILD_INVITE_REQUEST" ) then
|
||||
|
||||
@@ -144,16 +144,9 @@
|
||||
-- Blizzard_MacroUI.lua:7). whileDead is the ref's and it earns its keep: writing a macro
|
||||
-- during a corpse run is a real thing people do.
|
||||
UIPanelWindows["MacroFrame"] = { area = "left", pushable = 5, whileDead = 1 }
|
||||
-- The crafting window (decision 0437 phase 2, TradeSkillFrame.xml): NOT an NPC session (it
|
||||
-- opens off your own cast, TradeSkillFrame.xml's own header comment) — the ref Lua's own
|
||||
-- registration line (Blizzard_TradeSkillUI.lua:13) is `{ area = "left", pushable = 3 }`,
|
||||
-- quoted verbatim.
|
||||
UIPanelWindows["BenillaTradeSkillFrame"] = { area = "left", pushable = 3 }
|
||||
-- The Enchanting window (decision 0437 phase 3, CraftFrame.xml): the ref Lua's own
|
||||
-- registration line (`Blizzard_CraftUI.lua:24`), quoted verbatim — `pushable = 4`, NOT `3`
|
||||
-- (grepped both ref files directly to confirm: `3` is TradeSkillFrame's own line-13 value,
|
||||
-- a different window/depth — CraftFrame.xml's own header comment has the same note).
|
||||
UIPanelWindows["BenillaCraftFrame"] = { area = "left", pushable = 4 }
|
||||
-- The trade skill and craft windows register THEMSELVES: `Blizzard_TradeSkillUI.lua:13`
|
||||
-- and `Blizzard_CraftUI.lua:24` write their own `UIPanelWindows` rows when the addon loads,
|
||||
-- which since 1973 is the reference's own file on the first TRADE_SKILL_SHOW / CRAFT_SHOW.
|
||||
-- The game menu (decision 0674, GameMenuFrame.xml) — the ref's own registration line
|
||||
-- verbatim (`UIPanelWindows["GameMenuFrame"] = { area = "center", pushable = 0,
|
||||
-- whileDead = 1 }`, UIParent.lua l.15), and the FIRST **center**-area row benilla ships.
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
<Ui>
|
||||
<!--
|
||||
The always-up world-state readout — report B190's second half: the alliance/horde progress
|
||||
line an outdoor-PvP zone or a battleground shows at the top of the screen. In Eastern
|
||||
Plaguelands that is the two tower counters ("Towers Controlled: 3"); in Warsong Gulch the
|
||||
flag captures; in Arathi Basin bases and resources.
|
||||
|
||||
The SPEC is the reference's two C bindings, byte-carved (wow-re
|
||||
system/ui/scratch/worldstate-ui-law.md, 0x4c5a40 / 0x4c5a70) and re-expressed here:
|
||||
|
||||
GetNumWorldStateUI() -> how many rows the current scope admits
|
||||
GetWorldStateUIInfo(i) -> TEN values, in this order:
|
||||
uiState, text, icon, dynamicIcon,
|
||||
tooltip, dynamicTooltip, extendedUI,
|
||||
extUIState1, extUIState2, extUIState3
|
||||
|
||||
Note what that return is NOT: there is no uiType and no hidden in 1.12 — those are a later
|
||||
expansion of the API, and reading this list as if they were there shifts every value by two.
|
||||
`text` arrives already macro-expanded (the host runs the reference's %<id>w expander);
|
||||
every other string is its DBC column verbatim, and never nil — an empty column is "".
|
||||
|
||||
Which rows exist at all is the host's (crates/benilla-app/src/world_state_ui.rs), including
|
||||
the gate that makes this frame look broken if you don't know about it: the world-PvP rows
|
||||
(Eastern Plaguelands, Silithus) are Type 1, which the reference admits only while the player
|
||||
has joined the zone-dependent DEFENSE chat channel. Leave LocalDefense and the readout goes
|
||||
away while the map icons stay.
|
||||
|
||||
THE GEOMETRY IS THE REFERENCE'S, and it is not what you would guess (wow-re
|
||||
worldstate-ui-law.md §12, carved 2026-08-25; decision 1604 folds it back). The first pass
|
||||
of this frame invented a snug 20x20 icon in a 22px row, and that was wrong in a way that
|
||||
showed: EVERY icon these rows name is a sprite authored into the UPPER-LEFT corner of a
|
||||
power-of-two canvas with the rest transparent — AllianceTower/HordeTower are a 16x16 sprite
|
||||
at (3,2) of a 32x32 file, UI-PVP-Alliance/Horde a ~40x40 crest in a 64x64 one. Squeeze that
|
||||
whole canvas into a snug box and the visible art floats up and to the left of the box,
|
||||
away from its label. NOTHING IS CROPPED to fix that — there is no <TexCoords> on any of
|
||||
these regions, and a texture with none samples the full 0..1 quad (CSimpleTexture's ctor
|
||||
0x76fc40 stamps it). The compensation is geometric, and these three numbers are one tuned
|
||||
system: the icon slot is OVERSIZED to 42x42 (a 32px file upscaled 1.3125x), hung SIX UNITS
|
||||
OFF the row's left edge, and the text is seated TEN UNITS ABOVE the slot's centreline. The
|
||||
upper-left ink then lands as a ~21x21 square whose centre sits just under the text's, which
|
||||
is what "icon next to its label" is supposed to look like.
|
||||
|
||||
THERE ARE TWO ICON SLOTS, fed by two different DBC columns, and shipped content never puts
|
||||
the same art in both:
|
||||
|
||||
Icon (+0x0c) -> the 42x42 slot below. AllianceTower, HordeTower,
|
||||
UI-PVP-Alliance, UI-PVP-Horde.
|
||||
DynamicIcon (+0x64) -> a SEPARATE 32x32 button off the row's right edge, shown only
|
||||
while uiState == 2. HordeFlag/AllianceFlag, Warsong Gulch only.
|
||||
|
||||
So the dynamic icon does NOT replace the static one — both are on screen together, which is
|
||||
the Warsong Gulch look: the faction shield with its capture count, and a flashing enemy
|
||||
flag to its right while a carrier is running. `uiState == 2` is the flag-taken value; the
|
||||
first pass swapped the art on `uiState ~= 0`, which fired on the ordinary state 1 and
|
||||
replaced a shield that should never have moved.
|
||||
|
||||
OUT, named: the CAPTUREPOINT extended UI — the sliding bar over
|
||||
Interface\WorldStateFrame\WorldState-CaptureBar that the one Eastern Plaguelands progress
|
||||
row drives. Its two states come through the binding already (extUIState1/2 carry the
|
||||
alliance/horde fractions), so it is a widget to add, not a seam to change. NOW KNOWN, and
|
||||
worth writing down before someone builds it: in the reference that row never reaches this
|
||||
frame at all — the update path branches on a non-empty extendedUI FIRST, and id 138 is the
|
||||
only always-up-class row with one, so EPL's real readout is two text rows here plus a
|
||||
separate bar anchored under the MinimapCluster. Until that bar exists we keep showing the
|
||||
row as its text line (decision 1590 §7's stand-in, unchanged) so the progress number is on
|
||||
screen; it costs a third line the reference doesn't draw, and it goes when the bar lands.
|
||||
Also out: the battleground scoreboard (the Type 2 rows), which the reference builds from the
|
||||
same DBC through a DIFFERENT handler and a different pair of bindings whose bail leg is not
|
||||
even the same shape — GetNumBattlefieldStats/GetBattlefieldStatInfo, a separate slice.
|
||||
-->
|
||||
<Script>
|
||||
<![CDATA[
|
||||
NUM_ALWAYS_UP_ROWS = 0;
|
||||
|
||||
-- The reference's own numbers (worldstate-ui-law.md §12). The row is an anchor node, not a
|
||||
-- box: its 42-wide icon slot hangs off its left edge and its text runs past its right, so
|
||||
-- none of these are "the readout's size" — they are the offsets that seat corner-authored
|
||||
-- art against a label. The pitch is the template height, because rows chain TOP to the
|
||||
-- previous row's BOTTOM with no gap.
|
||||
WORLD_STATE_ROW_HEIGHT = 24;
|
||||
-- The dynamic-icon flash: 0.5 s in, 0.5 s out, indefinitely.
|
||||
WORLD_STATE_FLASH_HALF = 0.5;
|
||||
|
||||
-- The flash overlay's ramp. The art (…Flash.blp) is DXT1 with no alpha channel — black IS
|
||||
-- the transparency — which is why it draws ADD; the pulse is the region's own alpha.
|
||||
function WorldStateDynamicIcon_OnUpdate(elapsed)
|
||||
local t = (this.flashTime or 0) + elapsed;
|
||||
local period = 2 * WORLD_STATE_FLASH_HALF;
|
||||
while ( t >= period ) do
|
||||
t = t - period;
|
||||
end
|
||||
this.flashTime = t;
|
||||
local level = t / WORLD_STATE_FLASH_HALF;
|
||||
if ( level > 1 ) then
|
||||
level = 2 - level;
|
||||
end
|
||||
getglobal(this:GetName() .. "Flash"):SetAlpha(level);
|
||||
end
|
||||
|
||||
-- Rebuild the readout from the host's list. Cheap enough to run whole on every fire: the
|
||||
-- list is at most a handful of rows, and the events are a zone's worth apart.
|
||||
function WorldStateAlwaysUpFrame_Update()
|
||||
local numUI = GetNumWorldStateUI();
|
||||
|
||||
-- Grow the row pool on demand and never shrink it, the way every pooled list here
|
||||
-- does: the tail from a busier zone is parked hidden rather than destroyed. Row 1
|
||||
-- hangs off the container; every later row chains to the one above it, so the pitch is
|
||||
-- the template's height and lives in exactly one place.
|
||||
if ( numUI > NUM_ALWAYS_UP_ROWS ) then
|
||||
for i = NUM_ALWAYS_UP_ROWS + 1, numUI do
|
||||
local row = CreateFrame("Frame", "WorldStateAlwaysUpFrame" .. i,
|
||||
WorldStateAlwaysUpFrame, "WorldStateAlwaysUpTemplate");
|
||||
if ( i == 1 ) then
|
||||
row:SetPoint("TOP", WorldStateAlwaysUpFrame, "TOP", -23, -20);
|
||||
else
|
||||
row:SetPoint("TOP", getglobal("WorldStateAlwaysUpFrame" .. (i - 1)),
|
||||
"BOTTOM", 0, 0);
|
||||
end
|
||||
end
|
||||
NUM_ALWAYS_UP_ROWS = numUI;
|
||||
end
|
||||
|
||||
for i = 1, NUM_ALWAYS_UP_ROWS do
|
||||
local row = getglobal("WorldStateAlwaysUpFrame" .. i);
|
||||
if ( i > numUI ) then
|
||||
row:Hide();
|
||||
else
|
||||
local uiState, text, icon, dynamicIcon, tooltip, dynamicTooltip = GetWorldStateUIInfo(i);
|
||||
local name = row:GetName();
|
||||
|
||||
-- The static slot takes its column verbatim. An EMPTY column is not a special
|
||||
-- case: SetTexture("") clears the art and draws nothing while the authored
|
||||
-- 42x42 rect — and so everything anchored to it — stays exactly where it was.
|
||||
-- The slot is reserved and blank, never collapsed; a row that slid its text
|
||||
-- left into the empty slot would ragged the readout's left edge, and the
|
||||
-- reference does no such thing.
|
||||
getglobal(name .. "Icon"):SetTexture(icon);
|
||||
getglobal(name .. "Text"):SetText(text);
|
||||
row.tooltip = tooltip;
|
||||
|
||||
-- The dynamic slot is a SEPARATE button off the row's right edge, and it is
|
||||
-- the flag-taken state alone that shows it.
|
||||
local dynamic = getglobal(name .. "DynamicIcon");
|
||||
if ( dynamicIcon ~= "" and uiState == 2 ) then
|
||||
getglobal(dynamic:GetName() .. "Texture"):SetTexture(dynamicIcon);
|
||||
getglobal(dynamic:GetName() .. "Flash"):SetTexture(dynamicIcon .. "Flash");
|
||||
dynamic.tooltip = dynamicTooltip;
|
||||
dynamic.flashTime = 0;
|
||||
dynamic:Show();
|
||||
else
|
||||
dynamic:Hide();
|
||||
end
|
||||
row:Show();
|
||||
end
|
||||
end
|
||||
|
||||
-- OURS, and non-visual: the reference's container is a bare 10x10 anchor node it never
|
||||
-- resizes, so its own bottom says nothing about how far the rows reach. We keep the
|
||||
-- authored size but grow the height to the rows' real extent, so "how much of the top
|
||||
-- of the screen is this using?" is a question the frame can answer — the dev cost pill
|
||||
-- asks it, and seats itself below (crates/benilla-app/src/perf/hud.rs). Rows anchor to
|
||||
-- the container's TOP, which is fixed, so nothing moves.
|
||||
if ( numUI > 0 ) then
|
||||
WorldStateAlwaysUpFrame:SetHeight(20 + numUI * WORLD_STATE_ROW_HEIGHT);
|
||||
WorldStateAlwaysUpFrame:Show();
|
||||
else
|
||||
WorldStateAlwaysUpFrame:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
function WorldStateAlwaysUpFrame_OnEvent()
|
||||
WorldStateAlwaysUpFrame_Update();
|
||||
end
|
||||
]]>
|
||||
</Script>
|
||||
|
||||
<!-- One row: the oversized icon slot, its line of text, and the dynamic-icon button that only
|
||||
Warsong Gulch ever lights. Grown on demand into WorldStateAlwaysUpFrame by the update
|
||||
above. Every offset here is the reference's — see the header for why they are not the
|
||||
snug numbers you would pick by eye. -->
|
||||
<Frame name="WorldStateAlwaysUpTemplate" virtual="true" enableMouse="true">
|
||||
<Size><AbsDimension x="45" y="24"/></Size>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="$parentIcon">
|
||||
<Size><AbsDimension x="42" y="42"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT">
|
||||
<Offset><AbsDimension x="-6" y="0"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<FontString name="$parentText" inherits="GameFontNormalSmall" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="$parentIcon" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="-12" y="10"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button name="$parentDynamicIcon" hidden="true">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT">
|
||||
<Offset><AbsDimension x="40" y="9"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="$parentTexture">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="$parentFlash" alphaMode="ADD">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnUpdate>
|
||||
WorldStateDynamicIcon_OnUpdate(arg1);
|
||||
</OnUpdate>
|
||||
<OnEnter>
|
||||
if ( this.tooltip and this.tooltip ~= "" ) then
|
||||
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT");
|
||||
GameTooltip:SetText(this.tooltip);
|
||||
GameTooltip:Show();
|
||||
end
|
||||
</OnEnter>
|
||||
<OnLeave>
|
||||
GameTooltip:Hide();
|
||||
</OnLeave>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnEnter>
|
||||
if ( this.tooltip and this.tooltip ~= "" ) then
|
||||
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT");
|
||||
GameTooltip:SetText(this.tooltip);
|
||||
GameTooltip:Show();
|
||||
end
|
||||
</OnEnter>
|
||||
<OnLeave>
|
||||
GameTooltip:Hide();
|
||||
</OnLeave>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
<!-- The container. Top-centre of the screen, which is where an outdoor-PvP zone's counters and
|
||||
a battleground's score belong: the minimap owns the top-right and the readout must not
|
||||
fight it. The reference declares it 10x10 and never touches it again — it is an anchor
|
||||
node, and rows overflow it freely (see the update for the one thing we do differently, and
|
||||
why). Hidden until a scope admits a row. -->
|
||||
<Frame name="WorldStateAlwaysUpFrame" parent="UIParent" toplevel="true" hidden="true">
|
||||
<Size><AbsDimension x="10" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="UIParent" relativePoint="TOP">
|
||||
<Offset><AbsDimension x="-5" y="-15"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
-- UPDATE_WORLD_STATES is the reference's own event for this list (0x20e). The
|
||||
-- entering-world fire is what paints the readout on a login that lands inside a
|
||||
-- scope the server has already sent states for.
|
||||
this:RegisterEvent("UPDATE_WORLD_STATES");
|
||||
this:RegisterEvent("PLAYER_ENTERING_WORLD");
|
||||
</OnLoad>
|
||||
<OnEvent>
|
||||
WorldStateAlwaysUpFrame_OnEvent();
|
||||
</OnEvent>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
@@ -166,13 +166,22 @@ Interface\FrameXML\MoneyInputFrame.xml
|
||||
# reference them. The StaticPopup engine and every dialog it used to carry are the reference's
|
||||
# own file now, far below at the reference's own slot after UIPanelTemplates (decision 1960).
|
||||
UiPanels.xml
|
||||
# The tooltip: a hover window the bag/merchant/loot/unit frames drive. Loaded before them so
|
||||
# `GameTooltip` + its verbs exist when their OnEnter handlers fire (runtime, so order
|
||||
# is not strictly required — but it keeps the dependency legible). It also declares the two
|
||||
# virtual templates (GameTooltipTemplate, ShoppingTooltipTemplate), which IS an order
|
||||
# requirement: ItemRef.xml below expands one, and so do the 27 corpus addons that name
|
||||
# GameTooltipTemplate in an XML inherits=.
|
||||
GameTooltip.xml
|
||||
# ══ THE TOOLTIP — the reference's own, off the player's chain (1751; decision 1968) ══
|
||||
# GameTooltip.xml sources HealthBar.lua and GameTooltip.lua and <Include>s GameTooltipTemplate.xml:
|
||||
# the two virtual templates (GameTooltipTemplate, ShoppingTooltipTemplate — thirty declared line
|
||||
# pairs each, the status bar, the money row), the GameTooltip / ShoppingTooltip1 / ShoppingTooltip2
|
||||
# instances and SmallTextTooltip. The widget itself is this engine's (0274); every verb the file
|
||||
# calls was already bound. Its slot is the reference's own: FrameXML.toc l.13, after MoneyFrame
|
||||
# (l.9-10, `SmallMoneyFrameTemplate` resolves at load) and before the panel kit. The order
|
||||
# requirement the old header named still holds: ItemRef.xml below expands GameTooltipTemplate,
|
||||
# and so do the 27 corpus addons that name it in an XML inherits=.
|
||||
#
|
||||
# What our transcription carried that the reference does not, and where each went (1968): the
|
||||
# `$parentThicken` plate fill (a look deviation, retired — the plate is the reference's art);
|
||||
# `SHOW_NEWBIE_TIPS`'s default, which is UIOptionsFrame.lua's and sits in OptionsFrame.xml's uvar
|
||||
# block with its family; `FACTION_BAR_COLORS`' fallback table — the reference's is
|
||||
# ReputationFrame.lua's, on the chain below; `NUM_SHOPPING_TOOLTIPS` — PaperDollFrame.lua's.
|
||||
Interface\FrameXML\GameTooltip.xml
|
||||
# ══ THE DROPDOWN KIT — the reference's own, off the player's chain (1751) ══
|
||||
# UIDropDownMenuTemplate + the DropDownList frames + the whole `UIDropDownMenu_*` API — before its
|
||||
# first customers (the unit popups below, the world map's pickers, the trainer filter). The stock
|
||||
@@ -559,11 +568,14 @@ Interface\FrameXML\CastingBarFrame.xml
|
||||
# 1.12 never reaches it — vmangos refuses to send that packet and substitutes a full START,
|
||||
# saying so in `Player::SendMirrorTimers` — and `ui_mirror.rs`'s Pause edge carries the note.
|
||||
Interface\FrameXML\MirrorTimer.xml
|
||||
# The always-up world-state readout (decision 1586, report B190): the tower/flag/base counters an
|
||||
# outdoor-PvP zone or a battleground shows at top-centre, driven by the UPDATE_WORLD_STATES event
|
||||
# `crate::world_state_ui` fires. Needs Fonts (GameFontNormalSmall) and GameTooltip; sits beside the
|
||||
# mirror timers, which share its top-centre lane.
|
||||
WorldStateFrame.xml
|
||||
# ══ THE WORLD-STATE FRAMES — the reference's own, off the player's chain (1751; decision 1972) ══
|
||||
# WorldStateFrame.xml + WorldStateFrame.lua: the always-up readout 1586 built (the tower/flag/base
|
||||
# counters at top-centre, `UPDATE_WORLD_STATES` from `crate::world_state_ui`), the capture bars,
|
||||
# and the battleground SCOREBOARD ours never had — `WorldStateScoreFrame`, over the score family
|
||||
# 1972 built (`MSG_PVP_LOG_DATA`, the eleven verbs, `UPDATE_BATTLEFIELD_SCORE`) and UIParent.xml's
|
||||
# `BattlefieldMinimap_LoadUI`. Needs Fonts, GameTooltip, the panel kit (its close button, tabs,
|
||||
# faux scroll frame) — all above; sits beside the mirror timers, which share its top-centre lane.
|
||||
Interface\FrameXML\WorldStateFrame.xml
|
||||
# ══ THE DURABILITY ALERT — the reference's own, off the player's chain (1751, window 4) ══
|
||||
# The "armor guy" under the minimap: `DurabilityFrame`, its eleven glyph textures, and
|
||||
# `DurabilityFrame_SetAlerts` / `_OnUpdate` with `INVENTORY_ALERT_STATUS_SLOTS` and
|
||||
@@ -752,42 +764,34 @@ Interface\FrameXML\SkillFrame.xml
|
||||
# `HonorFrameTemplates.xml` is `<Include>`d by `HonorFrame.xml` and inherited by the LoadOnDemand
|
||||
# `Blizzard_InspectUI`, which loads later still.
|
||||
Interface\FrameXML\HonorFrame.xml
|
||||
# ══ THE INSPECT WINDOW — the reference's own, off the player's chain (1751, window 24) ══
|
||||
# Another player's paper doll (decision 0631), and the FIRST LoadOnDemand ADDON we take off the
|
||||
# chain rather than a FrameXML file. Everything below it in this manifest that names a
|
||||
# `Blizzard_*` addon follows the shape set here.
|
||||
# ══ THE LoadOnDemand ADDONS — Inspect, Talent, Macro, Raid: NO ROW HERE (1751; 1967) ══
|
||||
# `Blizzard_InspectUI`, `Blizzard_TalentUI`, `Blizzard_MacroUI` and `Blizzard_RaidUI` are not
|
||||
# listed, exactly as the reference's own `FrameXML.toc` lists no addon: each is `LoadOnDemand: 1`
|
||||
# on the player's chain — inside patch.MPQ; the loose `Interface\AddOns\Blizzard_*\` folders on
|
||||
# disk hold one `.pub` decoy each, which is what kept these four windows on transcriptions for
|
||||
# months — and `ui_script::addons` seats every such addon as a registry row (1957) that
|
||||
# `UIParentLoadAddOn` loads the first time its opener runs. The openers are the reference's own,
|
||||
# in UIParent.xml, its home for them: `InspectUnit` → `InspectFrame_LoadUI`, `ToggleTalentFrame`
|
||||
# → `TalentFrame_LoadUI`, `ShowMacroFrame` → `MacroFrame_LoadUI`, and the stock RaidFrame.lua's
|
||||
# `RaidFrame_LoadUI` on the first roster update. 1832, 1833, 1848 and 1874 took the four off the
|
||||
# chain but listed them here with everything else, and left the LoadUI calls out rather than
|
||||
# stub them (1203); 1967 gives them the reference's load timing and the calls back.
|
||||
#
|
||||
# **The addon is inside patch.MPQ, not the loose folder.** `Interface\AddOns\Blizzard_InspectUI\`
|
||||
# on disk holds one `Blizzard_InspectUI.pub` decoy and nothing else; the real four files sit in
|
||||
# the archive, which the chain mounts. Believing the decoy is what kept this window (and Macro,
|
||||
# Talent, Raid, Trainer) on a transcription for months.
|
||||
#
|
||||
# No LoadOnDemand: we list the addon's files here and they load with everything else. The
|
||||
# reference reaches them through `InspectUnit` → `InspectFrame_LoadUI`; ours keeps `InspectUnit`
|
||||
# (in UIParent.xml, the reference's own home for that name) and drops the LoadUI call.
|
||||
#
|
||||
# The addon's own toc order, which is load order — the `.xml` files pull their `.lua` siblings
|
||||
# through `<Script file=.../>` relative to the addon directory, exactly as a FrameXML file does.
|
||||
# Its toc lists a fourth entry, `Localization.lua`, which is omitted here because it holds no code
|
||||
# at all: the whole file is the comment `-- This file is executed at the end of addon load`.
|
||||
#
|
||||
# Dependencies are unchanged from the file this replaces: Fonts/UiPanels/GameTooltip, plus
|
||||
# `CharacterFrameTabButtonTemplate` from UiPanels.xml, plus ItemButtonTemplate.xml far above
|
||||
# (`InspectPaperDollItemSlotButtonTemplate` inherits it, resolved at LOAD). And the one that
|
||||
# binds hardest is the entry directly above: the honor page inherits HonorFrame.xml's five row
|
||||
# templates — the reference has the identical edge, which is why its own toc has the same order.
|
||||
#
|
||||
# What we GAIN by swapping: `InspectGuildText` and `InspectTitleText`, two FontStrings our
|
||||
# transcription never had.
|
||||
Interface\AddOns\Blizzard_InspectUI\Blizzard_InspectUI.xml
|
||||
Interface\AddOns\Blizzard_InspectUI\InspectPaperDollFrame.xml
|
||||
Interface\AddOns\Blizzard_InspectUI\InspectHonorFrame.xml
|
||||
# The dressing room (decision 1060): the ctrl-click item preview. Loaded right after the
|
||||
# windows whose slots feed it (the character window above, the bags before that) and before
|
||||
# everything else that ctrl-clicks into it — order is legibility only, since `DressUpItemLink`
|
||||
# is a global resolved at call time. Needs UiPanels (the panel manager, its left-slot row) and
|
||||
# Fonts; its booth pane needs nothing loaded at all.
|
||||
DressUpFrame.xml
|
||||
# What those headers recorded that still holds: the inspect honor page inherits HonorFrame.xml's
|
||||
# five row templates, the macro icon chooser inherits `ClassTrainerListScrollFrameTemplate`
|
||||
# (1862) — FrameXML files listed above, so resolved long before any addon loads, which is also
|
||||
# why the reference's own toc has the same order. The gates over chain files
|
||||
# (`ui_script::reference_ui`) walk these addons' files after the manifest's entries — a reached
|
||||
# addon is part of the shipped interface, row or no row.
|
||||
# ══ THE DRESSING ROOM — the reference's own, off the player's chain (1751; decision 1969) ══
|
||||
# DressUpFrame.xml + DressUpFrame.lua: the ctrl-click item preview (1060). Its `<DressUpModel>`
|
||||
# is a real widget kind now — PlayerModel's three verbs plus its own `TryOn`/`Dress`/`Undress`
|
||||
# (table 0x84f190), queued as intents the app composes the booth's look from — and its pane draws
|
||||
# the "dressup" booth by the reference's own name (`portrait::model_pane_booth`). The rotate
|
||||
# buttons and OnUpdate drive `Model_RotateLeft/Right/OnUpdate` from UIParent.xml above, and the
|
||||
# Close/Reset buttons inherit UIPanelCloseButton/UIPanelButtonTemplate from the panel kit above.
|
||||
# Slot as before: after the windows whose slots feed it; `DressUpItemLink` resolves at call time.
|
||||
Interface\FrameXML\DressUpFrame.xml
|
||||
# ══ THE SPELLBOOK — the reference's own, off the player's chain (1751; decision 1952) ══
|
||||
# SpellBookFrame.xml + SpellBookFrame.lua: the book, the twelve page buttons, the skill-line tabs,
|
||||
# the Spell/Pet toggle tabs. Its whole engine surface was already ours (1924's UpdateSpells, 1931's
|
||||
@@ -800,23 +804,6 @@ Interface\FrameXML\SpellBookFrame.xml
|
||||
# <Model> (UI-AutoCastButton.mdx) and this engine's <Model> draws no pixels; the shine benilla
|
||||
# draws is its own token texture (1383/1392/1393). BENILLA_ALLOW_OWN_UI=1.
|
||||
SpellBookAdapters.xml
|
||||
# ══ THE TALENT WINDOW — the reference's own, off the player's chain (1751, window 25) ══
|
||||
# The second LoadOnDemand addon (1832 was the first, and its header carries the mechanics).
|
||||
# `Blizzard_TalentUI` is FIVE files behind one manifest line: the `.xml` sources its own `.lua`
|
||||
# and `<Include>`s `Blizzard_TalentUITemplates.xml`, which is where the four templates every
|
||||
# talent button, branch, arrow and tab inherits actually live. Its toc's only other entry is the
|
||||
# empty `Localization.lua`.
|
||||
#
|
||||
# It needs NOTHING built — every engine verb the five files call was already registered, checked
|
||||
# name by name against our bindings. What it needs LOADED is UIPanelTemplates (its scroll frame
|
||||
# is `UIPanelScrollFrameTemplate`, its close and cancel buttons `UIPanelCloseButton` /
|
||||
# `UIPanelButtonTemplate`) and Fonts, both far above.
|
||||
#
|
||||
# `ToggleTalentFrame` moved to UIParent.xml, the reference's own home for it — see 1833.
|
||||
#
|
||||
# What we GAIN: `TalentFrameTab4` and `TalentFrameTab5`. The reference lays out five tab slots
|
||||
# where our transcription stopped at the three a 1.12 class actually has.
|
||||
Interface\AddOns\Blizzard_TalentUI\Blizzard_TalentUI.xml
|
||||
# ══ THE SOCIAL WINDOW — the reference's own, off the player's chain (1751; decision 1959) ══
|
||||
# FriendsFrame.xml + FriendsFrame.lua: the O-key window's four tabs — friends/ignore, who, the
|
||||
# guild roster with its control and info panes, and the raid tab whose pane is the stock
|
||||
@@ -831,8 +818,10 @@ Interface\FrameXML\FriendsFrame.xml
|
||||
# a child cannot be declared before its parent — the reference splits it the same way and for the
|
||||
# same reason (its FrameXML.toc has RaidFrame.xml on the very next line). Decision 1549.
|
||||
#
|
||||
# TWO entries replace one of ours: the pane itself, and the LoadOnDemand half that carries the
|
||||
# group frames and the tear-off pullouts. 1839 measured the swap — 83 of our 84 frame names were
|
||||
# The pane is the entry here; the LoadOnDemand half that carries the group frames and the
|
||||
# tear-off pullouts, `Blizzard_RaidUI`, is a registry row that the stock RaidFrame.lua loads on
|
||||
# the first roster update (the addons header far above; 1967). 1839 measured the swap — 83 of our
|
||||
# 84 frame names were
|
||||
# already the reference's, 20 of our 22 Lua functions were, and NOTHING needed building. The one
|
||||
# name that was not is `BenillaRaidDropDown`, and the reference has no raid drop-down at all: it
|
||||
# reuses `FriendsDropDown`, which FriendsFrame.xml declares four manifest lines above.
|
||||
@@ -840,7 +829,6 @@ Interface\FrameXML\FriendsFrame.xml
|
||||
# GAINED: `RaidPulloutFrameTemplate`, `RaidPulloutButtonTemplate`, `RaidBuffButtonTemplate` and the
|
||||
# fifteen `RaidPullout*` verbs — the tear-off group pullouts we never transcribed.
|
||||
Interface\FrameXML\RaidFrame.xml
|
||||
Interface\AddOns\Blizzard_RaidUI\Blizzard_RaidUI.xml
|
||||
# ══ THE GOSSIP WINDOW — the reference's own, off the player's chain (1751) ══════════════════
|
||||
# No engine verb, no widget method, no shadowed name — all three instruments clean. It was
|
||||
# deferred once on `GossipResize` reading `GetTextHeight()` in the same repaint that sets the
|
||||
@@ -856,11 +844,18 @@ Interface\FrameXML\GossipFrame.xml
|
||||
# That is why the notes below say "grouping" rather than "reuses that file's helpers": the
|
||||
# windows still cluster here, but nothing depends on this file any more.
|
||||
Interface\FrameXML\MerchantFrame.xml
|
||||
# The mail window (decision 0544 P1/P2): the mailbox inbox, open-letter, and send tab over
|
||||
# the Era mail API (`benilla-ui/src/script/mail.rs`) + `crate::ui_mail`'s feed/drain. Kept
|
||||
# beside MerchantFrame in the NPC-window cluster; its postage/enclosed/COD coin displays use
|
||||
# the BenillaMoney_* kit, which is defined in MoneyFrame.xml far above.
|
||||
MailFrame.xml
|
||||
# ══ THE MAIL WINDOW — the reference's own, off the player's chain (1751; decision 1970) ══
|
||||
# MailFrame.xml + MailFrame.lua: the inbox, the open letter (with the auction invoice pane and
|
||||
# the letter-copy button), the send tab with its stationery picker and recipient autocomplete,
|
||||
# over the Era mail API (`benilla-ui/src/script/mail.rs`) + `crate::ui_mail`'s feed/drain. What
|
||||
# the swap built: the stationery family (`GetNumStationeries`/`GetStationeryInfo`/
|
||||
# `SelectStationery`/`GetSelectedStationeryTexture`, wow-re `stationery-bindings.md`) and the two
|
||||
# events the stock file listens for that nothing fired — `MAIL_SEND_INFO_UPDATE` on an attachment
|
||||
# change and `CLOSE_INBOX_ITEM(index)` when a take empties a mail. Its templates are all on the
|
||||
# chain above: the panel kit, ItemButtonTemplate, MoneyFrame/MoneyInputFrame, UIRadioButtonTemplate
|
||||
# (UIPanelTemplates.xml), InvoiceTextFontNormal (Fonts.xml) and FriendsFrameTabTemplate
|
||||
# (FriendsFrame.xml, which the reference's own toc also seats before this, l.71 → l.86).
|
||||
Interface\FrameXML\MailFrame.xml
|
||||
# ══ THE TRADE WINDOW — the reference's own, off the player's chain (1751; decision 1966) ══
|
||||
# TradeFrame.xml + TradeFrame.lua: both columns' seven slots over the trade API
|
||||
# (`benilla-ui/src/script/trade.rs`) + `crate::ui_trade`'s feed/drain, the recipient's
|
||||
@@ -951,29 +946,23 @@ Interface\FrameXML\TaxiFrame.xml
|
||||
# either moves — `ui_stable.rs`, `names.rs` and `net/apply/npc.rs` all reach it by stock names
|
||||
# already, and `portrait`'s booth keys on `PetStableModel`, which both spell the same.
|
||||
Interface\FrameXML\PetStable.xml
|
||||
# The auction house (decision 1511): the three-tab doublewide window over the auction API
|
||||
# (`benilla-ui/src/script/auction.rs`) + the app's feed/drain. Kept in the "NPC session
|
||||
# windows grouped" cluster beside Trainer/Bank/Taxi, and loaded AFTER MoneyFrame (its eight
|
||||
# money displays are SmallMoneyFrameTemplate, and it registers the AUCTION_DEPOSIT money type
|
||||
# into that file's MoneyTypeInfo), after MerchantFrame (grouping only now — that file's global
|
||||
# chain's MoneyInputFrame_* for its four money inputs, 1882), after ScrollTemplates (four faux lists), after
|
||||
# UIPanelTemplates (UIPanelButtonTemplate / UIPanelCloseButton / InputBoxTemplate /
|
||||
# UICheckButtonTemplate), after UIDropDownMenu (the rarity capsule) and after UiPanels (the
|
||||
# panel manager's `doublewide` seat, `CharacterFrameTabButtonTemplate`, the PanelTemplates
|
||||
# tab kit and the StaticPopup engine its two confirmations register into) — all above.
|
||||
AuctionFrame.xml
|
||||
# The crafting window (decision 0437 phase 2): NOT an NPC session (opens off your own cast of
|
||||
# an effect-47 opener spell, TradeSkillFrame.xml's own header comment) — loaded right after
|
||||
# TrainerFrame per the same "player-state/profession window" grouping and because it depends
|
||||
# on nothing TrainerFrame doesn't already need (Fonts/UiPanels/GameTooltip/ScrollTemplates,
|
||||
# all loaded above).
|
||||
TradeSkillFrame.xml
|
||||
# The Enchanting window (decision 0437 phase 3): the exact twin of TradeSkillFrame above —
|
||||
# same "opens off your own cast, not an NPC session" shape (CraftFrame.xml's own header
|
||||
# comment) — loaded right after it so `BuildColoredListString` (a guarded GLOBAL
|
||||
# TradeSkillFrame.xml defines) already exists when CraftFrame.xml reuses it without
|
||||
# redefining (CraftFrame.xml's own header comment, deviation 2).
|
||||
CraftFrame.xml
|
||||
# ══ THE AUCTION HOUSE — `Blizzard_AuctionUI`, a LoadOnDemand addon: NO ROW HERE (1751; 1971) ══
|
||||
# The three-tab doublewide window is the reference's own addon off the player's chain, seated as a
|
||||
# registry row (1957) and loaded on the first AUCTION_HOUSE_SHOW through UIParent.xml's own
|
||||
# `AuctionFrame_LoadUI` arm (ref UIParent.lua l.154-156, l.575-587), exactly like the trainer,
|
||||
# inspect, talent, macro and raid addons above. Its engine surface was already whole (decision
|
||||
# 1511's auction API); what it needed loaded is on the chain above: the panel kit, MoneyFrame /
|
||||
# MoneyInputFrame, UIDropDownMenu, and the dressing-room pane it embeds — `<DressUpModel
|
||||
# name="AuctionDressUpModel">`, the 1969 widget kind, drawing the same booth as the dressing room.
|
||||
# ══ THE CRAFTING WINDOWS — `Blizzard_TradeSkillUI` and `Blizzard_CraftUI`, LoadOnDemand: NO ROW HERE (1751; 1973) ══
|
||||
# The trade skill window (Blacksmithing, Tailoring, … and Poisons) and the craft window (Enchanting,
|
||||
# Beast Training) are the reference's own addons off the player's chain, registry rows (1957) loaded
|
||||
# on the first TRADE_SKILL_SHOW / CRAFT_SHOW through UIParent.xml's `TradeSkillFrame_LoadUI` /
|
||||
# `CraftFrame_LoadUI` arms (ref UIParent.lua l.166-168, l.190-192, l.605-632). Both inherit the
|
||||
# ClassTrainerFrameTemplates.xml kit far above and register their own panel rows. What 1973 built:
|
||||
# the four item-link verbs (`GetTradeSkillItemLink`, `GetTradeSkillReagentItemLink`,
|
||||
# `GetCraftItemLink` — the `|Henchant:` link — and `GetCraftReagentItemLink`, wow-re
|
||||
# `tradeskill-craft-item-links.md`) and `SetHyperlink`'s enchant leg. 0437's transcriptions are gone.
|
||||
# ══ THE LOOT WINDOW — the reference's own, off the player's chain (1751) ════════════════════
|
||||
# It never needed an engine VERB. It needed a widget TYPE: `<LootButton>` is a real registered
|
||||
# `CreateFrame` type (decision 1799), and registering it as a plain Button would have loaded the
|
||||
@@ -1041,8 +1030,8 @@ Interface\FrameXML\QuestTimerFrame.xml
|
||||
Interface\FrameXML\ChatFrame.xml
|
||||
Interface\FrameXML\FloatingChatFrame.xml
|
||||
# ══ THE MACRO WINDOW — the reference's own, off the player's chain (1751, window 32) ══
|
||||
# The fourth LoadOnDemand addon (1832's header carries the mechanics). ONE manifest line: its toc
|
||||
# lists `Blizzard_MacroUI.xml`, which sources its own `.lua`, plus the empty `Localization.lua`.
|
||||
# A LoadOnDemand addon with no row here since 1967 (the addons header far above); what stays in
|
||||
# this slot is the FrameXML file it needs loaded first.
|
||||
#
|
||||
# Nothing to build. 1835 recorded `SaveMacros` as a missing engine verb and it is not one — the
|
||||
# only occurrence in the reference's own file is COMMENTED OUT (`--SaveMacros();` in
|
||||
@@ -1063,9 +1052,9 @@ Interface\FrameXML\FloatingChatFrame.xml
|
||||
# `FauxScrollFrame_GetOffset` (`return frame.offset`, no `or 0` — our deleted copy had that
|
||||
# fallback and was hiding this) handed `MacroPopupFrame_Update` a nil to multiply. Clicking
|
||||
# "Change Name/Icon" raised. The reference's own `FrameXML.toc` loads this at its line 92, well
|
||||
# before any addon; ours takes it here, immediately above its one consumer. Decision 1862.
|
||||
# before any addon; ours takes it here — its one consumer, `Blizzard_MacroUI`, is a LoadOnDemand
|
||||
# row now (1967) and loads after everything. Decision 1862.
|
||||
Interface\FrameXML\ClassTrainerFrameTemplates.xml
|
||||
Interface\AddOns\Blizzard_MacroUI\Blizzard_MacroUI.xml
|
||||
# The script error log (decision 1495): the window `/errors` opens — the list B293's two
|
||||
# reporters asked for, which the reference's one-message-at-a-time ScriptErrors modal cannot
|
||||
# be. Ours entirely (1.12 has no counterpart), reading the retained log on the model rather
|
||||
|
||||
@@ -1069,6 +1069,24 @@ const TABLE: &[Spec] = &[
|
||||
None,
|
||||
None
|
||||
),
|
||||
// The battleground scoreboard and the battlefield minimap — the Interface block's last two
|
||||
// (Bindings.xml l.630-635), both Lua-bodied: `ToggleWorldStateScoreFrame` is the stock
|
||||
// WorldStateFrame.lua's since 1972 and `ToggleBattlefieldMinimap` UIParent.xml's (ref
|
||||
// UIParent.lua l.216-221). Defaults byte-real from `WTF\DefaultBindings.wtf`.
|
||||
spec!(
|
||||
"TOGGLEWORLDSTATESCORES",
|
||||
INTERFACE,
|
||||
Kind::Edge("ToggleWorldStateScoreFrame()"),
|
||||
Some("SHIFT-SPACE"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"TOGGLEBATTLEFIELDMINIMAP",
|
||||
INTERFACE,
|
||||
Kind::Edge("ToggleBattlefieldMinimap()"),
|
||||
Some("SHIFT-M"),
|
||||
None
|
||||
),
|
||||
// ── Miscellaneous (BINDING_HEADER_MISC) ─────────────────────────────────────────────
|
||||
spec!(
|
||||
"MINIMAPZOOMIN",
|
||||
@@ -1829,16 +1847,6 @@ pub(crate) static ABSENT: &[Absent] = &[
|
||||
),
|
||||
// ── Action bar ──────────────────────────────────────────────────────────────────────
|
||||
// ── Interface ───────────────────────────────────────────────────────────────────────
|
||||
absent!(
|
||||
"TOGGLEWORLDSTATESCORES",
|
||||
["ToggleWorldStateScoreFrame"],
|
||||
"no battlegrounds"
|
||||
),
|
||||
absent!(
|
||||
"TOGGLEBATTLEFIELDMINIMAP",
|
||||
["ToggleBattlefieldMinimap"],
|
||||
"no battlegrounds"
|
||||
),
|
||||
// ── Misc ────────────────────────────────────────────────────────────────────────────
|
||||
absent!(
|
||||
"TOGGLEFPS",
|
||||
|
||||
@@ -120,6 +120,7 @@ mod ui_action;
|
||||
mod ui_auction;
|
||||
mod ui_aura;
|
||||
mod ui_bank;
|
||||
mod ui_battlefield_score;
|
||||
mod ui_bind_confirm;
|
||||
mod ui_binder;
|
||||
mod ui_cast;
|
||||
@@ -209,6 +210,7 @@ use ui_action::UiActionPlugin;
|
||||
use ui_auction::UiAuctionPlugin;
|
||||
use ui_aura::UiAuraPlugin;
|
||||
use ui_bank::UiBankPlugin;
|
||||
use ui_battlefield_score::BattlefieldScorePlugin;
|
||||
use ui_binder::UiBinderPlugin;
|
||||
use ui_cast::UiCastPlugin;
|
||||
use ui_char::UiCharPlugin;
|
||||
@@ -641,6 +643,7 @@ pub fn run(build: BuildId) -> AppExit {
|
||||
// The dialog engine's own verbs (decision 1963): the pet trainer's question, the instance
|
||||
// boot clock, the area spirit healer, the battleground queue, the meeting stone.
|
||||
.add_plugins(UiDialogVerbsPlugin)
|
||||
.add_plugins(BattlefieldScorePlugin)
|
||||
// Being summoned (decision 1747): SMSG_SUMMON_REQUEST's latch, the CONFIRM_SUMMON dialog it
|
||||
// raises, and the CMSG_SUMMON_RESPONSE its Accept sends. The binder's twin one line up — a
|
||||
// server-asked question whose only wire answer is yes — and here for that reason.
|
||||
|
||||
@@ -1274,6 +1274,12 @@ pub(crate) enum ClientCommand {
|
||||
map_id: u32,
|
||||
accept: bool,
|
||||
},
|
||||
/// `RequestBattlefieldScoreData()` — `MSG_PVP_LOG_DATA`, empty (decision 1972).
|
||||
RequestBattlefieldScoreData,
|
||||
/// `LeaveBattlefield()` — `CMSG_LEAVE_BATTLEFIELD`: the active slot's map (decision 1972).
|
||||
LeaveBattlefield {
|
||||
map_id: u32,
|
||||
},
|
||||
/// `CancelMeetingStoneRequest()` — `CMSG 0x293`, empty.
|
||||
MeetingStoneLeave,
|
||||
/// `ConfirmPetUnlearn()` — `CMSG_PET_UNLEARN` with the latched trainer guid.
|
||||
@@ -1567,6 +1573,8 @@ pub(crate) enum ClientCommand {
|
||||
receiver: String,
|
||||
subject: String,
|
||||
body: String,
|
||||
/// The selected `Stationery.dbc` id (`CMSG_SEND_MAIL`'s sixth field, 1970).
|
||||
stationery: u32,
|
||||
item_guid: u64,
|
||||
money: u32,
|
||||
cod: u32,
|
||||
|
||||
@@ -258,6 +258,7 @@ pub(super) fn apply_net_updates(
|
||||
ResMut<crate::ui_dialog_verbs::AreaSpiritHealer>,
|
||||
ResMut<crate::ui_dialog_verbs::BattlefieldQueue>,
|
||||
ResMut<crate::ui_dialog_verbs::MeetingStone>,
|
||||
ResMut<crate::ui_battlefield_score::BattlefieldScoreboard>,
|
||||
),
|
||||
),
|
||||
// The guard's directions marker (`SMSG_GOSSIP_POI`) and the map id it has to be
|
||||
@@ -481,6 +482,7 @@ pub(super) fn apply_net_updates(
|
||||
mut area_spirit,
|
||||
mut battlefield_queue,
|
||||
mut meeting_stone,
|
||||
mut battlefield_scoreboard,
|
||||
),
|
||||
),
|
||||
mut poi_marker,
|
||||
@@ -959,6 +961,7 @@ pub(super) fn apply_net_updates(
|
||||
area_spirit.on_time(healer, ms, std::time::Instant::now());
|
||||
}
|
||||
SessionEvent::BattlefieldStatus(status) => battlefield_queue.apply(status),
|
||||
SessionEvent::PvpLogData(data) => battlefield_scoreboard.apply(data),
|
||||
SessionEvent::MeetingStoneSetQueue { area, status } => {
|
||||
meeting_stone.apply(area, status);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! dispatcher. The `UiScript` events these ultimately drive are fired by [`crate::ui_mail::feed_mail`]
|
||||
//! (the feed owns the VM), so these arms only mutate resources + send the wire re-syncs.
|
||||
|
||||
use benilla_protocol::messages::{mail_action, mail_error, MailListEntry};
|
||||
use benilla_protocol::messages::{mail_action, mail_error, mail_message_type, MailListEntry};
|
||||
|
||||
use crate::net::{ClientCommand, NetCommands};
|
||||
use crate::ui_items::{EquipError, EquipErrors};
|
||||
@@ -42,14 +42,43 @@ pub(super) fn mail_list(mails: Vec<MailListEntry>, mail: &mut MailOpen, commands
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// `MAIL_CHECK_MASK_COD_PAYMENT` — the `checked` bit (`byte[rec+0x148] & 8`) that marks a mail as
|
||||
/// the money a COD taker paid; taking that money empties the mail (wow-re, 1970).
|
||||
const CHECKED_COD_PAYMENT: u32 = 8;
|
||||
|
||||
/// The two take legs' "this mail is now empty" decision, off the client's own bytes (wow-re
|
||||
/// `stationery-bindings.md` §8, 1970): a take that empties the mail sends `CMSG_MAIL_DELETE`
|
||||
/// itself, then `CLOSE_INBOX_ITEM(index)`, then `MAIL_INBOX_UPDATE`. The money leg (`0x4ad6b0`)
|
||||
/// purges iff the mail is a COD payment, or an auction notice with no item; the item leg
|
||||
/// (`0x4ad7b0`) purges iff the money is gone and the mail is an auction notice or carries no
|
||||
/// letter text. A plain letter you took the money from stays open, husk and all — that is the
|
||||
/// reference too (the stock `OpenMailFrame_OnHide` deletes a copied, emptied letter on close).
|
||||
///
|
||||
/// The item leg's second conjunct reads `[rec+0x114] == 0 && [rec+0x25c] == 0`; `+0x114` is the
|
||||
/// letter's text id and `+0x25c` a second no-text field the carve did not name (INFERRED to be the
|
||||
/// fetched body). This takes the text id alone, which can only purge a mail the client also would
|
||||
/// when that second field is zero whenever the first is.
|
||||
fn take_empties(entry: &MailListEntry, action: u32) -> bool {
|
||||
let auction = entry.message_type == mail_message_type::AUCTION;
|
||||
match action {
|
||||
mail_action::MONEY_TAKEN => {
|
||||
entry.checked & CHECKED_COD_PAYMENT != 0 || (auction && entry.item.is_none())
|
||||
}
|
||||
mail_action::ITEM_TAKEN => entry.money == 0 && (auction || entry.item_text_id == 0),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// `SessionEvent::SendMailResult` (`SMSG_SEND_MAIL_RESULT`) — route per action/error (decision 0544
|
||||
/// P2). action == SEND queues a [`MailSendAck`] for the feed (MAIL_FAILED always, MAIL_SEND_SUCCESS
|
||||
/// on OK, else the red error line). A successful take/return/delete re-syncs the inbox with a fresh
|
||||
/// `CMSG_GET_MAIL_LIST` (the reference client's inbox-refresh moment); an EQUIP_ERROR routes to the
|
||||
/// existing inventory-error surface; any other failure surfaces the red error line.
|
||||
/// on OK, else the red error line). A successful take applies to the local row and, when it
|
||||
/// empties the mail, deletes it the way the client does ([`take_empties`]); every successful
|
||||
/// take/return/delete then re-syncs the inbox with a fresh `CMSG_GET_MAIL_LIST` (the reference
|
||||
/// client's inbox-refresh moment); an EQUIP_ERROR routes to the existing inventory-error surface;
|
||||
/// any other failure surfaces the red error line.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn send_mail_result(
|
||||
_mail_id: u32,
|
||||
mail_id: u32,
|
||||
action: u32,
|
||||
error: u32,
|
||||
equip_error: Option<u32>,
|
||||
@@ -82,6 +111,25 @@ pub(super) fn send_mail_result(
|
||||
// A take/return/delete result.
|
||||
match error {
|
||||
mail_error::OK => {
|
||||
// The take landed on the local row first (the client clears `[rec+0x140]` /
|
||||
// `[rec+0x120]` before deciding), and an emptied mail is purged client-side:
|
||||
// `CMSG_MAIL_DELETE`, then `CLOSE_INBOX_ITEM(index)` from the feed (1970).
|
||||
if let Some(pos) = mail.mails.iter().position(|e| e.message_id == mail_id) {
|
||||
match action {
|
||||
mail_action::MONEY_TAKEN => mail.mails[pos].money = 0,
|
||||
mail_action::ITEM_TAKEN => mail.mails[pos].item = None,
|
||||
_ => {}
|
||||
}
|
||||
if take_empties(&mail.mails[pos], action) {
|
||||
if let Some(mailbox) = mail.mailbox {
|
||||
let _ = commands
|
||||
.0
|
||||
.send(ClientCommand::MailDelete { mailbox, mail_id });
|
||||
}
|
||||
mail.close_inbox.push(pos as u32 + 1);
|
||||
mail.mails.remove(pos);
|
||||
}
|
||||
}
|
||||
// Re-sync the inbox: the taken money/item or removed row is gone server-side.
|
||||
if let Some(mailbox) = mail.mailbox {
|
||||
let _ = commands.0.send(ClientCommand::GetMailList { mailbox });
|
||||
|
||||
@@ -1071,6 +1071,10 @@ fn writer_loop(
|
||||
ClientCommand::BattlefieldPort { map_id, accept } => {
|
||||
w.battlefield_port(map_id, accept)
|
||||
}
|
||||
ClientCommand::RequestBattlefieldScoreData => {
|
||||
w.request_battlefield_score_data()
|
||||
}
|
||||
ClientCommand::LeaveBattlefield { map_id } => w.leave_battlefield(map_id),
|
||||
ClientCommand::MeetingStoneLeave => w.meeting_stone_leave(),
|
||||
ClientCommand::PetUnlearn { trainer } => w.pet_unlearn(trainer),
|
||||
ClientCommand::BankerActivate { guid } => w.banker_activate(guid),
|
||||
@@ -1162,15 +1166,16 @@ fn writer_loop(
|
||||
receiver,
|
||||
subject,
|
||||
body,
|
||||
stationery,
|
||||
item_guid,
|
||||
money,
|
||||
cod,
|
||||
} => w.send_mail(
|
||||
mailbox, &receiver, &subject, &body,
|
||||
// stationery/package: vmangos discards both — player mail is always
|
||||
// stored MAIL_STATIONERY_DEFAULT (41, decision 0544) regardless of what
|
||||
// rides the wire here.
|
||||
41, 0, item_guid, money, cod,
|
||||
// The stationery the player selected (1970) and package 0 — vmangos
|
||||
// discards both and stores MAIL_STATIONERY_DEFAULT (41, decision 0544),
|
||||
// but the wire carries what the client chose, as the reference's does.
|
||||
stationery, 0, item_guid, money, cod,
|
||||
),
|
||||
ClientCommand::MailTakeMoney { mailbox, mail_id } => {
|
||||
w.mail_take_money(mailbox, mail_id)
|
||||
|
||||
@@ -162,7 +162,7 @@ pub(super) fn refresh_hud_snapshot(
|
||||
/// right, the action bar the bottom, the chat dock the bottom left).
|
||||
///
|
||||
/// **Asked of the frame, not recomputed from its numbers.** Its row pitch and anchor live in
|
||||
/// `assets/ui/WorldStateFrame.xml`; mirroring them here would be two copies to keep in step, so
|
||||
/// the stock `WorldStateFrame.xml` (1972); mirroring them here would be two copies to keep in step, so
|
||||
/// this reads the resolved edge the layout actually produced (`GetBottom`, y-up). One tiny chunk
|
||||
/// at 4 Hz, only while the HUD is drawing — the same shape as [`crate::hover_log`]'s tooltip
|
||||
/// probe.
|
||||
@@ -181,12 +181,25 @@ pub(super) fn refresh_hud_snapshot(
|
||||
/// (~47 µs on a 200-frame tree — `layout_methods::settle`'s own measurement), four times a second.
|
||||
/// Every other frame it is a chunk load and a table lookup.
|
||||
pub(crate) fn top_centre_claimed(script: &UiScript, win_h: f32) -> f32 {
|
||||
// The stock `WorldStateAlwaysUpFrame` is a permanently shown container (1972); what the
|
||||
// readout actually occupies is its ROWS — `AlwaysUpFrame<n>`, built on demand and hidden when
|
||||
// the scope admits nothing — so the claim is the lowest shown row's bottom, or nothing.
|
||||
const CHUNK: &str = r#"
|
||||
local f = WorldStateAlwaysUpFrame
|
||||
if not (f and f:IsVisible()) then return -1 end
|
||||
local bottom, screen = f:GetBottom(), GetScreenHeight()
|
||||
if not bottom or not screen or screen <= 0 then return -1 end
|
||||
return (screen - bottom) / screen
|
||||
local lowest, i = nil, 1
|
||||
while true do
|
||||
local r = getglobal("AlwaysUpFrame" .. i)
|
||||
if not r then break end
|
||||
if r:IsShown() then
|
||||
local b = r:GetBottom()
|
||||
if b and (not lowest or b < lowest) then lowest = b end
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
local screen = GetScreenHeight()
|
||||
if not lowest or not screen or screen <= 0 then return -1 end
|
||||
return (screen - lowest) / screen
|
||||
"#;
|
||||
let frac: f32 = script.eval::<f64>(CHUNK).unwrap_or(-1.0) as f32;
|
||||
if !(0.0..=1.0).contains(&frac) {
|
||||
@@ -349,10 +362,12 @@ mod tests {
|
||||
claimed > 8.0,
|
||||
"two rows reach past the pill's own seat, so the pill must move: {claimed}"
|
||||
);
|
||||
// The frame's own geometry, read back the way the probe reads it: the container's top offset
|
||||
// plus one row per pushed row. Asserted against the XML rather than restated as constants.
|
||||
// The frame's own geometry, read back the way the probe reads it: the second (lowest)
|
||||
// row's resolved bottom. Asserted against the stock XML rather than restated as constants.
|
||||
let expected: f32 = s
|
||||
.eval::<f64>("return (20 + WORLD_STATE_ROW_HEIGHT * 2 + 15) / GetScreenHeight()")
|
||||
.eval::<f64>(
|
||||
"return (GetScreenHeight() - AlwaysUpFrame2:GetBottom()) / GetScreenHeight()",
|
||||
)
|
||||
.expect("the frame's own numbers") as f32
|
||||
* SCREEN_H;
|
||||
assert!(
|
||||
|
||||
@@ -371,7 +371,7 @@ pub(super) fn sync_dressup_booth(
|
||||
//
|
||||
// And the other half of `SetRotation`: the turn-in-place shuffle
|
||||
// ([`super::booth::drive_booth_turn`], 1559). The dressing room wires the same held-arrow
|
||||
// `OnUpdate` the character window does (`BenillaDressUpModel_OnUpdate`), so it steps its feet
|
||||
// `OnUpdate` the character window does (the stock `Model_OnUpdate`, 1969), so it steps its feet
|
||||
// the same way. Keyed on the yaw alone — this block also runs for a re-bake, which is a
|
||||
// `RefreshUnit` in the reference and does not turn the model.
|
||||
if booth.turn.faced != Some(preview.yaw) {
|
||||
|
||||
@@ -160,8 +160,9 @@ const STABLE_SLOT: &str = "stable";
|
||||
///
|
||||
/// The names, and where each is declared: `CharacterModelFrame` (stock `PaperDollFrame.xml`),
|
||||
/// `PetModelFrame` (stock `PetPaperDollFrame.xml`), `PetStableModel` (stock `PetStable.xml`, the
|
||||
/// reference's own file since 1751), `DressUpModel` (stock `DressUpFrame.xml`'s name, still our
|
||||
/// file), and `InspectModelFrame` (stock `InspectPaperDollFrame.xml`, out of the LoadOnDemand
|
||||
/// reference's own file since 1751), `DressUpModel` (stock `DressUpFrame.xml`, 1969),
|
||||
/// `AuctionDressUpModel` (the auction addon's `Blizzard_AuctionDressUp.xml`, 1971), and
|
||||
/// `InspectModelFrame` (stock `InspectPaperDollFrame.xml`, out of the LoadOnDemand
|
||||
/// `Blizzard_InspectUI` — the reference's own since 1832).
|
||||
///
|
||||
/// **Every name here is now the reference's.** This note used to carry a `BenillaInspectModelFrame`
|
||||
@@ -169,12 +170,17 @@ const STABLE_SLOT: &str = "stable";
|
||||
/// take the name from. It is not packed — the `.pub` is only what the loose
|
||||
/// `Interface\AddOns\Blizzard_InspectUI\` folder holds, while the real `.xml` and `.lua` sit
|
||||
/// inside `patch.MPQ`, which the chain mounts. 1832 migrated the window and the prefix went with it.
|
||||
const MODEL_PANE_BOOTHS: [(&str, &str); 5] = [
|
||||
const MODEL_PANE_BOOTHS: [(&str, &str); 6] = [
|
||||
("CharacterModelFrame", PAPERDOLL_SLOT),
|
||||
("PetModelFrame", PETDOLL_SLOT),
|
||||
("InspectModelFrame", INSPECT_SLOT),
|
||||
("PetStableModel", STABLE_SLOT),
|
||||
("DressUpModel", dressup::DRESSUP_SLOT),
|
||||
// The auction house's embedded dressing room (`Blizzard_AuctionDressUp.xml`, 1971) — a second
|
||||
// `<DressUpModel>` sharing the one booth: the app keeps ONE dressing-room look (its
|
||||
// `TryOn`/`Dress` intents are not per-widget), so the two panes show the same substitutions.
|
||||
// A deliberate approximation, named: the reference's two widgets each clone their own model.
|
||||
("AuctionDressUpModel", dressup::DRESSUP_SLOT),
|
||||
];
|
||||
|
||||
/// The booth a named model pane samples, or `None` for a pane no window has claimed.
|
||||
|
||||
@@ -576,6 +576,7 @@ fn feed_auction(
|
||||
mut last_open: Local<crate::ui_script::VmMemo<Option<u64>>>,
|
||||
mut last_can_query: Local<crate::ui_script::VmMemo<bool>>,
|
||||
mut last_sell: Local<crate::ui_script::VmMemo<Option<(i64, u32)>>>,
|
||||
mut last_classes: Local<crate::ui_script::VmMemo<Vec<AuctionCategory>>>,
|
||||
) {
|
||||
let Some(mut script) = script else {
|
||||
return;
|
||||
@@ -585,6 +586,17 @@ fn feed_auction(
|
||||
let last_can_query = last_can_query.get(&script);
|
||||
let last_sell = last_sell.get(&script);
|
||||
|
||||
// The Browse tab's class tree is login-scoped: the stock `AuctionFrameBrowse_OnLoad` reads
|
||||
// `GetAuctionItemClasses()` at the addon's LOAD — the first auctioneer, before the session's
|
||||
// snapshot lands — and the client answers off `ItemClass.dbc` with no session at all (1971).
|
||||
// Pushed once the two catalogs exist and whenever they change (a diff, like the inbox's).
|
||||
let classes_now = categories(catalogs.0.as_deref(), catalogs.1.as_deref());
|
||||
let last_classes = last_classes.get(&script);
|
||||
if *last_classes != classes_now {
|
||||
*last_classes = classes_now.clone();
|
||||
script.set_auction_item_classes(classes_now);
|
||||
}
|
||||
|
||||
// The queued client messages (decision 1523). Resolved against the VM's own GlobalStrings and
|
||||
// shown on the surface the message's catalog row names — the `ui_quest` shape (0669).
|
||||
//
|
||||
@@ -622,7 +634,6 @@ fn feed_auction(
|
||||
}
|
||||
|
||||
let self_guid = self_q.iter().next().map(|g| g.0);
|
||||
let (classes, subclasses) = (&catalogs.0, &catalogs.1);
|
||||
let rolls = crate::items::RollCatalogs {
|
||||
props: catalogs.2.as_deref(),
|
||||
enchants: catalogs.3.as_deref(),
|
||||
@@ -647,7 +658,6 @@ fn feed_auction(
|
||||
});
|
||||
AuctionState {
|
||||
lists,
|
||||
categories: categories(classes.as_deref(), subclasses.as_deref()),
|
||||
// The rate this house charges, keyed by the id the hello reply carried.
|
||||
deposit_percent: houses
|
||||
.as_deref()
|
||||
@@ -681,13 +691,12 @@ fn feed_auction(
|
||||
// One routine invalidates all three lists in the reference too — the three events fire
|
||||
// together rather than being diffed apart, and each tab's handler repaints only if it
|
||||
// is the visible one.
|
||||
for event in [
|
||||
"AUCTION_ITEM_LIST_UPDATE",
|
||||
"AUCTION_BIDDER_LIST_UPDATE",
|
||||
"AUCTION_OWNED_LIST_UPDATE",
|
||||
] {
|
||||
script.fire_event(event, vec![]);
|
||||
}
|
||||
// Three literal fires, not a loop over names: the producer tripwire
|
||||
// (`reference_ui::every_event_a_chain_file_registers_has_a_producer`) reads fire
|
||||
// sites by their literal, and the stock addon registers all three (1971).
|
||||
script.fire_event("AUCTION_ITEM_LIST_UPDATE", vec![]);
|
||||
script.fire_event("AUCTION_BIDDER_LIST_UPDATE", vec![]);
|
||||
script.fire_event("AUCTION_OWNED_LIST_UPDATE", vec![]);
|
||||
}
|
||||
}
|
||||
*last = fresh;
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
//! The battleground **scoreboard** feed (decision 1972; wow-re `battlefield-verb-family.md`):
|
||||
//! the app's half of the stock `WorldStateFrame.lua` score frame — the name-resolution barrier,
|
||||
//! the team derivation, the column headers, the request throttle and the leave.
|
||||
//!
|
||||
//! - **The board arrives raw** (`MSG_PVP_LOG_DATA`, GUIDs and numbers, wire order) and the
|
||||
//! reference does nothing with it until EVERY row's name has resolved (§6.1: the last name
|
||||
//! arrival is what rebuilds and fires). Ours asks the name cache for each row every frame it
|
||||
//! is unresolved and pushes the board the frame the last one lands.
|
||||
//! - **Team is derived, never wire data** (§6.2): race → faction; `0` Horde, `1` Alliance, `-1`
|
||||
//! for a race the tables do not carry. Race and class strings come off the same traits the
|
||||
//! name query answered with.
|
||||
//! - **Columns are `WorldStateUI.dbc` rows** (`worldstate-ui-law.md`, the `0x2D4` status-3 arm):
|
||||
//! in table order, the first contiguous run of rows whose `MapID` is the battleground's map or
|
||||
//! `-1` and whose `Type` is 2; the text raw, the icon, the tooltip.
|
||||
//! - **`UPDATE_BATTLEFIELD_SCORE`** fires here — on a pushed board, and on the status-3 arrival
|
||||
//! the queue flags — BEFORE the queue feed fires `UPDATE_BATTLEFIELD_STATUS` (§4.2's order).
|
||||
//! - **The request is throttled to 5000 ms** (§5.1) and the leave carries the active map (§5.3).
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use benilla_protocol::messages::PvpLogData;
|
||||
use benilla_ui::script::{BattlefieldScoreRow, BattlefieldScores, BattlefieldStatColumn, UiScript};
|
||||
|
||||
use crate::names::NameCache;
|
||||
use crate::net::{ClientCommand, NetCommands};
|
||||
use crate::ui_dialog_verbs::BattlefieldQueue;
|
||||
use crate::ui_script::UiInput;
|
||||
use crate::world_state_ui::WorldStateUiRes;
|
||||
|
||||
/// `RequestBattlefieldScoreData`'s throttle — `0x4aa170`: `now + 0x1388`.
|
||||
const REQUEST_THROTTLE: Duration = Duration::from_millis(5000);
|
||||
|
||||
/// The last `MSG_PVP_LOG_DATA`, and the request stamp.
|
||||
#[derive(Resource, Default)]
|
||||
pub(crate) struct BattlefieldScoreboard {
|
||||
log: Option<PvpLogData>,
|
||||
last_request: Option<Instant>,
|
||||
}
|
||||
|
||||
impl BattlefieldScoreboard {
|
||||
/// `SessionEvent::PvpLogData` — the whole board, replacing the last.
|
||||
pub(crate) fn apply(&mut self, data: PvpLogData) {
|
||||
self.log = Some(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// The scoreboard's column headers for `map`: `WorldStateUI.dbc` in table order, the first
|
||||
/// contiguous run of `(MapID == map || MapID == -1) && Type == 2` — once a run has started, the
|
||||
/// first non-matching row ends it (`0x4aa9fe`).
|
||||
pub(crate) fn score_columns(catalog: &WorldStateUiRes, map: u32) -> Vec<BattlefieldStatColumn> {
|
||||
let mut out = Vec::new();
|
||||
for (_, row) in catalog.0.rows() {
|
||||
let matches = (row.map_id == map || row.map_id == u32::MAX) && row.ui_type == 2;
|
||||
if matches {
|
||||
out.push(BattlefieldStatColumn {
|
||||
text: row.text.clone(),
|
||||
icon: row.icon.clone(),
|
||||
tooltip: row.tooltip.clone(),
|
||||
});
|
||||
} else if !out.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Resolve the raw board through the name cache; `None` while any name is still in flight.
|
||||
fn resolve_board(
|
||||
log: &PvpLogData,
|
||||
names: &mut NameCache,
|
||||
commands: &NetCommands,
|
||||
) -> Option<Vec<BattlefieldScoreRow>> {
|
||||
let mut rows = Vec::with_capacity(log.rows.len());
|
||||
for r in &log.rows {
|
||||
let name = names.resolve(r.guid, commands)?.to_string();
|
||||
let (race, class) = names
|
||||
.player_traits(r.guid)
|
||||
.map_or((0, 0), |(race, class, _)| (race, class));
|
||||
// `0` Horde, `1` Alliance, `-1` neither — the reference's mask walk lands on the same
|
||||
// three values (§6.2), and every playable race has a fixed side in 1.12.
|
||||
let faction = match crate::ui_unit::race_faction_group(race) {
|
||||
Some("Alliance") => 1,
|
||||
Some(_) => 0,
|
||||
None => -1,
|
||||
};
|
||||
let mut stats = [0u32; 8];
|
||||
for (slot, v) in stats.iter_mut().zip(&r.stats) {
|
||||
*slot = *v;
|
||||
}
|
||||
rows.push(BattlefieldScoreRow {
|
||||
name,
|
||||
killing_blows: r.killing_blows,
|
||||
honorable_kills: r.honorable_kills,
|
||||
deaths: r.deaths,
|
||||
honor_gained: r.honor_gained,
|
||||
faction,
|
||||
rank: i32::try_from(r.rank).unwrap_or(i32::MAX),
|
||||
race: crate::ui_unit::race_names(race).map(|(n, _)| n.to_string()),
|
||||
class: crate::ui_unit::class_names(class).map(|(n, _)| n.to_string()),
|
||||
stats,
|
||||
});
|
||||
}
|
||||
Some(rows)
|
||||
}
|
||||
|
||||
fn feed_battlefield_score(
|
||||
script: Option<NonSendMut<UiScript>>,
|
||||
board: Res<BattlefieldScoreboard>,
|
||||
mut queue: ResMut<BattlefieldQueue>,
|
||||
catalog: Option<Res<WorldStateUiRes>>,
|
||||
mut names: ResMut<NameCache>,
|
||||
commands: Res<NetCommands>,
|
||||
mut last: Local<crate::ui_script::VmMemo<Option<BattlefieldScores>>>,
|
||||
) {
|
||||
let Some(mut script) = script else {
|
||||
return;
|
||||
};
|
||||
let now = Instant::now();
|
||||
script.set_battlefield_run_time_ms(queue.run_time_ms(now));
|
||||
|
||||
let fresh = board.log.as_ref().and_then(|log| {
|
||||
let rows = resolve_board(log, &mut names, &commands)?;
|
||||
let columns = queue
|
||||
.active_map()
|
||||
.zip(catalog.as_deref())
|
||||
.map(|(map, cat)| score_columns(cat, map))
|
||||
.unwrap_or_default();
|
||||
Some(BattlefieldScores {
|
||||
rows,
|
||||
ended: log.ended,
|
||||
winner: log.winner.unwrap_or(0),
|
||||
columns,
|
||||
})
|
||||
});
|
||||
let last = last.get(&script);
|
||||
let status_rebuild = queue.take_score_dirty();
|
||||
let changed = fresh.is_some() && fresh != *last;
|
||||
if changed {
|
||||
script.set_battlefield_scores(fresh.clone().expect("checked"));
|
||||
*last = fresh;
|
||||
}
|
||||
if changed || status_rebuild {
|
||||
script.fire_event("UPDATE_BATTLEFIELD_SCORE", vec![]);
|
||||
}
|
||||
}
|
||||
|
||||
fn drain_battlefield_score(
|
||||
script: Option<NonSendMut<UiScript>>,
|
||||
mut board: ResMut<BattlefieldScoreboard>,
|
||||
queue: Res<BattlefieldQueue>,
|
||||
commands: Res<NetCommands>,
|
||||
) {
|
||||
let Some(mut script) = script else {
|
||||
return;
|
||||
};
|
||||
if script.take_battlefield_score_requests() > 0 {
|
||||
let now = Instant::now();
|
||||
let throttled = board
|
||||
.last_request
|
||||
.is_some_and(|t| now.duration_since(t) < REQUEST_THROTTLE);
|
||||
if !throttled {
|
||||
board.last_request = Some(now);
|
||||
let _ = commands.0.send(ClientCommand::RequestBattlefieldScoreData);
|
||||
}
|
||||
}
|
||||
if script.take_battlefield_leave_requests() > 0 {
|
||||
let _ = commands.0.send(ClientCommand::LeaveBattlefield {
|
||||
map_id: queue.active_map().unwrap_or(0),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BattlefieldScorePlugin;
|
||||
|
||||
impl Plugin for BattlefieldScorePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<BattlefieldScoreboard>().add_systems(
|
||||
Update,
|
||||
(
|
||||
// Before the queue feed: the score event precedes the status event on the
|
||||
// status-3 message, as in the client.
|
||||
feed_battlefield_score
|
||||
.before(crate::ui_dialog_verbs::feed_dialog_verbs)
|
||||
.before(UiInput),
|
||||
drain_battlefield_score.after(UiInput),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use benilla_formats::{WorldStateUiCatalog, WorldStateUiRow};
|
||||
|
||||
fn row(map_id: u32, ui_type: u32, text: &str) -> WorldStateUiRow {
|
||||
WorldStateUiRow {
|
||||
map_id,
|
||||
area_id: 0,
|
||||
icon: String::new(),
|
||||
text: text.into(),
|
||||
tooltip: String::new(),
|
||||
state_variable: 0,
|
||||
ui_type,
|
||||
dynamic_icon: String::new(),
|
||||
dynamic_tooltip: String::new(),
|
||||
extended_ui: String::new(),
|
||||
extended_ui_state: [0; 3],
|
||||
}
|
||||
}
|
||||
|
||||
/// The column scan: table order, the map's or the wildcard's `Type == 2` rows, and the first
|
||||
/// non-matching row after the run has started ends it.
|
||||
#[test]
|
||||
fn the_columns_are_the_first_contiguous_run_of_type_two_rows_for_the_map() {
|
||||
let cat = WorldStateUiRes(WorldStateUiCatalog::from_rows(vec![
|
||||
(1, row(489, 0, "always-up")),
|
||||
(2, row(529, 2, "other map")),
|
||||
(3, row(489, 2, "Flags Captured")),
|
||||
(4, row(u32::MAX, 2, "Flags Returned")),
|
||||
(5, row(489, 0, "a gap")),
|
||||
(6, row(489, 2, "after the gap")),
|
||||
]));
|
||||
let cols: Vec<String> = score_columns(&cat, 489)
|
||||
.into_iter()
|
||||
.map(|c| c.text)
|
||||
.collect();
|
||||
assert_eq!(cols, ["Flags Captured", "Flags Returned"]);
|
||||
// Another map keeps the wildcard row alone — the reference's `-1` rows serve every map.
|
||||
let cols: Vec<String> = score_columns(&cat, 30)
|
||||
.into_iter()
|
||||
.map(|c| c.text)
|
||||
.collect();
|
||||
assert_eq!(cols, ["Flags Returned"]);
|
||||
}
|
||||
}
|
||||
@@ -441,7 +441,7 @@ fn chat_vm() -> benilla_ui::script::UiScript {
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
// The UIMenu kit is the reference's own file since 1751 window 21, so this reads both
|
||||
// stores through the one loader that speaks them.
|
||||
|
||||
@@ -285,6 +285,15 @@ fn feed_craft(
|
||||
return;
|
||||
}
|
||||
script.set_craft(fresh.clone());
|
||||
// The reagent templates `GetCraftReagentItemLink` reads, pre-asked as the trade-skill feed
|
||||
// pre-asks its own (1973).
|
||||
if let Some(f) = &fresh {
|
||||
script.ask_item_templates(
|
||||
f.recipes
|
||||
.iter()
|
||||
.flat_map(|r| r.reagents.iter().map(|re| re.item)),
|
||||
);
|
||||
}
|
||||
match (&*last, &fresh) {
|
||||
(None, Some(f)) => {
|
||||
debug!("ui_craft: window opens — {} recipe(s)", f.recipes.len());
|
||||
|
||||
@@ -135,18 +135,60 @@ impl AreaSpiritHealer {
|
||||
pub(crate) struct BattlefieldQueue {
|
||||
slots: [Option<BattlefieldStatus>; 3],
|
||||
changed: bool,
|
||||
/// The slot the player is IN (`[0x8457cc]`, the status-3 arm), and the instance's clocks
|
||||
/// (`[0xb6ebb8]`/`[0xb6ebbc]`, §4.2): the run-time stamp `now − Δ₂` and the expiration
|
||||
/// `now + Δ₁`. Any other status for that slot resets both (1972).
|
||||
active: Option<(usize, u32)>,
|
||||
run_started: Option<Instant>,
|
||||
/// The status-3 arm rebuilds the scoreboard and fires `UPDATE_BATTLEFIELD_SCORE` before
|
||||
/// `UPDATE_BATTLEFIELD_STATUS` (§4.2's ordering) — the score feed reads this first.
|
||||
score_dirty: bool,
|
||||
}
|
||||
|
||||
impl BattlefieldQueue {
|
||||
/// `SMSG_BATTLEFIELD_STATUS`: an out-of-range slot aborts the handler; a zero map clears.
|
||||
pub(crate) fn apply(&mut self, status: BattlefieldStatus) {
|
||||
let Some(slot) = self.slots.get_mut(status.slot as usize) else {
|
||||
let index = status.slot as usize;
|
||||
let Some(slot) = self.slots.get_mut(index) else {
|
||||
return;
|
||||
};
|
||||
match status.in_progress {
|
||||
Some((_, elapsed_ms)) if status.map_id != 0 => {
|
||||
self.active = Some((index, status.map_id));
|
||||
self.run_started =
|
||||
Some(Instant::now() - std::time::Duration::from_millis(u64::from(elapsed_ms)));
|
||||
self.score_dirty = true;
|
||||
}
|
||||
_ => {
|
||||
if self.active.is_some_and(|(i, _)| i == index) {
|
||||
self.active = None;
|
||||
self.run_started = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
*slot = (status.map_id != 0).then_some(status);
|
||||
self.changed = true;
|
||||
}
|
||||
|
||||
/// The map of the battleground the player is in — `LeaveBattlefield`'s payload; `None` = 0.
|
||||
pub(crate) fn active_map(&self) -> Option<u32> {
|
||||
self.active.map(|(_, map)| map)
|
||||
}
|
||||
|
||||
/// `GetBattlefieldInstanceRunTime()`: ms since the status-3 stamp, 0 with none.
|
||||
pub(crate) fn run_time_ms(&self, now: Instant) -> u32 {
|
||||
self.run_started.map_or(0, |t| {
|
||||
now.saturating_duration_since(t)
|
||||
.as_millis()
|
||||
.min(u128::from(u32::MAX)) as u32
|
||||
})
|
||||
}
|
||||
|
||||
/// The status-3 arm's scoreboard rebuild, once per arrival.
|
||||
pub(crate) fn take_score_dirty(&mut self) -> bool {
|
||||
std::mem::take(&mut self.score_dirty)
|
||||
}
|
||||
|
||||
/// The map id `AcceptBattlefieldPort` sends for a 1-based slot, if the slot holds a queue.
|
||||
fn map_id(&self, index: u8) -> Option<u32> {
|
||||
self.slots
|
||||
@@ -173,7 +215,7 @@ impl MeetingStone {
|
||||
}
|
||||
}
|
||||
|
||||
fn feed_dialog_verbs(
|
||||
pub(crate) fn feed_dialog_verbs(
|
||||
script: Option<NonSendMut<UiScript>>,
|
||||
mut pet: ResMut<PetUnlearnState>,
|
||||
mut boot: ResMut<InstanceBoot>,
|
||||
@@ -416,6 +458,7 @@ mod tests {
|
||||
status: 2,
|
||||
time_ms: Some(0),
|
||||
in_progress: None,
|
||||
queued: None,
|
||||
};
|
||||
q.apply(status(1, 489));
|
||||
q.apply(status(5, 30));
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
//! The app-side **dressing-room feed** (decision 1060): the bridge between the window's intents
|
||||
//! (`BenillaDressUpModel_Dress/TryOn/Close`, [`DressUpIntent`]) and the booth's look
|
||||
//! ([`DressUpPreview`]).
|
||||
//! The app-side **dressing-room feed** (decisions 1060, 1969): the bridge between the stock
|
||||
//! window's `DressUpModel` widget verbs ([`DressUpIntent`], queued engine-side) and the booth's
|
||||
//! look ([`DressUpPreview`]).
|
||||
//!
|
||||
//! Three jobs, each frame, before the VM ticks ([`UiInput`]):
|
||||
//!
|
||||
//! - **Apply the intents, in order.** `Dress` drops every substitution (the ref's
|
||||
//! `SetUnit("player")` on open and `Dress()` on Reset); `TryOn(id)` records one; `Close` empties
|
||||
//! the room. Order matters — `DressUpItem` resets *then* tries on in the same breath.
|
||||
//! `SetUnit("player")` on open and `Dress()` on Reset); `Undress` strips every worn piece but
|
||||
//! the hands; `TryOn(id)` records one; `Close` — derived here from the window going invisible,
|
||||
//! the stock file having no hook of ours — empties the room. Order matters — `DressUpItem`
|
||||
//! resets *then* tries on in the same breath.
|
||||
//! - **Resolve each tried-on item to a display.** An item id is all a `|Hitem:` link carries, so the
|
||||
//! display id / inventory type come from the ask-once template cache
|
||||
//! ([`Items::template`] — `CMSG_ITEM_QUERY_SINGLE` on a miss, exactly as the reference's
|
||||
@@ -139,6 +141,10 @@ fn lanes_coexist(main: u8, off: u8) -> bool {
|
||||
pub(crate) struct DressUpRoom {
|
||||
/// Open? `false` = the window is closed and the booth stays empty (the `Close` intent).
|
||||
open: bool,
|
||||
/// `Undress()` has stripped the body: the player's own worn pieces are not part of the look
|
||||
/// until the next `Dress`. The hands are untouched — the widget clears components bodyslots
|
||||
/// `0..0xb` and no hand lane (wow-re `dressup-model-equipment.md` §5).
|
||||
bare: bool,
|
||||
/// Resolved substitutions by equipment slot — `(display id, inventory type)`, the same pair the
|
||||
/// enum-shaped array carries.
|
||||
worn: [Option<CharEnumItem>; 19],
|
||||
@@ -159,16 +165,29 @@ impl DressUpRoom {
|
||||
match intent {
|
||||
DressUpIntent::Dress => {
|
||||
self.open = true;
|
||||
self.bare = false;
|
||||
self.worn = Default::default();
|
||||
self.held_order.clear();
|
||||
self.pending.clear();
|
||||
}
|
||||
DressUpIntent::Undress => {
|
||||
self.open = true;
|
||||
self.bare = true;
|
||||
// Every worn piece off — base (`bare`, read by `player_look`) and tried-on alike —
|
||||
// while a held substitution, like a held base item, stays in its hand.
|
||||
for slot in 0..self.worn.len() {
|
||||
if !HELD_SLOTS.contains(&slot) {
|
||||
self.worn[slot] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
DressUpIntent::TryOn(item) => {
|
||||
self.open = true;
|
||||
self.pending.push(item);
|
||||
}
|
||||
DressUpIntent::Close => {
|
||||
self.open = false;
|
||||
self.bare = false;
|
||||
self.worn = Default::default();
|
||||
self.held_order.clear();
|
||||
self.pending.clear();
|
||||
@@ -231,8 +250,23 @@ fn feed_dressup(
|
||||
for intent in script.take_dressup_intents() {
|
||||
room.apply(intent);
|
||||
}
|
||||
// The pane's rotate buttons own the yaw; the booth mirrors it (the paper doll's own law, 0208 §5).
|
||||
preview.yaw = script.dressup_yaw();
|
||||
// The window went away: the room empties and the booth stops baking. Read off the frame
|
||||
// itself — the file is the reference's and has no hook of ours in its OnHide (1969); the
|
||||
// reference's widget keeps its state while hidden, but its next `DressUpItem` re-issues
|
||||
// `SetUnit("player")` precisely because the frame was not visible, so nothing kept is ever
|
||||
// observable.
|
||||
// Two windows drive the one room since 1971: the dressing room and the auction house's
|
||||
// embedded dress-up pane (`AuctionDressUpFrame`); the booth empties when neither is up.
|
||||
if room.open
|
||||
&& !script.frame_visible("DressUpFrame")
|
||||
&& !script.frame_visible("AuctionDressUpFrame")
|
||||
{
|
||||
room.apply(DressUpIntent::Close);
|
||||
}
|
||||
// The pane's rotate buttons own the yaw through the stock `Model_RotateLeft/Right/OnUpdate`,
|
||||
// which write `DressUpModel:SetRotation`; the booth mirrors the pane (the paper doll's own
|
||||
// law, 0208 §5 / 1751).
|
||||
preview.yaw = script.model_pane_facing("DressUpModel");
|
||||
|
||||
room.resolve_pending(&mut items, &commands);
|
||||
|
||||
@@ -272,6 +306,11 @@ fn player_look(
|
||||
equipment[idx] = worn;
|
||||
continue;
|
||||
}
|
||||
// Undressed: the player's own worn pieces are off; the hands (attachments, not bodyslots)
|
||||
// keep what they hold.
|
||||
if room.bare && !HELD_SLOTS.contains(&idx) {
|
||||
continue;
|
||||
}
|
||||
// …and past that point we are dressing the player's OWN gear, which is where the two
|
||||
// equipment-display preferences apply — and where the reference applies them too, by
|
||||
// construction rather than by a test (decision 1472). `DressUpModel::SetUnit 0x476cb0`
|
||||
@@ -430,6 +469,53 @@ mod tests {
|
||||
assert!(!room.open, "closing empties the room (the booth goes dark)");
|
||||
}
|
||||
|
||||
/// `DressUpModel:Undress()` strips every worn piece — the player's own and a tried-on one —
|
||||
/// and no hand lane (bodyslots `0..0xb` only, wow-re §5); a later try-on lands on the bare
|
||||
/// body, and `Dress()` puts the player's own gear back.
|
||||
#[test]
|
||||
fn undress_strips_the_body_but_not_the_hands_and_a_try_on_lands_on_the_bare_body() {
|
||||
let (cmds, _rx) = commands();
|
||||
let mut items = Items::default();
|
||||
items.insert_template(1000, Some(worn("Worn Chest", 5000, 5)));
|
||||
items.insert_template(2000, Some(worn("Shiny Chest", 7000, 5)));
|
||||
items.insert_template(3000, Some(worn("Sword", 6000, 13)));
|
||||
items.insert_template(4000, Some(worn("Worn Helm", 8000, 1)));
|
||||
let store = player(&[(4, 1000), (15, 3000), (0, 4000)]);
|
||||
|
||||
let mut room = DressUpRoom::default();
|
||||
room.apply(DressUpIntent::Dress);
|
||||
room.apply(DressUpIntent::TryOn(2000));
|
||||
room.resolve_pending(&mut items, &cmds);
|
||||
room.apply(DressUpIntent::Undress);
|
||||
let look = player_look(&store, &net(), &room, &mut items, &cmds).unwrap();
|
||||
assert_eq!(look.equipment[4].display_id, 0, "the tried-on chest is off");
|
||||
assert_eq!(look.equipment[0].display_id, 0, "the worn helm is off");
|
||||
assert_eq!(
|
||||
look.equipment[15].display_id, 6000,
|
||||
"the sword stays in the hand"
|
||||
);
|
||||
|
||||
room.apply(DressUpIntent::TryOn(2000));
|
||||
room.resolve_pending(&mut items, &cmds);
|
||||
let look = player_look(&store, &net(), &room, &mut items, &cmds).unwrap();
|
||||
assert_eq!(
|
||||
look.equipment[4].display_id, 7000,
|
||||
"a try-on lands on the bare body"
|
||||
);
|
||||
assert_eq!(
|
||||
look.equipment[0].display_id, 0,
|
||||
"…which stays bare elsewhere"
|
||||
);
|
||||
|
||||
room.apply(DressUpIntent::Dress);
|
||||
let look = player_look(&store, &net(), &room, &mut items, &cmds).unwrap();
|
||||
assert_eq!(
|
||||
look.equipment[4].display_id, 5000,
|
||||
"Dress puts the player's own chest back"
|
||||
);
|
||||
assert_eq!(look.equipment[0].display_id, 8000);
|
||||
}
|
||||
|
||||
/// An item whose template has not answered yet stays PENDING rather than previewing nothing:
|
||||
/// linked-in-chat items are the normal case here, and the first click on one always misses the
|
||||
/// cache. It lands on the frame the answer does — and the ask goes out exactly once.
|
||||
|
||||
@@ -50,7 +50,7 @@ use std::collections::{HashMap, HashSet};
|
||||
use benilla_protocol::messages::{mail_error, mail_message_type, MailListEntry};
|
||||
use bevy::prelude::*;
|
||||
|
||||
use benilla_ui::script::{MailInboxRow, MailState, UiScript};
|
||||
use benilla_ui::script::{MailInboxRow, MailState, ScriptValue, StationeryView, UiScript};
|
||||
|
||||
use crate::entities::ItemDisplays;
|
||||
use crate::items::Items;
|
||||
@@ -124,6 +124,9 @@ pub(crate) struct MailOpen {
|
||||
last_list_query: Option<f64>,
|
||||
/// SEND-action results the net bridge queued for the feed to fire ([`MailSendAck`]).
|
||||
pub(crate) send_acks: Vec<MailSendAck>,
|
||||
/// 1-based inbox rows a take just emptied and purged (`net::apply::mail`) — the feed fires
|
||||
/// `CLOSE_INBOX_ITEM(index)` for each, ahead of the `MAIL_INBOX_UPDATE` the purge causes.
|
||||
pub(crate) close_inbox: Vec<u32>,
|
||||
/// Refusals (a take/return/delete failure) the feed shows, as message keys — resolved at the
|
||||
/// feed, which is where the VM's `GlobalStrings` can be read
|
||||
/// ([`crate::ui_action::keyed_line`]).
|
||||
@@ -434,6 +437,51 @@ fn resolve_row(
|
||||
}
|
||||
|
||||
/// Build the Lua-facing snapshot from [`MailOpen`] — `None` when no mailbox is open.
|
||||
/// The usable stationery list (wow-re `stationery-bindings.md` §`0x4ad970`, 1970): every
|
||||
/// `Stationery.dbc` row that is always available (`Flags & 1`) or whose item the player carries
|
||||
/// (bags, not the bank — the client's `0x622270`), and whose item template is cached — the name,
|
||||
/// icon and price come off the template — sorted by BuyPrice ascending, the client's comparator
|
||||
/// (`0x4ada90`). A carried paper costs nothing to use (`cost` nil). Templates are asked once here
|
||||
/// so the list fills from the second frame after world enter, mailbox or no mailbox.
|
||||
fn stationeries(
|
||||
catalog: &Stationery,
|
||||
self_q: &Query<(&ObjectStore, &crate::net::Guid), With<SelfPlayer>>,
|
||||
items: &mut Items,
|
||||
icons: Option<&ItemDisplays>,
|
||||
commands: &NetCommands,
|
||||
) -> Vec<StationeryView> {
|
||||
let store = self_q.iter().next().map(|(s, _)| s);
|
||||
let mut out: Vec<(u32, StationeryView)> = Vec::new();
|
||||
for row in catalog.0.rows() {
|
||||
let Some(t) = items.template(row.item, 0, commands).cloned() else {
|
||||
continue;
|
||||
};
|
||||
let carried = store.is_some_and(|s| {
|
||||
crate::ui_items::count_of(
|
||||
&s.0,
|
||||
items,
|
||||
row.item,
|
||||
crate::ui_items::InventoryScope::CARRIED,
|
||||
) > 0
|
||||
});
|
||||
if row.flags & 1 == 0 && !carried {
|
||||
continue;
|
||||
}
|
||||
out.push((
|
||||
t.buy_price,
|
||||
StationeryView {
|
||||
id: row.id,
|
||||
name: t.name.clone(),
|
||||
icon: crate::ui_items::item_icon(icons, t.display_info_id).unwrap_or_default(),
|
||||
cost: (!carried).then_some(t.buy_price),
|
||||
texture: row.texture.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
out.sort_by_key(|(price, v)| (*price, v.id));
|
||||
out.into_iter().map(|(_, v)| v).collect()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)] // mirrors `resolve_row`'s cache set
|
||||
fn snapshot(
|
||||
mail: &MailOpen,
|
||||
@@ -472,6 +520,17 @@ fn snapshot(
|
||||
/// Push the current mail into the VM and fire the show/update/close + send-result events on a
|
||||
/// transition or content change (an async sender/item/body landing, a mark-read flip, a fresh
|
||||
/// list). Diffed against a `Local` memory, exactly like the merchant/gossip feeds.
|
||||
/// The feed's tail, bundled — the signature sits at the 16-SystemParam ceiling: the random-suffix
|
||||
/// roll's catalogs (1547), where a refusal lands (1815), and the usable stationery list's memo
|
||||
/// (1970), pushed on change like the inbox.
|
||||
#[derive(bevy::ecs::system::SystemParam)]
|
||||
struct MailFeedExtras<'w, 's> {
|
||||
props: Option<Res<'w, crate::items::RandomProperties>>,
|
||||
enchants: Option<Res<'w, crate::items::Enchants>>,
|
||||
sink: crate::ui_action::MessageSink<'w>,
|
||||
stationeries: Local<'s, crate::ui_script::VmMemo<Vec<StationeryView>>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn feed_mail(
|
||||
script: Option<NonSendMut<UiScript>>,
|
||||
@@ -490,18 +549,17 @@ fn feed_mail(
|
||||
self_q: Query<(&crate::net::ObjectStore, &crate::net::Guid), With<crate::net::SelfPlayer>>,
|
||||
states: Res<crate::world_state::WorldStates>,
|
||||
// The random-suffix roll's catalogs (1547): an enclosed item's rolled name.
|
||||
// The roll catalogs and the message sink, paired (the 16-SystemParam ceiling this signature
|
||||
// sits at): the random-suffix roll (1547), and where a refusal lands (1815).
|
||||
rolls_and_sink: (
|
||||
Option<Res<crate::items::RandomProperties>>,
|
||||
Option<Res<crate::items::Enchants>>,
|
||||
crate::ui_action::MessageSink,
|
||||
),
|
||||
extras: MailFeedExtras,
|
||||
) {
|
||||
let Some(mut script) = script else {
|
||||
return;
|
||||
};
|
||||
let (props, enchants, mut sink) = rolls_and_sink;
|
||||
let MailFeedExtras {
|
||||
props,
|
||||
enchants,
|
||||
mut sink,
|
||||
stationeries: mut last_stationeries,
|
||||
} = extras;
|
||||
let rolls = crate::items::RollCatalogs {
|
||||
props: props.as_deref(),
|
||||
enchants: enchants.as_deref(),
|
||||
@@ -520,6 +578,12 @@ fn feed_mail(
|
||||
// MAIL_SEND_SUCCESS on OK (resets the form) or the refusal's own line on a failure. The
|
||||
// success also SAYS so — `ERR_MAIL_SENT`, the yellow info line `0x4ad0b1` shows before it
|
||||
// resets the form (decision 1821).
|
||||
// A take emptied and purged a row: `CLOSE_INBOX_ITEM(index)` — the stock handler hides the
|
||||
// open letter when it is that row — ahead of the `MAIL_INBOX_UPDATE` the changed list fires
|
||||
// below, the client's own order (wow-re `stationery-bindings.md` §8, 1970).
|
||||
for index in std::mem::take(&mut mail.close_inbox) {
|
||||
script.fire_event("CLOSE_INBOX_ITEM", vec![ScriptValue::Int(i64::from(index))]);
|
||||
}
|
||||
for ack in std::mem::take(&mut mail.send_acks) {
|
||||
script.fire_event("MAIL_FAILED", vec![]);
|
||||
let key = if ack.ok {
|
||||
@@ -565,7 +629,24 @@ fn feed_mail(
|
||||
if changed {
|
||||
script.set_mail(fresh.clone());
|
||||
}
|
||||
// The send tab's usable stationery (1970): `Stationery.dbc` rows the player may use — the
|
||||
// always-available one, or one whose item they carry — whose item template is cached, priced
|
||||
// by BuyPrice ascending. The templates are asked ahead of any mailbox (the catalog is five
|
||||
// rows) so the list is whole the frame the window opens: the stock `SendMailFrame_Reset`
|
||||
// selects row 1 on show, and an empty list then would leave every send silently unsent.
|
||||
let usable = stationery
|
||||
.as_deref()
|
||||
.map(|catalog| stationeries(catalog, &self_q, &mut items, icons.as_deref(), &commands))
|
||||
.unwrap_or_default();
|
||||
let memo = last_stationeries.get(&script);
|
||||
if *memo != usable {
|
||||
*memo = usable.clone();
|
||||
script.set_mail_stationeries(usable);
|
||||
}
|
||||
if opened {
|
||||
// The selection is cleared on open AND close (`0x4ace07`, 1970); the stock tab's reset
|
||||
// on MAIL_SHOW re-selects row 1.
|
||||
script.clear_stationery();
|
||||
script.fire_event("MAIL_SHOW", vec![]);
|
||||
// The compose tab resets on open — a fresh window carries no stale attachment/money.
|
||||
script.clear_send_mail_item();
|
||||
@@ -573,6 +654,7 @@ fn feed_mail(
|
||||
// MAIL_SEND_SUCCESS on open too, so the page-turn sound on open is faithful, not a bug.
|
||||
script.fire_event("MAIL_SEND_SUCCESS", vec![]);
|
||||
} else if closed {
|
||||
script.clear_stationery();
|
||||
script.fire_event("MAIL_CLOSED", vec![]);
|
||||
// The close core's tail (wow-re `0x4acdad`/`0x4acdb1`, decision 0913): a session where a
|
||||
// mail was read — or one arrived while the window was open — re-asks the server, and the
|
||||
@@ -728,23 +810,28 @@ fn drain_mail(
|
||||
// SendMail: resolve the attachment's (bag, slot) to the wire item guid at send time (the
|
||||
// reference re-reads the slot when the send fires — a lazy resolve, decision 0216/0544).
|
||||
if let Some(req) = script.take_mail_send() {
|
||||
let item_guid = req
|
||||
.item
|
||||
.and_then(|(bag, slot)| {
|
||||
self_q.iter().next().and_then(|s| {
|
||||
crate::ui_items::slot_guid(&s.0, bag, (slot.max(1) - 1) as u8, &items)
|
||||
})
|
||||
let item_guid = req.item.and_then(|(bag, slot)| {
|
||||
self_q.iter().next().and_then(|s| {
|
||||
crate::ui_items::slot_guid(&s.0, bag, (slot.max(1) - 1) as u8, &items)
|
||||
})
|
||||
.unwrap_or(0);
|
||||
let _ = commands.0.send(ClientCommand::SendMail {
|
||||
mailbox,
|
||||
receiver: req.target,
|
||||
subject: req.subject,
|
||||
body: req.body,
|
||||
item_guid,
|
||||
money: req.money,
|
||||
cod: req.cod,
|
||||
});
|
||||
if req.item.is_some() && item_guid.is_none() {
|
||||
// The attached item is gone from its slot: `ERR_ITEM_NOT_FOUND`, no packet, the
|
||||
// attachment dropped and `MAIL_SEND_INFO_UPDATE` fired (`0x4ae98d`, 1970).
|
||||
mail.errors.push("ERR_ITEM_NOT_FOUND");
|
||||
script.drop_send_mail_item();
|
||||
} else {
|
||||
let _ = commands.0.send(ClientCommand::SendMail {
|
||||
mailbox,
|
||||
receiver: req.target,
|
||||
subject: req.subject,
|
||||
body: req.body,
|
||||
stationery: req.stationery,
|
||||
item_guid: item_guid.unwrap_or(0),
|
||||
money: req.money,
|
||||
cod: req.cod,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if script.take_mail_close() {
|
||||
|
||||
@@ -24,7 +24,7 @@ fn shipped_action_bar_drives_end_to_end() {
|
||||
super::test_ui::load_ui(&s, "UIParent.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
super::test_ui::load_ui(&s, "GameTooltip.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
let frames = super::test_ui::load_ui(&s, "Interface\\FrameXML\\MainMenuBar.xml")
|
||||
+ super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionBarFrame.xml")
|
||||
@@ -190,7 +190,7 @@ fn load_action_bar(s: &UiScript) {
|
||||
super::test_ui::load_ui(s, "Interface\\FrameXML\\MainMenuBar.xml");
|
||||
super::test_ui::load_ui(s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
super::test_ui::load_ui(s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
super::test_ui::load_ui(s, "GameTooltip.xml");
|
||||
super::test_ui::load_ui(s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
super::test_ui::load_ui(s, "Interface\\FrameXML\\ActionBarFrame.xml");
|
||||
super::test_ui::load_ui(s, "Interface\\FrameXML\\BonusActionBarFrame.xml");
|
||||
|
||||
@@ -1359,7 +1359,7 @@ fn the_main_bar_pages_and_a_bonus_page_still_outranks_it() {
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
@@ -1528,7 +1528,7 @@ fn bonus_bar_slides_up_with_sound_and_down_without() {
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\MainMenuBar.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
super::test_ui::load_ui(&s, "GameTooltip.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionBarFrame.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\BonusActionBarFrame.xml");
|
||||
|
||||
@@ -1750,7 +1750,7 @@ fn bonus_bar_turnaround_continues_from_position() {
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\MainMenuBar.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
super::test_ui::load_ui(&s, "GameTooltip.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionBarFrame.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\BonusActionBarFrame.xml");
|
||||
|
||||
@@ -1832,7 +1832,7 @@ fn the_page_arrows_do_not_steal_each_other_s_clicks() {
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\MainMenuBar.xml");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
super::test_ui::load_ui(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
super::test_ui::load_ui(&s, "GameTooltip.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionBarFrame.xml");
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\BonusActionBarFrame.xml");
|
||||
|
||||
|
||||
@@ -390,16 +390,13 @@ const BLIZZARD_ADDONS: [&str; 12] = [
|
||||
];
|
||||
|
||||
/// The Blizzard addons the chain carries as LoadOnDemand registry rows — the ones the reference
|
||||
/// reaches through `LoadAddOn` from `UIParentLoadAddOn` (1957). Two exclusions, both by shape:
|
||||
/// an addon `benilla.toc` lists with everything else (the eager trio the InspectUI header
|
||||
/// describes) already loaded and is not a row here; an addon whose toc is not LoadOnDemand
|
||||
/// would need the startup walk to read the chain, which it does not yet — that one stays with
|
||||
/// its own file until its window migrates.
|
||||
/// reaches through `LoadAddOn` from `UIParentLoadAddOn` (1957). `benilla.toc` lists none of them,
|
||||
/// like the reference's own `FrameXML.toc` (1967). One exclusion, by shape: an addon whose toc is
|
||||
/// not LoadOnDemand would need the startup walk to read the chain, which it does not yet — that
|
||||
/// one stays with its own file until its window migrates.
|
||||
fn chain_addons() -> Vec<Addon> {
|
||||
let eager = super::manifest::chain_addons(&Addon::builtin().toc.files);
|
||||
BLIZZARD_ADDONS
|
||||
.iter()
|
||||
.filter(|name| !eager.iter().any(|e| e.eq_ignore_ascii_case(name)))
|
||||
.filter_map(|name| {
|
||||
let bytes = super::reference_ui::read(&format!("Interface/AddOns/{name}/{name}.toc"))?;
|
||||
let toc = Toc::parse(&benilla_ui::source::decode(&bytes));
|
||||
@@ -982,8 +979,9 @@ impl Walk {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
/// The chain's Blizzard LoadOnDemand addons are registry rows (1957): every row is
|
||||
/// LoadOnDemand and read off the chain, `Blizzard_TrainerUI` is among them, and an addon
|
||||
/// the manifest lists eagerly is not a row twice.
|
||||
/// LoadOnDemand and read off the chain, the eight windows the interface opens through them
|
||||
/// are among the rows, and the manifest lists no addon at all — the reference's own
|
||||
/// `FrameXML.toc` has none, every Blizzard addon loads on demand (1967).
|
||||
#[test]
|
||||
fn the_chain_carries_blizzards_load_on_demand_addons_as_registry_rows() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
@@ -1006,13 +1004,29 @@ mod tests {
|
||||
a.name
|
||||
);
|
||||
}
|
||||
let eager = super::super::manifest::chain_addons(&Addon::builtin().toc.files);
|
||||
for e in &eager {
|
||||
for name in [
|
||||
"Blizzard_AuctionUI",
|
||||
"Blizzard_CraftUI",
|
||||
"Blizzard_InspectUI",
|
||||
"Blizzard_MacroUI",
|
||||
"Blizzard_RaidUI",
|
||||
"Blizzard_TalentUI",
|
||||
"Blizzard_TradeSkillUI",
|
||||
"Blizzard_TrainerUI",
|
||||
] {
|
||||
assert!(
|
||||
!rows.iter().any(|a| a.name.eq_ignore_ascii_case(e)),
|
||||
"{e} is listed in benilla.toc and must not also be a row"
|
||||
rows.iter().any(|a| a.name == name),
|
||||
"{name} is a row the interface opens on demand"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
!Addon::builtin()
|
||||
.toc
|
||||
.files
|
||||
.iter()
|
||||
.any(|f| f.replace('/', "\\").starts_with("Interface\\AddOns\\")),
|
||||
"benilla.toc lists a Blizzard addon eagerly; the reference loads every one on demand"
|
||||
);
|
||||
// The glue's list is the player's folder alone.
|
||||
assert!(installed(None)
|
||||
.iter()
|
||||
|
||||
@@ -405,6 +405,10 @@ fn bag_tooltip_hangs_left_when_the_slot_sits_in_the_right_half() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
// The stock tooltip declares no size: it sizes from its lines through the font engine, as
|
||||
// the client's does (1968) — a harness that reads its rect needs one; the fixed-width
|
||||
// font is that engine here.
|
||||
s.set_text_measurer(Box::new(super::FixedWidthFont(6.0)));
|
||||
for file in BAG_UI {
|
||||
load_xml(&s, file);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ fn shipped_xml() -> Vec<(String, String)> {
|
||||
out.sort();
|
||||
// A sanity floor for the walk, not a census: `assets/ui` retires file by file (1751), so
|
||||
// the floor sits well under the count rather than one step above it (1956).
|
||||
assert!(out.len() >= 20, "only {} xml files swept", out.len());
|
||||
assert!(out.len() >= 15, "only {} xml files swept", out.len());
|
||||
out
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ fn harness() -> UiScript {
|
||||
// does not, so a session without the tooltip loses the whole repaint.
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
// `SecondsToTimeAbbrev`, which 1.12 keeps in UIParent.lua and so do we since window 18.
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
|
||||
@@ -394,7 +394,7 @@ fn managed_positions_track_the_bottom_bar_stack() {
|
||||
load_xml(&s, "Interface\\FrameXML\\ChatFrame.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml"); // the dropdown kit's MenuBackdrop reads TOOLTIP_DEFAULT_COLOR
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); // the dropdown kit's MenuBackdrop reads TOOLTIP_DEFAULT_COLOR
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml");
|
||||
|
||||
@@ -160,6 +160,10 @@ fn shipped_character_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);
|
||||
// The stock tooltip declares no size: it sizes from its lines through the font engine, as
|
||||
// the client's does (1968) — a harness that reads its rect needs one; the fixed-width
|
||||
// font is that engine here.
|
||||
s.set_text_measurer(Box::new(super::FixedWidthFont(6.0)));
|
||||
for f in super::test_ui::CHARACTER_UI {
|
||||
super::test_ui::load_ui_strict(&s, f);
|
||||
}
|
||||
@@ -1207,7 +1211,7 @@ fn an_addons_tab_click_selects_through_the_generic_entry_point() {
|
||||
// The reference's `PanelTemplates_SelectTab` ends with `if GameTooltip:IsOwned(tab)` —
|
||||
// an arm our deleted copy omitted ("our tabs set no tooltip"), so selecting a tab now needs
|
||||
// the tooltip to exist (1860).
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
|
||||
|
||||
@@ -44,7 +44,7 @@ fn chat_with_menu() -> UiScript {
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"ScrollTemplates.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -22,7 +22,7 @@ fn chat_ui() -> UiScript {
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"ScrollTemplates.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -21,7 +21,7 @@ fn chat_frame() -> UiScript {
|
||||
// the dropdown kit's own dependency — its MenuBackdrop reads `TOOLTIP_DEFAULT_COLOR`.)
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit the chat menus build from
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
@@ -1002,7 +1002,7 @@ fn the_chat_menu_builds_its_rows_on_the_references_kit() {
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
r"Interface\FrameXML\UIMenu.xml",
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
|
||||
@@ -22,7 +22,7 @@ fn picker() -> UiScript {
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"ScrollTemplates.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -22,7 +22,7 @@ fn load_combo_frame() -> UiScript {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UnitPopup.xml");
|
||||
|
||||
@@ -68,7 +68,7 @@ fn setup() -> UiScript {
|
||||
// The stock DELETE_GOOD_ITEM's OnHide hands focus back to `ChatFrameEditBox` (1960).
|
||||
load_xml(&s, r"Interface\FrameXML\UIMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\ChatFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml"); // TOOLTIP_DEFAULT_COLOR, read by the dropdown backdrop
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); // TOOLTIP_DEFAULT_COLOR, read by the dropdown backdrop
|
||||
load_xml(&s, r"Interface\FrameXML\UIDropDownMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\FloatingChatFrame.xml"); // declares ChatFrameEditBox
|
||||
|
||||
@@ -40,13 +40,15 @@ const ROOM_UI: &[&str] = &[
|
||||
"Interface\\FrameXML\\ItemRef.xml",
|
||||
"Interface\\FrameXML\\MerchantFrame.xml",
|
||||
"Interface\\FrameXML\\StackSplitFrame.xml",
|
||||
"DressUpFrame.xml",
|
||||
"Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\ChatFrame.xml",
|
||||
"Interface\\FrameXML\\UIPanelTemplates.lua",
|
||||
"Interface\\FrameXML\\UIPanelTemplates.xml",
|
||||
// The reference's own room (1969): its Close/Reset buttons inherit the panel kit's templates,
|
||||
// which resolve at load — so after it, as the manifest has it.
|
||||
"Interface\\FrameXML\\DressUpFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\FloatingChatFrame.xml",
|
||||
@@ -69,7 +71,7 @@ fn load_room(s: &UiScript) {
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Cooldown.xml",
|
||||
] {
|
||||
load_xml(s, file);
|
||||
@@ -114,7 +116,6 @@ fn shown_paper_doll() -> UiScript {
|
||||
super::test_ui::load_ui_strict(&s, file);
|
||||
}
|
||||
for file in [
|
||||
"DressUpFrame.xml",
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\UIMenu.xml",
|
||||
@@ -122,6 +123,8 @@ fn shown_paper_doll() -> UiScript {
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"Interface\\FrameXML\\UIPanelTemplates.lua",
|
||||
"Interface\\FrameXML\\UIPanelTemplates.xml",
|
||||
// The reference's room (1969), after the panel kit its buttons inherit from.
|
||||
"Interface\\FrameXML\\DressUpFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\FloatingChatFrame.xml",
|
||||
@@ -416,9 +419,9 @@ fn reset_re_dresses_close_empties_and_the_arrows_spin_the_pane() {
|
||||
let _ = s.take_dressup_intents();
|
||||
s.resolve();
|
||||
|
||||
// The pane's OnLoad seeded the ref's default facing.
|
||||
// The pane's OnLoad (`Model_OnLoad`) seeded the ref's default facing on the widget itself.
|
||||
assert!(
|
||||
(s.dressup_yaw() - 0.61).abs() < 1e-6,
|
||||
(s.model_pane_facing("DressUpModel") - 0.61).abs() < 1e-6,
|
||||
"ref UIParent.lua:1422"
|
||||
);
|
||||
|
||||
@@ -431,29 +434,28 @@ fn reset_re_dresses_close_empties_and_the_arrows_spin_the_pane() {
|
||||
);
|
||||
|
||||
// One tap of rotate-left: OnClick on press AND release, −0.03 each.
|
||||
let before = s.dressup_yaw();
|
||||
let before = s.model_pane_facing("DressUpModel");
|
||||
let (x, y) = s
|
||||
.eval::<(f32, f32)>(
|
||||
"return (DressUpModelFrameRotateLeftButton:GetLeft() \
|
||||
+ DressUpModelFrameRotateLeftButton:GetRight()) / 2, \
|
||||
(DressUpModelFrameRotateLeftButton:GetTop() \
|
||||
+ DressUpModelFrameRotateLeftButton:GetBottom()) / 2",
|
||||
"return (DressUpModelRotateLeftButton:GetLeft() \
|
||||
+ DressUpModelRotateLeftButton:GetRight()) / 2, \
|
||||
(DressUpModelRotateLeftButton:GetTop() \
|
||||
+ DressUpModelRotateLeftButton:GetBottom()) / 2",
|
||||
)
|
||||
.unwrap();
|
||||
s.mouse_button(x, y, "LeftButton", true);
|
||||
s.mouse_button(x, y, "LeftButton", false);
|
||||
assert!(
|
||||
(s.dressup_yaw() - (before - 0.06)).abs() < 1e-5,
|
||||
(s.model_pane_facing("DressUpModel") - (before - 0.06)).abs() < 1e-5,
|
||||
"a tap fires OnClick twice: {} → {}",
|
||||
before,
|
||||
s.dressup_yaw()
|
||||
s.model_pane_facing("DressUpModel")
|
||||
);
|
||||
|
||||
s.run("HideUIPanel(DressUpFrame)").unwrap();
|
||||
assert_eq!(
|
||||
s.take_dressup_intents(),
|
||||
vec![DressUpIntent::Close],
|
||||
"closing the window empties the booth"
|
||||
);
|
||||
// Closing the window is what empties the booth, and the app reads it off the frame — the
|
||||
// stock file's OnHide plays its sound and queues nothing (1969).
|
||||
assert!(!s.frame_visible("DressUpFrame"), "the room is hidden");
|
||||
assert!(s.take_dressup_intents().is_empty());
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ fn load_dropdown_kit(s: &UiScript) {
|
||||
"UIParent.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
] {
|
||||
load_xml(s, file);
|
||||
|
||||
@@ -26,7 +26,7 @@ fn harness() -> UiScript {
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"MinimapCluster.xml",
|
||||
// The reference's own file. This module carried a private disk-only `load_xml` until the
|
||||
// swap, which structurally could not name a chain entry — [`super::test_ui::load_ui`] is
|
||||
@@ -403,7 +403,7 @@ fn the_quest_tracker_stacks_below_the_durability_guy() {
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"MinimapCluster.xml",
|
||||
"ScrollTemplates.xml",
|
||||
"Interface\\FrameXML\\DurabilityFrame.xml",
|
||||
|
||||
@@ -114,7 +114,7 @@ fn an_error_toast_draws_over_an_open_panel_window() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
|
||||
@@ -200,7 +200,7 @@ fn escape_closes_the_options_window_before_opening_the_menu() {
|
||||
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // the Keybindings page's faux-scroll kit (1008)
|
||||
load_xml(&s, "KeyBindingsPage.xml");
|
||||
|
||||
@@ -19,7 +19,7 @@ fn exp_bar_harness() -> UiScript {
|
||||
"UIParent.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"Cooldown.xml",
|
||||
@@ -39,6 +39,10 @@ fn exp_bar_harness() -> UiScript {
|
||||
] {
|
||||
super::test_ui::load_ui(&s, file);
|
||||
}
|
||||
// 1.12 ships detailed tips ON — `SHOW_NEWBIE_TIPS = "1"` is UIOptionsFrame_Init's (ref
|
||||
// UIOptionsFrame.lua l.100; ours sits in OptionsFrame.xml's uvar block, 1968), and a harness
|
||||
// without the options file says so itself, the way the reference's tooltip would read it.
|
||||
s.run("SHOW_NEWBIE_TIPS = \"1\"").unwrap();
|
||||
s
|
||||
}
|
||||
|
||||
|
||||
@@ -185,13 +185,6 @@ const KNOWN: &[Known] = &[
|
||||
// the reference inherits `TextStatusBar` — true of our transcription, and no longer true of
|
||||
// anything. `TargetFrame.xml` is the reference's own now and its ToT bars inherit
|
||||
// `TextStatusBar` like every other unit bar. The gate found them itself, which is its job.
|
||||
Known {
|
||||
frame: "WorldStateAlwaysUpFrame",
|
||||
flag: Flag::Mouse,
|
||||
why:
|
||||
"the reference's PvP objective banner has OnEnter/OnLeave for its tooltip. Wants that \
|
||||
handler, not the flag.",
|
||||
},
|
||||
// ── We take the mouse where the reference does not ─────────────────────────────────────────
|
||||
//
|
||||
// The merchant rows' divergence RETIRED (1751): our `MerchantFrame.xml` is gone and the
|
||||
@@ -232,24 +225,13 @@ const KNOWN: &[Known] = &[
|
||||
// frame it was built to report (1751 §5: the drift instruments retire with the copies).
|
||||
// ── parent ─────────────────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Three, and each is a seat inside the SAME tree the reference seats it in — which is the
|
||||
// question this flag exists to ask (decision 1757). A frame whose seat crosses the boundary
|
||||
// between UIParent's tree and the top level is a defect, because `SetFullScreenFrame` hides
|
||||
// `UIParent` and everything below it; a frame seated one rung along inside that tree is not.
|
||||
Known {
|
||||
frame: "SendMailBodyEditBox",
|
||||
flag: Flag::Parent,
|
||||
why: "the reference interposes SendMailScrollChildFrame between the pane and its content; \
|
||||
our mail panes are the render approximation MailFrame.xml names at the site (flat \
|
||||
art, no live scrollbar), so the body hangs off SendMailScrollFrame directly. Wants \
|
||||
the real scroll child, not a re-seat.",
|
||||
},
|
||||
Known {
|
||||
frame: "OpenMailInvoiceFrame",
|
||||
flag: Flag::Parent,
|
||||
why: "as SendMailBodyEditBox — OpenMailScrollChildFrame is the scroll child we do not \
|
||||
build",
|
||||
},
|
||||
// Empty since 1970. The rows that lived here were seats inside the SAME tree the reference
|
||||
// seats them in — the question this flag exists to ask (decision 1757): a frame whose seat
|
||||
// crosses the boundary between UIParent's tree and the top level is a defect, because
|
||||
// `SetFullScreenFrame` hides `UIParent` and everything below it; a frame seated one rung
|
||||
// along inside that tree is not. The last two were our mail transcription's flat body panes,
|
||||
// which hung their content off the scroll frame with no scroll child between; the mail
|
||||
// window is the reference's own now, scroll children and all.
|
||||
];
|
||||
|
||||
/// The extracted reference FrameXML directory, or `None` when the install isn't there.
|
||||
@@ -593,8 +575,11 @@ fn the_shipped_frames_carry_the_references_flags() {
|
||||
// 1948 retired our ChatFrame.xml, 1952 our SpellBookFrame.xml, 1953 our PetActionBar.xml,
|
||||
// 1956 our SkillFrame.xml, 1958 our UnitPopup.xml and 1959 our FriendsFrame.xml (their
|
||||
// frames are the reference's own now), which took the paired count from the low 400s to
|
||||
// the high 150s; the floor guards the pairing, not the census.
|
||||
compared > 120,
|
||||
// the high 150s; 1966 (TradeFrame), 1968 (GameTooltip), 1969 (DressUpFrame) and 1970
|
||||
// (MailFrame) to the high 80s. The floor guards the pairing, not the census — it comes
|
||||
// down with every window that migrates, and reaches zero with the last file of ours
|
||||
// that declares a reference-named frame.
|
||||
compared > 60,
|
||||
"only {compared} frames compared — the pairing broke, and the sweep guards nothing"
|
||||
);
|
||||
assert!(
|
||||
|
||||
@@ -9,15 +9,11 @@
|
||||
|
||||
use benilla_ui::script::{FriendInfo, SocialRequest, SocialState, UiScript, WhoInfo};
|
||||
|
||||
use super::test_ui::load_ui_strict as load_xml;
|
||||
|
||||
/// The window's own manifest slice, in `load_default_ui` order.
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for f in super::test_ui::SOCIAL_UI {
|
||||
load_xml(&s, f);
|
||||
}
|
||||
super::test_ui::load_social_ui(&mut s);
|
||||
s
|
||||
}
|
||||
|
||||
|
||||
@@ -563,7 +563,7 @@ fn escape_during_a_countdown_cancels_it_and_does_not_open_the_menu() {
|
||||
fn the_world_map_cannot_open_behind_the_menu_and_gives_its_slot_back() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let s = harness_with(&[
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml", // the map's continent/zone pickers initialize into it at OnLoad
|
||||
"ScrollTemplates.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
@@ -614,7 +614,7 @@ fn the_world_map_cannot_open_behind_the_menu_and_gives_its_slot_back() {
|
||||
fn nothing_opens_behind_the_world_map_and_escape_closes_it_first() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let s = harness_with(&[
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"ScrollTemplates.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
@@ -680,7 +680,7 @@ fn the_bag_row_greys_under_the_menu_without_any_of_it_disappearing() {
|
||||
"UIParent.xml",
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
],
|
||||
@@ -809,7 +809,7 @@ fn backpack() -> ContainerState {
|
||||
fn the_menu_rides_the_shared_era_window_scale() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = harness_with(&[
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"ScrollTemplates.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -82,7 +82,7 @@ fn setup() -> UiScript {
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
||||
load_xml(&s, "GameTooltip.xml"); // PASS/NEED/GREED + item hovers
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); // PASS/NEED/GREED + item hovers
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua");
|
||||
@@ -563,7 +563,7 @@ fn managed_positions_engage_for_the_bare_frame_name() {
|
||||
// `TOOLTIP_DEFAULT_COLOR`, which the chain's dropdown backdrops read in their OnLoad — the
|
||||
// dropdown kit rides in with the loot window now (1838), so this bespoke setup needs it too.
|
||||
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_group_loot(&s);
|
||||
|
||||
let bottom = |s: &UiScript| s.eval::<f64>("return GroupLootFrame1:GetBottom()").unwrap();
|
||||
@@ -618,7 +618,7 @@ fn ctrl_and_shift_on_the_roll_icon_preview_and_post_its_link() {
|
||||
let mut s = setup();
|
||||
load_group_loot(&s);
|
||||
load_xml(&s, "UIParent.xml"); // BenillaChatEdit_InsertLink, the shared shift-insert helper
|
||||
load_xml(&s, "DressUpFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\DressUpFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit the chat menus build from
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
|
||||
use benilla_ui::script::{GuildState, ScriptValue, UiScript, UnitState};
|
||||
|
||||
use super::test_ui::load_ui_strict as load_xml;
|
||||
|
||||
/// The guild engine API, stood in for in Lua (see the module header).
|
||||
///
|
||||
/// One mutable table, `BenillaGuildFixture`, is the whole model; every getter reads it and every
|
||||
@@ -203,9 +201,7 @@ fn setup() -> UiScript {
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
for f in super::test_ui::SOCIAL_UI {
|
||||
load_xml(&s, f);
|
||||
}
|
||||
super::test_ui::load_social_ui(&mut s);
|
||||
s
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ fn setup() -> UiScript {
|
||||
"ScrollTemplates.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
// Before UiPanels.xml: the shared StaticPopup carries a `SmallMoneyFrameTemplate` coin
|
||||
// row, whose OnLoad calls `SmallMoneyFrame_OnLoad` — the TOC's own order (1580's
|
||||
// talent-wipe fixture hit this first).
|
||||
|
||||
@@ -379,18 +379,20 @@ fn shown_inspect_honor_page() -> UiScript {
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
// `InspectUnit`'s home since 1832.
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
// Before the inspect addon — its honor page inherits this file's row templates and calls
|
||||
// its two shared painters, and `inherits=` resolves at load (the manifest's own order).
|
||||
// The stock inspect slot buttons inherit this (1832).
|
||||
"Interface\\FrameXML\\ItemButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\HonorFrame.xml",
|
||||
"Interface\\AddOns\\Blizzard_InspectUI\\Blizzard_InspectUI.xml",
|
||||
"Interface\\AddOns\\Blizzard_InspectUI\\InspectPaperDollFrame.xml",
|
||||
"Interface\\AddOns\\Blizzard_InspectUI\\InspectHonorFrame.xml",
|
||||
] {
|
||||
load_xml(&s, file);
|
||||
}
|
||||
// The window is a LoadOnDemand addon, reached the way the app reaches it: seated off the
|
||||
// chain as a registry row (1957) and loaded by the reference's own `InspectFrame_LoadUI`
|
||||
// (UIParent.xml; 1967).
|
||||
super::test_ui::seat_chain_addon(&mut s, "Blizzard_InspectUI");
|
||||
s.run("InspectFrame_LoadUI()").unwrap();
|
||||
s.set_unit("target", Some(alliance_player()));
|
||||
// The PLAYER snapshot is required even though this page is about somebody else, and the reason
|
||||
// is the reference's own asymmetry: `BenillaHonorPane_SetRank` passes the inspected token for
|
||||
|
||||
@@ -127,7 +127,7 @@ fn armed() -> UiScript {
|
||||
// `InspectUnit` lives here now — the reference's own home for that name, and no longer in the
|
||||
// window's own file (decision 1832). The manifest's order, and 49 other harnesses'.
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
// Before InspectFrame.xml, and required rather than tidy: this window's honor page inherits
|
||||
// HonorFrame.xml's five row templates and `inherits=` resolves at LOAD, so without it the
|
||||
// twelve honor rows materialize bare (decision 1512; the manifest states the same order).
|
||||
@@ -137,18 +137,11 @@ fn armed() -> UiScript {
|
||||
// this entry far above the inspect window all along (decision 1832).
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\HonorFrame.xml");
|
||||
load_xml(
|
||||
&s,
|
||||
"Interface\\AddOns\\Blizzard_InspectUI\\Blizzard_InspectUI.xml",
|
||||
);
|
||||
load_xml(
|
||||
&s,
|
||||
"Interface\\AddOns\\Blizzard_InspectUI\\InspectPaperDollFrame.xml",
|
||||
);
|
||||
load_xml(
|
||||
&s,
|
||||
"Interface\\AddOns\\Blizzard_InspectUI\\InspectHonorFrame.xml",
|
||||
);
|
||||
// The window is a LoadOnDemand addon, reached the way the app reaches it: seated off the
|
||||
// chain as a registry row (1957) and loaded by the reference's own `InspectFrame_LoadUI`
|
||||
// (UIParent.xml; 1967).
|
||||
super::test_ui::seat_chain_addon(&mut s, "Blizzard_InspectUI");
|
||||
s.run("InspectFrame_LoadUI()").unwrap();
|
||||
s.set_unit("target", Some(target_unit()));
|
||||
s.set_inspect(Some(inspect_view("target")));
|
||||
// 4 yards away (d² = 16) — comfortably inside the verified 100.0.
|
||||
@@ -160,7 +153,7 @@ fn armed() -> UiScript {
|
||||
#[test]
|
||||
fn shipped_inspect_frame_loads_clean() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let s = UiScript::new().unwrap();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
// `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");
|
||||
@@ -177,7 +170,7 @@ fn shipped_inspect_frame_loads_clean() {
|
||||
// `InspectUnit` lives here now — the reference's own home for that name, and no longer in the
|
||||
// window's own file (decision 1832). The manifest's order, and 49 other harnesses'.
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
// Before InspectFrame.xml, and required rather than tidy: this window's honor page inherits
|
||||
// HonorFrame.xml's five row templates and `inherits=` resolves at LOAD, so without it the
|
||||
// twelve honor rows materialize bare (decision 1512; the manifest states the same order).
|
||||
@@ -187,18 +180,11 @@ fn shipped_inspect_frame_loads_clean() {
|
||||
// this entry far above the inspect window all along (decision 1832).
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\HonorFrame.xml");
|
||||
load_xml(
|
||||
&s,
|
||||
"Interface\\AddOns\\Blizzard_InspectUI\\Blizzard_InspectUI.xml",
|
||||
);
|
||||
load_xml(
|
||||
&s,
|
||||
"Interface\\AddOns\\Blizzard_InspectUI\\InspectPaperDollFrame.xml",
|
||||
);
|
||||
load_xml(
|
||||
&s,
|
||||
"Interface\\AddOns\\Blizzard_InspectUI\\InspectHonorFrame.xml",
|
||||
);
|
||||
// The window is a LoadOnDemand addon, reached the way the app reaches it: seated off the
|
||||
// chain as a registry row (1957) and loaded by the reference's own `InspectFrame_LoadUI`
|
||||
// (UIParent.xml; 1967).
|
||||
super::test_ui::seat_chain_addon(&mut s, "Blizzard_InspectUI");
|
||||
s.run("InspectFrame_LoadUI()").unwrap();
|
||||
// All 19 slots exist and carry their GetInventorySlotInfo id (1..=19, no ammo slot).
|
||||
// The stock `InspectPaperDollItemSlotButton_OnLoad` puts it on the frame's own ID
|
||||
// (`this:SetID(id)`); our retired file kept it in an `invSlotId` field of its own (1832).
|
||||
|
||||
@@ -98,7 +98,7 @@ fn the_self_menu_row_gates_on_the_binding_and_confirms_before_sending() {
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua", // `TEXT`, which StaticPopup.lua and UnitPopup.lua read at file scope
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"Interface\\FrameXML\\UnitPopup.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
@@ -111,10 +111,14 @@ fn the_self_menu_row_gates_on_the_binding_and_confirms_before_sending() {
|
||||
"Interface\\FrameXML\\TargetFrame.xml",
|
||||
"Interface\\FrameXML\\PetFrame.xml",
|
||||
r"Interface\FrameXML\RaidFrame.xml",
|
||||
r"Interface\AddOns\Blizzard_RaidUI\Blizzard_RaidUI.xml",
|
||||
] {
|
||||
load_xml(&s, file);
|
||||
}
|
||||
// The window is a LoadOnDemand addon, reached the way the app reaches it: seated off the
|
||||
// chain as a registry row (1957) and loaded by the reference's own `RaidFrame_LoadUI`
|
||||
// (UIParent.xml; 1967).
|
||||
super::test_ui::seat_chain_addon(&mut s, "Blizzard_RaidUI");
|
||||
s.run("RaidFrame_LoadUI()").unwrap();
|
||||
s.resolve();
|
||||
assert!(s.errors().is_empty(), "load errors: {:?}", s.errors());
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(crate) fn harness() -> UiScript {
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"ScrollTemplates.xml",
|
||||
"UIParent.xml",
|
||||
|
||||
@@ -609,7 +609,7 @@ fn ctrl_and_shift_on_a_loot_row_preview_and_post_without_looting() {
|
||||
for file in [
|
||||
"UIParent.xml", // BenillaChatEdit_InsertLink, the shared shift-insert helper
|
||||
"Interface\\FrameXML\\LootFrame.xml",
|
||||
"DressUpFrame.xml",
|
||||
"Interface\\FrameXML\\DressUpFrame.xml",
|
||||
"Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
|
||||
@@ -97,7 +97,7 @@ fn harness_with(player: &str) -> UiScript {
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
// The chain's `PanelTemplates_SelectTab` reaches for `GameTooltip` unguarded.
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"UIParent.xml", // `ShowMacroFrame` lives here now
|
||||
// **ScrollTemplates BEFORE UIPanelTemplates, the manifest's own order.** Ours still
|
||||
// carries dead `FauxScrollFrame_*` copies the chain overrides by loading after (1846's
|
||||
@@ -109,10 +109,14 @@ fn harness_with(player: &str) -> UiScript {
|
||||
// `ClassTrainerListScrollFrameTemplate` — the icon chooser's scroll frame inherits it.
|
||||
r"Interface\FrameXML\ClassTrainerFrameTemplates.xml",
|
||||
"MicroMenu.xml", // stock `MacroFrame_OnShow`/`_OnHide` drive the micro button
|
||||
r"Interface\AddOns\Blizzard_MacroUI\Blizzard_MacroUI.xml",
|
||||
] {
|
||||
super::test_ui::load_ui(&s, file);
|
||||
}
|
||||
// The window is a LoadOnDemand addon, reached the way the app reaches it: seated off the
|
||||
// chain as a registry row (1957) and loaded by the reference's own `MacroFrame_LoadUI`
|
||||
// (UIParent.xml; 1967).
|
||||
super::test_ui::seat_chain_addon(&mut s, "Blizzard_MacroUI");
|
||||
s.run("MacroFrame_LoadUI()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
// The icon chooser's list is the app's push; three entries is enough to index into.
|
||||
s.set_macro_icons(vec![
|
||||
|
||||
@@ -178,31 +178,6 @@ pub(crate) fn load_default_ui(script: &UiScript) -> Vec<String> {
|
||||
/// per-entry rather than per-run so the manifest's order is the load order verbatim — the whole
|
||||
/// reason the list is a manifest and not two lists.
|
||||
///
|
||||
/// Both stores answer `<Include>` / `<Script file=>` the same way: the loader resolves a reference
|
||||
/// against the *including document's own* directory in its source's path space (1186), which for a
|
||||
/// chain document means `Interface\FrameXML\ContainerFrame.xml`'s `<Script
|
||||
/// file="ContainerFrame.lua"/>` reaches `Interface\FrameXML\ContainerFrame.lua` without anything
|
||||
/// here knowing about it.
|
||||
/// The Blizzard addons the manifest lists off the chain (`Interface\AddOns\<Name>\…` rows), by
|
||||
/// name, in first-appearance order — the eager trio the InspectUI header describes, which the
|
||||
/// registry must not also carry as LoadOnDemand rows (`addons::chain_addons`, 1957).
|
||||
pub(crate) fn chain_addons(files: &[String]) -> Vec<String> {
|
||||
let mut out: Vec<String> = Vec::new();
|
||||
for file in files {
|
||||
let norm = file.replace('/', "\\");
|
||||
let Some(rest) = norm.strip_prefix("Interface\\AddOns\\") else {
|
||||
continue;
|
||||
};
|
||||
let Some((name, _)) = rest.split_once('\\') else {
|
||||
continue;
|
||||
};
|
||||
if !out.iter().any(|n| n == name) {
|
||||
out.push(name.to_string());
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn load_manifest(script: &UiScript, files: &[String]) -> Vec<String> {
|
||||
let builtin = Addon::builtin();
|
||||
let reference = reference_ui::addon(
|
||||
|
||||
@@ -565,6 +565,10 @@ fn merchant_switch_plays_close_then_open_and_queues_the_consumable_close() {
|
||||
fn shipped_merchant_hover_scopes_highlight_and_anchors_item_tooltip() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
// The stock tooltip declares no size: it sizes from its lines through the font engine, as
|
||||
// the client's does (1968) — a harness that reads its rect needs one; the fixed-width
|
||||
// font is that engine here.
|
||||
s.set_text_measurer(Box::new(super::FixedWidthFont(6.0)));
|
||||
for f in super::test_ui::MERCHANT_UI {
|
||||
load_xml(&s, f);
|
||||
}
|
||||
@@ -1138,7 +1142,7 @@ fn ctrl_and_shift_on_a_vendor_row_preview_and_post_without_buying() {
|
||||
for file in [
|
||||
"UIParent.xml", // BenillaChatEdit_InsertLink, the shared shift-insert helper
|
||||
"Interface\\FrameXML\\MerchantFrame.xml",
|
||||
"DressUpFrame.xml",
|
||||
"Interface\\FrameXML\\DressUpFrame.xml",
|
||||
"Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
|
||||
@@ -32,7 +32,7 @@ fn harness_with(extra: &[&str]) -> UiScript {
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
"MicroMenu.xml",
|
||||
@@ -258,6 +258,10 @@ fn the_talent_button_appears_at_level_ten_and_the_row_closes_up_below_it() {
|
||||
fn every_micro_button_hovers_with_its_reference_explanation() {
|
||||
let mut s = harness_with(&["UIParent.xml"]);
|
||||
set_player_level(&mut s, 10);
|
||||
// 1.12 ships detailed tips ON — `SHOW_NEWBIE_TIPS = "1"` is UIOptionsFrame_Init's (ref
|
||||
// UIOptionsFrame.lua l.100; ours sits in OptionsFrame.xml's uvar block, 1968), and a harness
|
||||
// without the options file says so itself, the way the reference's tooltip would read it.
|
||||
s.run("SHOW_NEWBIE_TIPS = \"1\"").unwrap();
|
||||
s.resolve();
|
||||
|
||||
for (button, newbie) in [
|
||||
@@ -270,7 +274,8 @@ fn every_micro_button_hovers_with_its_reference_explanation() {
|
||||
("MainMenuMicroButton", "NEWBIE_TOOLTIP_MAINMENU"),
|
||||
("HelpMicroButton", "NEWBIE_TOOLTIP_HELP"),
|
||||
] {
|
||||
s.run(&format!("BenillaMicroButton_OnEnter({button})"))
|
||||
// A hover is a handler: the reference's helper reads the firing frame off `this`.
|
||||
s.run(&format!("this = {button} BenillaMicroButton_OnEnter(this)"))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return GameTooltip:NumLines()").unwrap(),
|
||||
|
||||
@@ -26,7 +26,7 @@ fn minimap_zoom_buttons_resync_when_switching_inside_and_outside() {
|
||||
// half (the PVP tint slice, decision 0287) touches it from OnLoad on.
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "MinimapCluster.xml");
|
||||
|
||||
// OnLoad seeds the +/- state from the live zoom. The CVar default is 3 — a middle level, so
|
||||
@@ -91,7 +91,7 @@ fn tracking_frame_follows_get_tracking_texture_across_player_auras_changed() {
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "MinimapCluster.xml");
|
||||
|
||||
let vis = |s: &UiScript| {
|
||||
@@ -141,7 +141,7 @@ fn game_time_session(hour: u32, minute: u32) -> UiScript {
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "MinimapCluster.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTime.xml");
|
||||
s
|
||||
@@ -254,7 +254,7 @@ fn a_click_on_the_minimap_parks_a_centre_relative_ping_request() {
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "MinimapCluster.xml");
|
||||
s.resolve();
|
||||
|
||||
@@ -300,7 +300,7 @@ fn get_ping_position_is_nil_until_there_is_a_ping() {
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "MinimapCluster.xml");
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -22,7 +22,7 @@ fn load_action_bar(s: &UiScript) {
|
||||
"UIParent.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
@@ -466,7 +466,7 @@ fn multibar_hover_renders_the_buttons_own_action() {
|
||||
"UIParent.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
|
||||
@@ -60,7 +60,7 @@ fn harness_on(mut s: UiScript) -> UiScript {
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"ScrollTemplates.xml", // the Keybindings page's faux-scroll kit
|
||||
"KeyBindingsPage.xml", // the Keybindings body's templates + script (1008)
|
||||
@@ -757,7 +757,7 @@ fn interface_harness() -> UiScript {
|
||||
// own backdrop reads `GameTooltip`'s TOOLTIP_DEFAULT_COLOR. `harness_on` loads two of
|
||||
// these again after these — re-running a UI file is what `/reload` does, and the
|
||||
// loader takes it.
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"Interface\\FrameXML\\BasicControls.xml", // `TEXT`, which UnitPopup.lua reads at file scope
|
||||
"Interface\\FrameXML\\UnitPopup.xml",
|
||||
@@ -826,7 +826,7 @@ fn chat_harness() -> UiScript {
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
@@ -861,7 +861,7 @@ fn actionbars_harness() -> UiScript {
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
|
||||
@@ -2109,6 +2109,10 @@ fn hover_label(s: &mut UiScript, frame: &str) {
|
||||
#[test]
|
||||
fn a_hovered_row_raises_its_1_12_description_on_the_era_seat() {
|
||||
let mut s = harness_on(audio_harness());
|
||||
// The stock tooltip declares no size: it sizes from its lines through the font engine, as
|
||||
// the client's does (1968) — a harness that reads its rect needs one; the fixed-width
|
||||
// font is that engine here.
|
||||
s.set_text_measurer(Box::new(super::FixedWidthFont(6.0)));
|
||||
s.run("OPTION_TOOLTIP_GAMEFIELD_DESELECT = \"Checking this will prevent the deselection.\"")
|
||||
.unwrap();
|
||||
s.run("ERA_WINDOW_SCALE = 1").unwrap();
|
||||
|
||||
@@ -48,7 +48,7 @@ fn harness() -> UiScript {
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"ScrollTemplates.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
|
||||
@@ -858,7 +858,7 @@ fn gossip_bank_option_hands_the_left_slot_to_the_bank() {
|
||||
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
|
||||
// UIPanelScrollFrameTemplate lives here, and the gossip scroll frame inherits it. NOT
|
||||
// optional: a missing template is a loader *warning*, not an error, so an under-loaded
|
||||
|
||||
@@ -24,7 +24,7 @@ fn harness(extra: &[&str]) -> UiScript {
|
||||
load_xml(&s, "Interface\\FrameXML\\MainMenuBar.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ActionBarFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BonusActionBarFrame.xml");
|
||||
for f in extra {
|
||||
@@ -231,6 +231,10 @@ fn get_net_stats_reports_the_pushed_latency() {
|
||||
fn hovering_the_meter_shows_the_live_latency() {
|
||||
let mut s = harness(&["UIParent.xml"]);
|
||||
s.set_latency_ms(Some(42));
|
||||
// 1.12 ships detailed tips ON — `SHOW_NEWBIE_TIPS = "1"` is UIOptionsFrame_Init's (ref
|
||||
// UIOptionsFrame.lua l.100; ours sits in OptionsFrame.xml's uvar block, 1968), and a harness
|
||||
// without the options file says so itself, the way the reference's tooltip would read it.
|
||||
s.run("SHOW_NEWBIE_TIPS = \"1\"").unwrap();
|
||||
s.resolve();
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -28,7 +28,7 @@ pub(super) fn load_pet_bar(s: &UiScript) {
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
|
||||
@@ -30,7 +30,7 @@ fn load_pet_frame() -> UiScript {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UnitPopup.xml");
|
||||
|
||||
@@ -112,7 +112,7 @@ fn setup() -> UiScript {
|
||||
// `ChatFrameEditBox`, which the reference's own purchase button indexes on every click to
|
||||
// decide where focus goes after the name box closes — a nil there raises before the charter is
|
||||
// bought. Ours guarded it; the reference does not.
|
||||
load_xml(&s, "GameTooltip.xml"); // TOOLTIP_DEFAULT_COLOR, read by the dropdown backdrops
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); // TOOLTIP_DEFAULT_COLOR, read by the dropdown backdrops
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); // ChatFrame's seven dropdowns inherit its template
|
||||
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit the chat menus build from
|
||||
load_xml(&s, "Interface\\FrameXML\\ChatFrame.xml");
|
||||
|
||||
@@ -22,7 +22,7 @@ fn questgiver_show_hide_plays_open_and_close_kits() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -93,7 +93,7 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -192,7 +192,7 @@ fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -295,7 +295,7 @@ fn reward_panel_choice_click_selects_and_completes_with_zero_based_index() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -375,7 +375,7 @@ fn greeting_goodbye_button_closes_the_window() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -433,7 +433,7 @@ fn detail_panel_action_buttons_resolve_to_real_onscreen_rects() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -562,7 +562,7 @@ fn write_on_still_fades_when_instant_text_is_off() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -640,7 +640,7 @@ fn npc_name_reaches_the_title_bar_on_open_and_on_refresh() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -724,7 +724,7 @@ fn greeting_panel_title_rows_grow_to_their_wrapped_titles() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -825,7 +825,7 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -839,7 +839,7 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
load_xml(&s, "UIParent.xml"); // BenillaChatEdit_InsertLink lives here
|
||||
load_xml(&s, "DressUpFrame.xml"); // DressUpItemLink lives here
|
||||
load_xml(&s, "Interface\\FrameXML\\DressUpFrame.xml"); // DressUpItemLink lives here
|
||||
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit its menus build from
|
||||
load_xml(&s, "Interface\\FrameXML\\ChatFrame.xml"); // ChatFrameEditBox lives here
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
|
||||
@@ -89,7 +89,7 @@ fn shipped_questlog_frame_loads_clean() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -123,7 +123,7 @@ fn shipped_questlog_frame_drives_end_to_end() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua");
|
||||
@@ -333,7 +333,7 @@ fn shift_click_toggles_the_watch_checkbox_and_the_tracker_hud() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -481,7 +481,7 @@ fn watch_guards_no_op_without_erroring() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -598,7 +598,7 @@ fn progress_auto_watches_for_five_minutes() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -683,7 +683,7 @@ fn the_auto_watch_flag_is_the_references_uvar_and_gates_the_watch() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -749,7 +749,7 @@ fn the_row_tag_is_its_own_right_flush_string_and_the_state_word_wins() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -828,7 +828,7 @@ fn empty_quest_log_hides_rows_and_disables_abandon() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -893,7 +893,7 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -1082,7 +1082,7 @@ fn overflowing_detail_content_clips_to_the_scrollframe_rect() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -1157,7 +1157,7 @@ fn wheel_over_the_detail_pane_changes_vertical_scroll() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -1222,7 +1222,7 @@ fn selection_change_resets_detail_scroll_but_a_quest_log_update_refresh_does_not
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -1299,7 +1299,7 @@ fn reward_row_hover_serves_the_shared_item_tooltip() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -1459,7 +1459,7 @@ fn reward_rows_preview_and_post_and_a_plain_click_stays_inert() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -1481,7 +1481,7 @@ fn reward_rows_preview_and_post_and_a_plain_click_stays_inert() {
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
load_xml(&s, "UIParent.xml"); // BenillaChatEdit_InsertLink lives here
|
||||
load_xml(&s, "DressUpFrame.xml"); // DressUpItemLink lives here
|
||||
load_xml(&s, "Interface\\FrameXML\\DressUpFrame.xml"); // DressUpItemLink lives here
|
||||
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit its menus build from
|
||||
load_xml(&s, "Interface\\FrameXML\\ChatFrame.xml"); // ChatFrameEditBox lives here
|
||||
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua");
|
||||
@@ -1558,7 +1558,7 @@ fn shift_click_on_a_title_posts_the_quest_name_with_chat_open_and_watches_with_i
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -1668,7 +1668,7 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -1755,7 +1755,7 @@ fn share_quest_is_dark_on_an_empty_log_even_in_a_party() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
@@ -1796,7 +1796,7 @@ fn share_quest_click_queues_the_selected_quests_id() {
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua");
|
||||
|
||||
@@ -16,17 +16,13 @@ use benilla_ui::script::{
|
||||
UiScript,
|
||||
};
|
||||
|
||||
use super::test_ui::load_ui_strict as load_xml;
|
||||
|
||||
/// The window's slice of the manifest, in `load_default_ui` order. `UIParent.xml` is in it for
|
||||
/// two functions the pane really calls — `MouseIsOver` (the drag's hover sweep) and
|
||||
/// `SecondsToTime` (the lockout rows) — and for the `READY_CHECK` arm that opens the popup.
|
||||
fn setup() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for f in super::test_ui::SOCIAL_UI {
|
||||
load_xml(&s, f);
|
||||
}
|
||||
super::test_ui::load_social_ui(&mut s);
|
||||
s
|
||||
}
|
||||
|
||||
|
||||
@@ -1224,13 +1224,18 @@ mod tests {
|
||||
// Everything the manifest pulls OFF THE CHAIN, and every name each of those defines —
|
||||
// including the `.lua` a chain `.xml` sources, which is where most of them live.
|
||||
let toc = &super::super::addons::Addon::builtin().toc.files;
|
||||
let pos: std::collections::HashMap<&String, usize> =
|
||||
toc.iter().enumerate().map(|(i, f)| (f, i)).collect();
|
||||
// Load order: a manifest entry at its line, a reached addon's file after everything.
|
||||
let chain = gated_chain_entries();
|
||||
let pos: std::collections::HashMap<&String, usize> = chain
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(k, f)| (f, toc.iter().position(|t| t == f).unwrap_or(toc.len() + k)))
|
||||
.collect();
|
||||
let mut chain_home: std::collections::HashMap<String, (String, usize)> =
|
||||
std::collections::HashMap::new();
|
||||
let mut chain_frames: std::collections::HashMap<String, String> =
|
||||
std::collections::HashMap::new();
|
||||
for entry in toc.iter().filter(|f| super::is_chain_entry(f)) {
|
||||
for entry in &chain {
|
||||
let leaf = entry.rsplit(['\\', '/']).next().unwrap_or(entry);
|
||||
let mut cands = vec![entry.clone()];
|
||||
if let Some(stem) = entry.strip_suffix(".xml") {
|
||||
@@ -1453,6 +1458,19 @@ mod tests {
|
||||
seat_a_player(&mut s);
|
||||
let failures = super::super::manifest::load_default_ui(&s);
|
||||
assert!(failures.is_empty(), "the shipped manifest: {failures:#?}");
|
||||
// The whole interface is the manifest AND every LoadOnDemand addon it reaches (1967):
|
||||
// what `MacroFrame_SaveMacro` answers to is `Blizzard_MacroUI.lua`, loaded on the first
|
||||
// `ShowMacroFrame`, and a call into it from ActionBarFrame.lua is answered exactly then.
|
||||
for name in reached_addons() {
|
||||
super::super::test_ui::seat_chain_addon(&mut s, &name);
|
||||
s.run(&format!("UIParentLoadAddOn(\"{name}\")")).unwrap();
|
||||
assert!(
|
||||
s.eval::<bool>(&format!("return IsAddOnLoaded(\"{name}\") == 1"))
|
||||
.unwrap(),
|
||||
"{name}: reached, seated, and did not load: {:?}",
|
||||
s.errors()
|
||||
);
|
||||
}
|
||||
let have: std::collections::HashSet<String> = s
|
||||
.eval::<Vec<String>>(
|
||||
"local t = {} for k in pairs(_G) do table.insert(t, k) end return t",
|
||||
@@ -1471,16 +1489,6 @@ mod tests {
|
||||
// reads clean. Each belongs to whichever window's migration left it, and each is one
|
||||
// binding or one sourced file away.
|
||||
const KNOWN: &[(&str, &str, &str)] = &[
|
||||
(
|
||||
"ClassTrainerFrameTemplates.xml",
|
||||
"ClassTrainerSkillButton_OnClick",
|
||||
"defined by `Blizzard_TrainerUI`, which is a LoadOnDemand addon here as it is in \
|
||||
the reference (1957): it loads on the first trainer, through UIParent's \
|
||||
TRAINER_SHOW arm, and the skill-button template that carries this handler is \
|
||||
instantiated only by that addon, so no click can reach the name before its \
|
||||
definer exists. The reference is in exactly this state until its addon loads. \
|
||||
Permanent by design, not a gap to close.",
|
||||
),
|
||||
(
|
||||
"ContainerFrame.xml",
|
||||
"KeyRingButtonIDToInvSlotID",
|
||||
@@ -1523,6 +1531,114 @@ mod tests {
|
||||
update; our `inventory_alerts` snapshot is recomputed on every inventory push \
|
||||
instead, so the recompute exists and only the Lua verb that forces one does not.",
|
||||
),
|
||||
(
|
||||
"Blizzard_GMSurveyUI.xml",
|
||||
"GMSurveyAnswerSubmit",
|
||||
"one of the GM survey's four engine verbs, none built: the survey window opens on \
|
||||
GMSURVEY_DISPLAY, which the stock HelpFrame.lua registers and nothing fires — the \
|
||||
trigger is ticket status 3 on SMSG_GMTICKET_GETTICKET, which vmangos never sends \
|
||||
(1889; the producer gate carries the event). Gated since the addon became a reached \
|
||||
LoadOnDemand row (1967).",
|
||||
),
|
||||
(
|
||||
"Blizzard_GMSurveyUI.xml",
|
||||
"GMSurveyCommentSubmit",
|
||||
"one of the GM survey's four engine verbs, none built: the survey window opens on \
|
||||
GMSURVEY_DISPLAY, which the stock HelpFrame.lua registers and nothing fires — the \
|
||||
trigger is ticket status 3 on SMSG_GMTICKET_GETTICKET, which vmangos never sends \
|
||||
(1889; the producer gate carries the event). Gated since the addon became a reached \
|
||||
LoadOnDemand row (1967).",
|
||||
),
|
||||
(
|
||||
"Blizzard_GMSurveyUI.xml",
|
||||
"GMSurveyQuestion",
|
||||
"one of the GM survey's four engine verbs, none built: the survey window opens on \
|
||||
GMSURVEY_DISPLAY, which the stock HelpFrame.lua registers and nothing fires — the \
|
||||
trigger is ticket status 3 on SMSG_GMTICKET_GETTICKET, which vmangos never sends \
|
||||
(1889; the producer gate carries the event). Gated since the addon became a reached \
|
||||
LoadOnDemand row (1967).",
|
||||
),
|
||||
(
|
||||
"Blizzard_GMSurveyUI.xml",
|
||||
"GMSurveySubmit",
|
||||
"one of the GM survey's four engine verbs, none built: the survey window opens on \
|
||||
GMSURVEY_DISPLAY, which the stock HelpFrame.lua registers and nothing fires — the \
|
||||
trigger is ticket status 3 on SMSG_GMTICKET_GETTICKET, which vmangos never sends \
|
||||
(1889; the producer gate carries the event). Gated since the addon became a reached \
|
||||
LoadOnDemand row (1967).",
|
||||
),
|
||||
(
|
||||
"Blizzard_BattlefieldMinimap.xml",
|
||||
"CreateMiniWorldMapArrowFrame",
|
||||
"the world map's arrow-frame family (Create/Position/Show/UpdateWorldMapArrowFrames), engine verbs the reference's WorldMapFrame.lua also calls — unbuilt; ours draws the map's arrows app-side, and they land with the world map's own migration. The addon is a reached LoadOnDemand row since 1972 (UIParent.xml's \
|
||||
BattlefieldMinimap_LoadUI, which the stock WorldStateFrame.lua calls on a \
|
||||
battleground's world states); its pane is a <Minimap>-kind widget this engine \
|
||||
draws nothing into yet, so the addon loads, paints nothing, and these are the verbs \
|
||||
its update would reach.",
|
||||
),
|
||||
(
|
||||
"Blizzard_BattlefieldMinimap.xml",
|
||||
"GetBattlefieldFlagPosition",
|
||||
"the battlefield minimap's flag-position getter (0x4ac230, structural row only; the position family is uncarved). The addon is a reached LoadOnDemand row since 1972 (UIParent.xml's \
|
||||
BattlefieldMinimap_LoadUI, which the stock WorldStateFrame.lua calls on a \
|
||||
battleground's world states); its pane is a <Minimap>-kind widget this engine \
|
||||
draws nothing into yet, so the addon loads, paints nothing, and these are the verbs \
|
||||
its update would reach.",
|
||||
),
|
||||
(
|
||||
"Blizzard_BattlefieldMinimap.xml",
|
||||
"GetBattlefieldMapIconScale",
|
||||
"the battlefield minimap's icon-scale getter (uncarved; reads the queue slot's Map.dbc row). The addon is a reached LoadOnDemand row since 1972 (UIParent.xml's \
|
||||
BattlefieldMinimap_LoadUI, which the stock WorldStateFrame.lua calls on a \
|
||||
battleground's world states); its pane is a <Minimap>-kind widget this engine \
|
||||
draws nothing into yet, so the addon loads, paints nothing, and these are the verbs \
|
||||
its update would reach.",
|
||||
),
|
||||
(
|
||||
"Blizzard_BattlefieldMinimap.xml",
|
||||
"GetBattlefieldPosition",
|
||||
"the battlefield minimap's player-position getter (wow-re `bindings.md` 0x4abf90, a structural row — the position family is outside 195cd099's carve). The addon is a reached LoadOnDemand row since 1972 (UIParent.xml's \
|
||||
BattlefieldMinimap_LoadUI, which the stock WorldStateFrame.lua calls on a \
|
||||
battleground's world states); its pane is a <Minimap>-kind widget this engine \
|
||||
draws nothing into yet, so the addon loads, paints nothing, and these are the verbs \
|
||||
its update would reach.",
|
||||
),
|
||||
(
|
||||
"Blizzard_BattlefieldMinimap.xml",
|
||||
"PositionMiniWorldMapArrowFrame",
|
||||
"as CreateMiniWorldMapArrowFrame — the arrow-frame family. The addon is a reached LoadOnDemand row since 1972 (UIParent.xml's \
|
||||
BattlefieldMinimap_LoadUI, which the stock WorldStateFrame.lua calls on a \
|
||||
battleground's world states); its pane is a <Minimap>-kind widget this engine \
|
||||
draws nothing into yet, so the addon loads, paints nothing, and these are the verbs \
|
||||
its update would reach.",
|
||||
),
|
||||
(
|
||||
"Blizzard_BattlefieldMinimap.xml",
|
||||
"ShowMiniWorldMapArrowFrame",
|
||||
"as CreateMiniWorldMapArrowFrame — the arrow-frame family. The addon is a reached LoadOnDemand row since 1972 (UIParent.xml's \
|
||||
BattlefieldMinimap_LoadUI, which the stock WorldStateFrame.lua calls on a \
|
||||
battleground's world states); its pane is a <Minimap>-kind widget this engine \
|
||||
draws nothing into yet, so the addon loads, paints nothing, and these are the verbs \
|
||||
its update would reach.",
|
||||
),
|
||||
(
|
||||
"Blizzard_BattlefieldMinimap.xml",
|
||||
"UpdateWorldMapArrowFrames",
|
||||
"as CreateMiniWorldMapArrowFrame — the arrow-frame family. The addon is a reached LoadOnDemand row since 1972 (UIParent.xml's \
|
||||
BattlefieldMinimap_LoadUI, which the stock WorldStateFrame.lua calls on a \
|
||||
battleground's world states); its pane is a <Minimap>-kind widget this engine \
|
||||
draws nothing into yet, so the addon loads, paints nothing, and these are the verbs \
|
||||
its update would reach.",
|
||||
),
|
||||
(
|
||||
"Blizzard_BattlefieldMinimap.xml",
|
||||
"WorldMap_GetPOITextureCoords",
|
||||
"a stock WorldMapFrame.lua function; our WorldMapFrame.xml is still ours and defines its own POI atlas walk, so the name arrives with the world map's migration. The addon is a reached LoadOnDemand row since 1972 (UIParent.xml's \
|
||||
BattlefieldMinimap_LoadUI, which the stock WorldStateFrame.lua calls on a \
|
||||
battleground's world states); its pane is a <Minimap>-kind widget this engine \
|
||||
draws nothing into yet, so the addon loads, paints nothing, and these are the verbs \
|
||||
its update would reach.",
|
||||
),
|
||||
(
|
||||
"StaticPopup.xml",
|
||||
"ReplaceTradeEnchant",
|
||||
@@ -1531,9 +1647,8 @@ mod tests {
|
||||
),
|
||||
];
|
||||
|
||||
let toc = &super::super::addons::Addon::builtin().toc.files;
|
||||
let mut missing: Vec<(String, String)> = Vec::new();
|
||||
for entry in toc.iter().filter(|f| super::is_chain_entry(f)) {
|
||||
for entry in &gated_chain_entries() {
|
||||
// `GlobalStrings.lua` is 4000 lines of `NAME = "…";` and nothing else — it calls no
|
||||
// global at all. What it DOES contain is every format specifier and every English
|
||||
// sentence in the interface, and this scanner's `name(` shape reads `%d (`, `%s (` and
|
||||
@@ -1694,7 +1809,7 @@ mod tests {
|
||||
.collect();
|
||||
|
||||
let mut orphans = Vec::new();
|
||||
for entry in toc.iter().filter(|f| super::is_chain_entry(f)) {
|
||||
for entry in &gated_chain_entries() {
|
||||
let leaf = entry.rsplit(['\\', '/']).next().unwrap_or(entry);
|
||||
let Some(stem) = leaf.strip_suffix(".xml") else {
|
||||
continue;
|
||||
@@ -1963,6 +2078,68 @@ mod tests {
|
||||
/// The chain for a path, `assets/ui` for a bare name — and an include resolves against the
|
||||
/// INCLUDING document's own directory in its own source's path space, which is the rule the
|
||||
/// loader follows (1186).
|
||||
/// The reference's LoadOnDemand Blizzard addons this interface REACHES — every `Blizzard_*`
|
||||
/// name a `UIParentLoadAddOn("…")` literal names in our own files or in a manifest chain
|
||||
/// entry's sources (the reference's `*_LoadUI` loaders in UIParent.xml, the options window's
|
||||
/// combat-text load). They have no manifest row, exactly as the reference's `FrameXML.toc`
|
||||
/// has none, so this is how the gates know which addon files are part of the shipped
|
||||
/// interface (1967). An addon nothing loads is an unbuilt window, not a migrated one.
|
||||
fn reached_addons() -> Vec<String> {
|
||||
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/ui");
|
||||
let mut out: Vec<String> = Vec::new();
|
||||
for entry in &super::super::addons::Addon::builtin().toc.files {
|
||||
let texts = if super::is_chain_entry(entry) {
|
||||
entry_sources(entry)
|
||||
} else {
|
||||
std::fs::read_to_string(dir.join(entry))
|
||||
.into_iter()
|
||||
.collect()
|
||||
};
|
||||
for text in texts {
|
||||
let text = strip_comments(&text);
|
||||
for (i, _) in text.match_indices("UIParentLoadAddOn(\"Blizzard_") {
|
||||
let rest = &text[i + "UIParentLoadAddOn(\"".len()..];
|
||||
if let Some(end) = rest.find('"') {
|
||||
let name = rest[..end].to_string();
|
||||
if !out.contains(&name) {
|
||||
out.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out.sort();
|
||||
out
|
||||
}
|
||||
|
||||
/// Every chain file the shipped interface loads, in load order: the manifest's chain entries,
|
||||
/// then each reached addon's files (`Interface\AddOns\<name>\<file>`, the addon's own toc
|
||||
/// order) — LoadOnDemand loads after everything. The set every gate over chain files walks
|
||||
/// (1967); an addon an opener names that the chain does not carry is a finding, not a skip.
|
||||
fn gated_chain_entries() -> Vec<String> {
|
||||
let mut out: Vec<String> = super::super::addons::Addon::builtin()
|
||||
.toc
|
||||
.files
|
||||
.iter()
|
||||
.filter(|f| super::is_chain_entry(f))
|
||||
.cloned()
|
||||
.collect();
|
||||
for name in reached_addons() {
|
||||
let bytes =
|
||||
super::read(&format!("Interface/AddOns/{name}/{name}.toc")).unwrap_or_else(|| {
|
||||
panic!("{name}: reached by UIParentLoadAddOn, not on the chain")
|
||||
});
|
||||
let toc = benilla_ui::toc::Toc::parse(&benilla_ui::source::decode(&bytes));
|
||||
for file in &toc.files {
|
||||
out.push(format!(
|
||||
"Interface\\AddOns\\{name}\\{}",
|
||||
file.replace('/', "\\")
|
||||
));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn entry_sources(entry: &str) -> Vec<String> {
|
||||
fn read_one(path: &str, chain: bool) -> Option<String> {
|
||||
if chain {
|
||||
@@ -2306,10 +2483,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let toc = &super::super::addons::Addon::builtin().toc.files;
|
||||
let mut dead: std::collections::BTreeMap<String, String> =
|
||||
std::collections::BTreeMap::new();
|
||||
for entry in toc.iter().filter(|f| super::is_chain_entry(f)) {
|
||||
for entry in &gated_chain_entries() {
|
||||
for text in entry_sources(entry) {
|
||||
let mut from = 0;
|
||||
while let Some(i) = text[from..].find("RegisterEvent(") {
|
||||
|
||||
@@ -32,7 +32,7 @@ fn every_shipped_ui_xml_parses() {
|
||||
assert!(
|
||||
// A sanity floor for the walk, not a census: `assets/ui` retires file by file (1751),
|
||||
// so the floor sits well under the count rather than one step above it (1956).
|
||||
checked >= 20,
|
||||
checked >= 15,
|
||||
"only {checked} xml files swept — sweep broke"
|
||||
);
|
||||
}
|
||||
@@ -428,8 +428,10 @@ fn every_shipped_texture_path_resolves_in_the_client_archives() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Never let the sweep pass by matching nothing — the shipped UI names hundreds of textures.
|
||||
assert!(refs.len() >= 200, "only {} texture paths swept", refs.len());
|
||||
// Never let the sweep pass by matching nothing — the shipped UI still names over a hundred
|
||||
// textures (173 after 1971 retired the auction transcription, fewer again after 1973 the
|
||||
// crafting pair; the count falls with every window that migrates, and the floor follows it).
|
||||
assert!(refs.len() >= 80, "only {} texture paths swept", refs.len());
|
||||
|
||||
// The shape half: a doubled separator is the Lua escaping written into XML, and it resolves to
|
||||
// nothing. Checked without the client so a data-less machine still catches this exact class.
|
||||
@@ -622,9 +624,10 @@ fn every_archive_path_a_shipped_lua_chunk_names_survives_its_own_escaping() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Never let the sweep pass by matching nothing.
|
||||
// Never let the sweep pass by matching nothing (19 after 1971; the floor follows the census
|
||||
// down as windows migrate).
|
||||
assert!(
|
||||
paths.len() >= 20,
|
||||
paths.len() >= 10,
|
||||
"only {} archive paths swept out of the shipped Lua",
|
||||
paths.len()
|
||||
);
|
||||
@@ -729,9 +732,11 @@ fn every_shipped_text_attribute_answers_against_the_real_global_strings() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Never let the sweep pass by matching nothing: 23 key-shaped values across six windows is the
|
||||
// floor as of 0991, and a regex that stops matching is exactly how this guard would retire.
|
||||
assert!(keys >= 23, "only {keys} key-shaped text= values swept");
|
||||
// Never let the sweep pass by matching nothing: 23 key-shaped values across six windows was
|
||||
// the floor as of 0991; ONE is left after 1971, and a regex that stops matching is exactly how
|
||||
// this guard would retire — so the floor is one, and the guard retires with the last file of
|
||||
// ours that writes a key-shaped `text=`.
|
||||
assert!(keys >= 1, "only {keys} key-shaped text= values swept");
|
||||
}
|
||||
|
||||
/// **No shipped script hands a GlobalStrings KEY to a text sink as if it were the string.**
|
||||
@@ -840,7 +845,7 @@ fn no_shipped_script_sets_a_global_string_key_as_display_text() {
|
||||
assert!(offenders.is_empty(), "{}", offenders.join("\n"));
|
||||
// The sweep must never pass by finding nothing to sweep.
|
||||
// The same walk floor as above (1956).
|
||||
assert!(swept >= 20, "only {swept} xml files swept — sweep broke");
|
||||
assert!(swept >= 15, "only {swept} xml files swept — sweep broke");
|
||||
}
|
||||
|
||||
/// **The `$parentTextureFrame` idiom's contract, over the whole shipped UI**: a frame whose art is
|
||||
@@ -1438,8 +1443,8 @@ fn every_declared_parent_really_attaches() {
|
||||
let declared = shipped_frame_parents();
|
||||
assert!(
|
||||
// A sanity floor for the scan, not a census — the declarations retire with the files
|
||||
// that carry them (1751); 67 before 1938, 30 after 1956.
|
||||
declared.len() >= 15,
|
||||
// that carry them (1751); 67 before 1938, 30 after 1956, 14 after 1970.
|
||||
declared.len() >= 10,
|
||||
"only {} parent declarations found — the scan broke",
|
||||
declared.len()
|
||||
);
|
||||
@@ -1576,6 +1581,9 @@ fn the_shipped_manifest_opens_the_macro_icon_picker() {
|
||||
s.resolve();
|
||||
let _ = s.errors();
|
||||
|
||||
// The window is a LoadOnDemand addon the app seats at setup (1957) and `ShowMacroFrame`
|
||||
// loads through the reference's own `MacroFrame_LoadUI` (1967).
|
||||
super::test_ui::seat_chain_addon(&mut s, "Blizzard_MacroUI");
|
||||
s.run("ShowMacroFrame()").unwrap();
|
||||
s.run("MacroButton1:Click()").unwrap();
|
||||
s.run("MacroEditButton:Click()").unwrap();
|
||||
|
||||
@@ -99,7 +99,7 @@ pub(super) fn spellbook_ui(w: f32, h: f32) -> UiScript {
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
@@ -139,7 +139,7 @@ fn shipped_spellbook_loads_clean() {
|
||||
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
// The reference's own file, off the chain, with the one adapter it needs from this engine.
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
@@ -692,7 +692,7 @@ fn the_macro_editor_takes_a_shift_click_and_only_a_shift_click() {
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
// `ShowMacroFrame` lives here since 1848.
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Cooldown.xml",
|
||||
// **ScrollTemplates BEFORE UIPanelTemplates, which is the manifest's own order.** Ours
|
||||
// still carries dead `FauxScrollFrame_*` copies that the chain overrides by loading after
|
||||
@@ -719,7 +719,6 @@ fn the_macro_editor_takes_a_shift_click_and_only_a_shift_click() {
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
r"Interface\FrameXML\OptionsFrameTemplates.xml",
|
||||
r"Interface\FrameXML\ReputationFrame.xml",
|
||||
r"Interface\AddOns\Blizzard_MacroUI\Blizzard_MacroUI.xml",
|
||||
// The reference's spellbook shows and hides the multibar grids (MultiActionBars.lua),
|
||||
// whose file wants the options window's uvars — the same tail `spellbook_ui` carries.
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
@@ -731,6 +730,11 @@ fn the_macro_editor_takes_a_shift_click_and_only_a_shift_click() {
|
||||
] {
|
||||
load_xml(&s, file);
|
||||
}
|
||||
// The window is a LoadOnDemand addon, reached the way the app reaches it: seated off the
|
||||
// chain as a registry row (1957) and loaded by the reference's own `MacroFrame_LoadUI`
|
||||
// (UIParent.xml; 1967).
|
||||
super::test_ui::seat_chain_addon(&mut s, "Blizzard_MacroUI");
|
||||
s.run("MacroFrame_LoadUI()").unwrap();
|
||||
s.fire_event("PLAYER_ENTERING_WORLD", vec![]);
|
||||
|
||||
// The file's own book plus a third Fire spell that is PASSIVE — book id 3, which the ref's
|
||||
|
||||
@@ -22,7 +22,7 @@ fn harness() -> UiScript {
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml"); // TOOLTIP_DEFAULT_* (the dropdown kit's MenuBackdrop)
|
||||
load_xml(&s, "Interface\\FrameXML\\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, "Cooldown.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
|
||||
|
||||
@@ -22,7 +22,7 @@ fn taxi_script() -> UiScript {
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
||||
load_xml(&s, "GameTooltip.xml"); // TaxiNodeOnButtonEnter's tooltip + SetTooltipMoney
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); // TaxiNodeOnButtonEnter's tooltip + SetTooltipMoney
|
||||
load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml"); // BenillaErrorsFrame_AddMessage — DrawOneHopLines' refusal
|
||||
// Three the reference's own TaxiFrame leans on that our transcription did not:
|
||||
// · GlobalStrings — `ERR_TAXINOPATHS` is a GlobalString, and `AddMessage(nil)` draws an
|
||||
|
||||
@@ -152,7 +152,7 @@ pub(super) const MERCHANT_UI: &[&str] = &[
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"GameTooltip.xml", // app load order: tooltip before merchant
|
||||
"Interface\\FrameXML\\GameTooltip.xml", // app load order: tooltip before merchant
|
||||
];
|
||||
|
||||
pub(super) const LOOT_UI: &[&str] = &[
|
||||
@@ -167,7 +167,7 @@ pub(super) const LOOT_UI: &[&str] = &[
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"GameTooltip.xml", // TOOLTIP_DEFAULT_COLOR, read by the dropdown backdrop
|
||||
"Interface\\FrameXML\\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.
|
||||
@@ -243,7 +243,7 @@ pub(super) const CHARACTER_UI: &[&str] = &[
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"UIParent.xml", // Model_OnLoad/_Rotate*/_OnUpdate — the model panes' turntable
|
||||
"UiPanels.xml", // CharacterFrameTabButtonTemplate, UIPanelWindows, Show/HideUIPanel
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Cooldown.xml", // CooldownFrameTemplate + CooldownFrame_SetTimer, per equipment slot
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
@@ -301,9 +301,9 @@ pub(super) const CHARACTER_UI: &[&str] = &[
|
||||
/// The social window's slice of the manifest (1959): everything the reference's
|
||||
/// `FriendsFrame.xml` + `RaidFrame.xml` reach at load or on show — `TEXT` and `GetText`, the
|
||||
/// bar chain under `UpdateMicroButtons`, the chat window whose edit box `FriendsFrame_SendMessage`
|
||||
/// opens, the unit menu, the party frames `RaidFrame_OnLoad` reconciles, and the raid tab's
|
||||
/// LoadOnDemand addon — in the manifest's own order. Shared by the friends, guild and raid
|
||||
/// harnesses.
|
||||
/// opens, the unit menu, the party frames `RaidFrame_OnLoad` reconciles — in the manifest's own
|
||||
/// order; the raid tab's LoadOnDemand addon follows through [`load_social_ui`]. Shared by the
|
||||
/// friends, guild and raid harnesses.
|
||||
pub(super) const SOCIAL_UI: &[&str] = &[
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
@@ -317,7 +317,7 @@ pub(super) const SOCIAL_UI: &[&str] = &[
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
"ScrollTemplates.xml",
|
||||
@@ -342,9 +342,19 @@ pub(super) const SOCIAL_UI: &[&str] = &[
|
||||
"Interface\\FrameXML\\PartyFrame.xml",
|
||||
"Interface\\FrameXML\\FriendsFrame.xml",
|
||||
"Interface\\FrameXML\\RaidFrame.xml",
|
||||
"Interface\\AddOns\\Blizzard_RaidUI\\Blizzard_RaidUI.xml",
|
||||
];
|
||||
|
||||
/// Load [`SOCIAL_UI`], then the raid tab's LoadOnDemand addon the way the app reaches it: seated
|
||||
/// off the chain as a registry row (1957) and loaded by the reference's own `RaidFrame_LoadUI`
|
||||
/// (UIParent.xml; 1967). Needs client data — the caller has checked with `wow_data_or_skip!`.
|
||||
pub(super) fn load_social_ui(s: &mut UiScript) {
|
||||
for f in SOCIAL_UI {
|
||||
load_ui_strict(s, f);
|
||||
}
|
||||
seat_chain_addon(s, "Blizzard_RaidUI");
|
||||
s.run("RaidFrame_LoadUI()").unwrap();
|
||||
}
|
||||
|
||||
pub(super) const BAG_UI: &[&str] = &[
|
||||
// The reference's own localized strings — `BACKPACK_TOOLTIP`, `EQUIP_CONTAINER`, `KEYRING`,
|
||||
// the `*_FONT_COLOR_CODE` pair. The app loads this at VM setup, ahead of the manifest
|
||||
@@ -370,7 +380,7 @@ pub(super) const BAG_UI: &[&str] = &[
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Cooldown.xml",
|
||||
// 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.
|
||||
|
||||
@@ -24,10 +24,19 @@ fn harness(extra: &[&str]) -> UiScript {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
// `FACTION_BAR_COLORS`, which the stock `GameTooltip_UnitColor` indexes on every unit hover:
|
||||
// the reference defines it at ReputationFrame.lua's file scope (1968).
|
||||
load_xml(&s, r"Interface\FrameXML\ReputationFrame.lua");
|
||||
for f in extra {
|
||||
load_xml(&s, f);
|
||||
}
|
||||
// The stock tooltip declares no size: it sizes from its lines through the font engine, as
|
||||
// the client's does (1968) — every test here reads its rect, so the fixed-width font is
|
||||
// that engine. And 1.12 ships detailed tips ON (`SHOW_NEWBIE_TIPS = "1"`, UIOptionsFrame_Init's;
|
||||
// ours in OptionsFrame.xml's uvar block) — a harness without the options file says so itself.
|
||||
s.set_text_measurer(Box::new(super::FixedWidthFont(6.0)));
|
||||
s.run("SHOW_NEWBIE_TIPS = \"1\"").unwrap();
|
||||
s
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ fn harness() -> UiScript {
|
||||
"UIParent.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
] {
|
||||
load_xml(&s, f);
|
||||
}
|
||||
@@ -193,11 +193,12 @@ fn an_addon_tooltip_from_the_template_gets_the_plate() {
|
||||
.any(|p| p.eq_ignore_ascii_case(r"Interface\Tooltips\UI-Tooltip-Border")),
|
||||
"…and its border: {drawn:?}"
|
||||
);
|
||||
// The Thicken deviation rides the template too — it is the shared plate, not one window's.
|
||||
// The template's children resolve against the CALLER's name — the status bar the reference's
|
||||
// template declares (`$parentStatusBar`), which TipBuddy anchors to by that global.
|
||||
assert!(
|
||||
s.eval::<bool>(r#"return getglobal("MyTipThicken") ~= nil"#)
|
||||
s.eval::<bool>(r#"return getglobal("MyTipStatusBar") ~= nil"#)
|
||||
.unwrap(),
|
||||
"$parentThicken resolves against the CALLER's name"
|
||||
"$parentStatusBar resolves against the CALLER's name"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
@@ -288,9 +289,9 @@ fn createframe_with_the_template_is_the_same_tooltip() {
|
||||
"Equip: Improves your chance to hit by 1%."
|
||||
);
|
||||
assert!(
|
||||
s.eval::<bool>(r#"return getglobal("BetterCharacterStatsTooltipThicken") ~= nil"#)
|
||||
s.eval::<bool>(r#"return getglobal("BetterCharacterStatsTooltipStatusBar") ~= nil"#)
|
||||
.unwrap(),
|
||||
"the plate came through CreateFrame's fourth argument too"
|
||||
"the template's children came through CreateFrame's fourth argument too"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ fn load_tot() -> UiScript {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UnitPopup.xml");
|
||||
|
||||
@@ -56,7 +56,7 @@ fn trainer_script_base() -> UiScript {
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
"ScrollTemplates.xml",
|
||||
|
||||
@@ -27,7 +27,9 @@ fn load_unit_frames(s: &UiScript) {
|
||||
load_xml(s, "Interface\\FrameXML\\TextStatusBar.xml");
|
||||
load_xml(s, r"Interface\FrameXML\MoneyFrame.lua");
|
||||
load_xml(s, r"Interface\FrameXML\MoneyFrame.xml");
|
||||
load_xml(s, "GameTooltip.xml");
|
||||
load_xml(s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
// `FACTION_BAR_COLORS` for the stock `GameTooltip_UnitColor` — ReputationFrame.lua's (1968).
|
||||
load_xml(s, r"Interface\FrameXML\ReputationFrame.lua");
|
||||
load_xml(s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
load_xml(s, "Interface\\FrameXML\\BasicControls.xml"); // `TEXT`, which UnitPopup.lua reads at file scope
|
||||
load_xml(s, "Interface\\FrameXML\\UnitPopup.xml");
|
||||
@@ -1220,7 +1222,8 @@ fn the_party_art_paints_over_the_bars() {
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\ReputationFrame.lua"); // FACTION_BAR_COLORS (1968)
|
||||
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.
|
||||
|
||||
@@ -29,7 +29,7 @@ fn load_popup_frames(s: &UiScript) {
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIMenu.xml",
|
||||
"Interface\\FrameXML\\ChatFrame.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
@@ -494,7 +494,7 @@ fn load_pet_menu_frames(s: &UiScript) {
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua", // `TEXT`, read at file scope below
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIMenu.xml",
|
||||
"Interface\\FrameXML\\ChatFrame.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
|
||||
@@ -23,7 +23,7 @@ fn harness() -> UiScript {
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml", // the map's continent/zone pickers initialize into it at OnLoad
|
||||
"ScrollTemplates.xml",
|
||||
// The blip templates, which WorldMapFrame.xml instantiates with inherits=. Not
|
||||
|
||||
@@ -26,11 +26,15 @@ pub(crate) fn harness() -> UiScript {
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"WorldStateFrame.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\WorldStateFrame.xml", // the reference's own (1972)
|
||||
] {
|
||||
load_xml(&s, f);
|
||||
}
|
||||
// The outdoor readout is gated on the options uvar `HIDE_OUTDOOR_WORLD_STATE == "0"`
|
||||
// (UIOptionsFrame.lua's default, ours in OptionsFrame.xml's uvar block); a harness without
|
||||
// the options file says so itself.
|
||||
s.run("HIDE_OUTDOOR_WORLD_STATE = \"0\"").unwrap();
|
||||
s
|
||||
}
|
||||
|
||||
@@ -164,7 +168,16 @@ fn the_dynamic_icon_is_a_second_slot_lit_only_by_the_taken_state() {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for quiet in [0, 1, 3] {
|
||||
// A zero state is not a quiet row but NO row: the stock `WorldStateAlwaysUpFrame_Update`
|
||||
// draws only `state > 0` (WorldStateFrame.lua) — our transcription had painted the shield at
|
||||
// zero too, and the reference's file retired that (1972).
|
||||
push(&mut s, vec![flag_row(0)]);
|
||||
let art = textures(&mut s);
|
||||
assert!(
|
||||
!art.iter().any(|p| p.contains("UI-PVP-Alliance")),
|
||||
"state 0 — the row is not drawn at all: {art:?}"
|
||||
);
|
||||
for quiet in [1, 3] {
|
||||
push(&mut s, vec![flag_row(quiet)]);
|
||||
let art = textures(&mut s);
|
||||
assert!(
|
||||
@@ -233,6 +246,24 @@ fn the_dynamic_icon_is_a_second_slot_lit_only_by_the_taken_state() {
|
||||
/// icon is optional, and the row must not collapse when the DBC column is empty.
|
||||
#[test]
|
||||
fn a_row_without_an_icon_still_shows_its_text() {
|
||||
let mut s = harness();
|
||||
s.fire_event("PLAYER_ENTERING_WORLD", vec![ScriptValue::Str("".into())]);
|
||||
push(
|
||||
&mut s,
|
||||
vec![WorldStateUiView {
|
||||
ui_state: 1,
|
||||
text: "Progress: 60".into(),
|
||||
..Default::default()
|
||||
}],
|
||||
);
|
||||
assert!(texts(&mut s).contains(&"Progress: 60".to_string()));
|
||||
}
|
||||
|
||||
/// An `ExtendedUI` row is not a text row at all — the stock update routes it to the extended
|
||||
/// kit (`ExtendedUI["CAPTUREPOINT"]`), which builds a capture BAR and never prints the text.
|
||||
/// Our transcription had shown the text; the reference's file draws the bar (1972).
|
||||
#[test]
|
||||
fn a_capture_point_row_is_a_bar_not_a_line_of_text() {
|
||||
let mut s = harness();
|
||||
s.fire_event("PLAYER_ENTERING_WORLD", vec![ScriptValue::Str("".into())]);
|
||||
push(
|
||||
@@ -245,7 +276,13 @@ fn a_row_without_an_icon_still_shows_its_text() {
|
||||
..Default::default()
|
||||
}],
|
||||
);
|
||||
assert!(texts(&mut s).contains(&"Progress: 60".to_string()));
|
||||
assert!(!texts(&mut s).contains(&"Progress: 60".to_string()));
|
||||
assert!(
|
||||
s.eval::<bool>("return WorldStateCaptureBar1 ~= nil and WorldStateCaptureBar1:IsShown()")
|
||||
.unwrap(),
|
||||
"the capture bar was built and shown"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The bindings' own edges, read from Lua the way an addon would. Two are easy to get wrong and
|
||||
@@ -389,9 +426,7 @@ fn the_visible_ink_sits_beside_its_label_not_adrift_of_it() {
|
||||
let label_mid_y = (text.top + text.bottom) * 0.5;
|
||||
|
||||
let (row_top, row_bottom) = s
|
||||
.eval::<(f64, f64)>(
|
||||
"local r = WorldStateAlwaysUpFrame1 return r:GetTop(), r:GetBottom()",
|
||||
)
|
||||
.eval::<(f64, f64)>("local r = AlwaysUpFrame1 return r:GetTop(), r:GetBottom()")
|
||||
.expect("the row resolved");
|
||||
let row_h = (row_top - row_bottom) as f32;
|
||||
|
||||
|
||||
@@ -445,6 +445,14 @@ fn feed_trade_skill(
|
||||
return;
|
||||
}
|
||||
script.set_trade_skill(fresh.clone());
|
||||
// The client has every product's and reagent's template cached by the time its list shows,
|
||||
// and `GetTradeSkillItemLink`/`GetTradeSkillReagentItemLink` never query — so the feed asks
|
||||
// for the templates the store lacks when the list lands, and the verbs read the answers (1973).
|
||||
if let Some(f) = &fresh {
|
||||
script.ask_item_templates(f.recipes.iter().flat_map(|r| {
|
||||
std::iter::once(r.product_item).chain(r.reagents.iter().map(|re| re.item))
|
||||
}));
|
||||
}
|
||||
match (&*last, &fresh) {
|
||||
(None, Some(f)) => {
|
||||
debug!(
|
||||
|
||||
@@ -32,7 +32,7 @@ const FILES: &[&str] = &[
|
||||
// `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\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"ScrollTemplates.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
@@ -41,9 +41,32 @@ const FILES: &[&str] = &[
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml", // the dialog engine (1960)
|
||||
"AuctionFrame.xml",
|
||||
// The reference's own addon (1971), its toc order: the window (which <Include>s its
|
||||
// templates) and the embedded dress-up pane. An integration test has no addon registry, so
|
||||
// the files load as chain files, the way the manifest's own entries do.
|
||||
// The embedded dress-up pane's OnLoad calls the dressing room's own `DressUpTexturePath`
|
||||
// (stock DressUpFrame.lua, on the chain since 1969) — the manifest seats that window above.
|
||||
"Interface\\FrameXML\\DressUpFrame.xml",
|
||||
"Interface\\AddOns\\Blizzard_AuctionUI\\Blizzard_AuctionUI.xml",
|
||||
"Interface\\AddOns\\Blizzard_AuctionUI\\Blizzard_AuctionDressUp.xml",
|
||||
];
|
||||
|
||||
/// [`load_ui`] with the Browse tab's class tree seated first: the stock addon reads
|
||||
/// `GetAuctionItemClasses()` at its LOAD (`AuctionFrameBrowse_OnLoad`), before any session, so
|
||||
/// the classes are login-scoped and must be there before the files are (1971).
|
||||
fn load_ui_with_classes(s: &mut UiScript) {
|
||||
s.set_auction_item_classes(vec![AuctionCategory {
|
||||
class_id: 4,
|
||||
name: "Armor".into(),
|
||||
subclasses: vec![AuctionSubCategory {
|
||||
sub_id: 1,
|
||||
name: "Cloth".into(),
|
||||
has_inv_types: true,
|
||||
}],
|
||||
}]);
|
||||
load_ui(s);
|
||||
}
|
||||
|
||||
fn load_ui(script: &UiScript) {
|
||||
// `common::load_ui`, not a local read: a manifest entry carrying a path separator is the
|
||||
// REFERENCE's own file and has to come off the player's chain, which `std::fs::read` under
|
||||
@@ -95,15 +118,6 @@ fn state(mut rows: Vec<AuctionItemRow>) -> AuctionState {
|
||||
lists[OWNER] = AuctionListState::default();
|
||||
AuctionState {
|
||||
lists,
|
||||
categories: vec![AuctionCategory {
|
||||
class_id: 4,
|
||||
name: "Armor".into(),
|
||||
subclasses: vec![AuctionSubCategory {
|
||||
sub_id: 1,
|
||||
name: "Cloth".into(),
|
||||
has_inv_types: true,
|
||||
}],
|
||||
}],
|
||||
deposit_percent: 5,
|
||||
}
|
||||
}
|
||||
@@ -126,8 +140,8 @@ fn seat_player(s: &mut UiScript, money: u64) {
|
||||
#[test]
|
||||
fn auction_frame_loads_and_key_regions_exist() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui_with_classes(&mut s);
|
||||
for name in [
|
||||
// The window, its three panes and its three tabs.
|
||||
"AuctionFrame",
|
||||
@@ -173,8 +187,8 @@ fn auction_frame_loads_and_key_regions_exist() {
|
||||
#[test]
|
||||
fn the_window_is_registered_doublewide() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui_with_classes(&mut s);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return UIPanelWindows['AuctionFrame'].area")
|
||||
.unwrap(),
|
||||
@@ -186,7 +200,7 @@ fn the_window_is_registered_doublewide() {
|
||||
fn auction_house_show_opens_the_window_on_the_browse_tab() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
load_ui_with_classes(&mut s);
|
||||
seat_player(&mut s, 500_000);
|
||||
s.set_auction(Some(state(vec![row(
|
||||
"Linen Cloth",
|
||||
@@ -244,7 +258,7 @@ fn auction_house_show_opens_the_window_on_the_browse_tab() {
|
||||
fn the_browse_list_populates_from_the_fed_snapshot() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
load_ui_with_classes(&mut s);
|
||||
seat_player(&mut s, 500_000);
|
||||
s.set_auction(Some(state(vec![
|
||||
// No bids yet: the row shows the seller's opening price as the current bid.
|
||||
@@ -344,7 +358,7 @@ fn the_browse_list_populates_from_the_fed_snapshot() {
|
||||
fn the_bid_and_buyout_gates_read_the_purse() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
load_ui_with_classes(&mut s);
|
||||
// 2 gold. Row 1 costs 10s to bid and 50s to buy out — affordable. Row 2 wants 5 gold.
|
||||
seat_player(&mut s, 20_000);
|
||||
s.set_auction(Some(state(vec![
|
||||
@@ -428,7 +442,7 @@ fn the_bid_and_buyout_gates_read_the_purse() {
|
||||
fn you_cannot_bid_on_your_own_auction() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
load_ui_with_classes(&mut s);
|
||||
seat_player(&mut s, 10_000_000);
|
||||
s.set_auction(Some(state(vec![row(
|
||||
"Linen Cloth",
|
||||
@@ -459,7 +473,7 @@ fn you_cannot_bid_on_your_own_auction() {
|
||||
fn search_reads_the_filters_and_nothing_queries_before_it() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
load_ui_with_classes(&mut s);
|
||||
seat_player(&mut s, 0);
|
||||
s.set_auction(Some(state(vec![])));
|
||||
s.set_auction_can_query(true);
|
||||
@@ -509,7 +523,7 @@ fn search_reads_the_filters_and_nothing_queries_before_it() {
|
||||
fn the_create_gate_and_the_deposit() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
load_ui_with_classes(&mut s);
|
||||
seat_player(&mut s, 100_000);
|
||||
s.set_auction(Some(state(vec![])));
|
||||
s.fire_event("AUCTION_HOUSE_SHOW", vec![]);
|
||||
@@ -589,6 +603,9 @@ fn the_create_gate_and_the_deposit() {
|
||||
// this exercises.
|
||||
s.run("SplitContainerItem(0, 1, 2)").unwrap();
|
||||
s.run("AuctionsItemButton:Click()").unwrap();
|
||||
// The stock create pane paints the slot on NEW_AUCTION_UPDATE, which the app fires the frame
|
||||
// the sell slot changes (`ui_auction`'s feed); a harness with no app fires it itself.
|
||||
s.fire_event("NEW_AUCTION_UPDATE", vec![]);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return AuctionsItemButtonName:GetText()")
|
||||
.unwrap(),
|
||||
@@ -604,6 +621,13 @@ fn the_create_gate_and_the_deposit() {
|
||||
.eval::<bool>("return AuctionsItemButtonCount:IsShown()")
|
||||
.unwrap());
|
||||
|
||||
// Dropping an item RESETS the form — the stock handler seeds the start price from the item's
|
||||
// sell price and zeroes the buyout (`AuctionSellItemButton_OnEvent`) — so the prices are typed
|
||||
// after the drop, as a player types them.
|
||||
s.run("MoneyInputFrame_SetCopper(StartPrice, 10000)")
|
||||
.unwrap();
|
||||
s.run("MoneyInputFrame_SetCopper(BuyoutPrice, 5000)")
|
||||
.unwrap();
|
||||
// With an item in the slot, the buyout-under-start error is the one the form explains out loud.
|
||||
s.run("AuctionsFrameAuctions_ValidateAuction()").unwrap();
|
||||
assert!(
|
||||
@@ -651,7 +675,7 @@ fn the_create_gate_and_the_deposit() {
|
||||
fn hiding_the_window_closes_the_session() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
load_ui_with_classes(&mut s);
|
||||
seat_player(&mut s, 0);
|
||||
s.set_auction(Some(state(vec![])));
|
||||
s.fire_event("AUCTION_HOUSE_SHOW", vec![]);
|
||||
@@ -680,7 +704,7 @@ fn hiding_the_window_closes_the_session() {
|
||||
fn paging_shows_the_turners_only_at_the_end_of_the_list() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
load_ui_with_classes(&mut s);
|
||||
seat_player(&mut s, 0);
|
||||
let mut rows = Vec::new();
|
||||
for i in 0..50 {
|
||||
@@ -736,7 +760,7 @@ fn paging_shows_the_turners_only_at_the_end_of_the_list() {
|
||||
fn a_row_hover_goes_through_the_reference_tooltip_verb() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
load_ui_with_classes(&mut s);
|
||||
s.set_auction(Some(state(vec![row("Copper Bar", 100, 500, 0, "Someone")])));
|
||||
s.fire_event("AUCTION_HOUSE_SHOW", vec![]);
|
||||
s.fire_event("AUCTION_ITEM_LIST_UPDATE", vec![]);
|
||||
|
||||
@@ -50,7 +50,7 @@ const FILES: &[&str] = &[
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml", // the dialog engine (1960)
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
// `CooldownFrameTemplate` and `CooldownFrame_SetTimer`. Not scenery: every one of the 20 slot
|
||||
// buttons runs `PaperDollItemSlotButton_Update` from its own OnLoad, which calls
|
||||
// `CooldownFrame_SetTimer` on `$parentCooldown` unconditionally (`PaperDollFrame.lua:692`) —
|
||||
|
||||
@@ -27,8 +27,13 @@ const FILES: &[&str] = &[
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml", // the dialog engine (1960)
|
||||
"GameTooltip.xml",
|
||||
"MailFrame.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
r"Interface\FrameXML\ItemButtonTemplate.xml", // the send tab's attachment slot inherits it
|
||||
// The stock tabs inherit `FriendsFrameTabTemplate`, and `inherits=` resolves at load — so the
|
||||
// social window and the kit it needs come first, as the reference's toc has them (1970).
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"Interface\\FrameXML\\FriendsFrame.xml",
|
||||
"Interface\\FrameXML\\MailFrame.xml",
|
||||
];
|
||||
|
||||
fn load_ui(script: &UiScript) {
|
||||
@@ -415,7 +420,8 @@ fn closing_a_taken_husk_deletes_it() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The expiry text pluralizes like the reference (GetText("DAYS_ABBR"): "Day"/"Days").
|
||||
/// The expiry text pluralizes like the reference (`GetText("DAYS_ABBR", nil, n)`: "Day"/"Days") —
|
||||
/// and carries the reference's own trailing space before the colour close (MailFrame.lua l.144).
|
||||
#[test]
|
||||
fn expiry_text_pluralizes_days() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
@@ -431,12 +437,12 @@ fn expiry_text_pluralizes_days() {
|
||||
assert_eq!(
|
||||
s.eval::<String>("return MailItem1ExpireTime:GetText()")
|
||||
.unwrap(),
|
||||
"|cff20ff2029 Days|r"
|
||||
"|cff20ff2029 Days |r"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return MailItem2ExpireTime:GetText()")
|
||||
.unwrap(),
|
||||
"|cff20ff201 Day|r"
|
||||
"|cff20ff201 Day |r"
|
||||
);
|
||||
assert!(s.take_errors().is_empty());
|
||||
}
|
||||
@@ -516,7 +522,8 @@ fn money_button_hover_shows_the_amount_tooltip() {
|
||||
s.fire_event("MAIL_INBOX_UPDATE", vec![]);
|
||||
s.run("MailItem1Button:Click()").unwrap();
|
||||
|
||||
s.run("BenillaOpenMailMoneyButton_OnEnter(OpenMailMoneyButton)")
|
||||
// The hover is the button's own inline handler (stock MailFrame.xml), reading `this`.
|
||||
s.run("this = OpenMailMoneyButton OpenMailMoneyButton:GetScript(\"OnEnter\")()")
|
||||
.unwrap();
|
||||
assert!(
|
||||
s.eval::<bool>("return GameTooltip:IsShown()").unwrap(),
|
||||
|
||||
@@ -32,7 +32,7 @@ const FILES: &[&str] = &[
|
||||
"UiPanels.xml",
|
||||
// `ToggleTalentFrame` lives here now, not in the window's own file (decision 1833).
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
// `UIPanelScrollFrameTemplate` — the stock scroll frame's whole substance: its `$parentScrollBar`
|
||||
// Slider AND its `<OnMouseWheel>`. Nothing else in the tree declares it, and a missing template
|
||||
// is a loader WARNING, not an error, so without this the window still built — just with no
|
||||
|
||||
@@ -29,7 +29,7 @@ const FILES: &[&str] = &[
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml", // the dialog engine (1960)
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
// The stock slot updates go through ItemButtonTemplate.lua's SetItemButton* helpers (1966).
|
||||
r"Interface\FrameXML\ItemButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\TradeFrame.xml",
|
||||
|
||||
@@ -31,11 +31,25 @@ const FILES: &[&str] = &[
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml", // the dialog engine (1960)
|
||||
"GameTooltip.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"ScrollTemplates.xml",
|
||||
"TradeSkillFrame.xml",
|
||||
"CraftFrame.xml",
|
||||
// The stock windows open from UIParent's own TRADE_SKILL_SHOW / CRAFT_SHOW arms (its
|
||||
// `*_LoadUI` then `*_Show`), not from a registration of their own — so UIParent.xml is a
|
||||
// dependency of the event path this suite drives (1973). With no addon registry here the
|
||||
// load reports ADDON_LOAD_FAILED through `message`, which is a shown frame, not an error.
|
||||
"UIParent.xml",
|
||||
// The reference's own addons (1973), off the chain — both inherit the trainer window's
|
||||
// list/detail kit, which the manifest seats above them. An integration test has no addon
|
||||
// registry, so the files load as chain files, the way the manifest's own entries do.
|
||||
r"Interface\FrameXML\ClassTrainerFrameTemplates.xml",
|
||||
// The reagent slots inherit `QuestItemTemplate` (147x41) through the addons' own row templates,
|
||||
// and the selection paints them with `SetItemButtonTexture`/`SetItemButtonCount`, which are
|
||||
// ItemButtonTemplate.lua's — both chain files the manifest seats above the addons.
|
||||
r"Interface\FrameXML\ItemButtonTemplate.xml",
|
||||
r"Interface\FrameXML\QuestFrameTemplates.xml",
|
||||
r"Interface\AddOns\Blizzard_TradeSkillUI\Blizzard_TradeSkillUI.xml",
|
||||
r"Interface\AddOns\Blizzard_CraftUI\Blizzard_CraftUI.xml",
|
||||
];
|
||||
|
||||
fn load_ui(script: &UiScript) {
|
||||
@@ -110,8 +124,7 @@ fn collapse_all_tab_and_filter_dropdowns_work_end_to_end() {
|
||||
s.set_trade_skill(Some(state()));
|
||||
s.fire_event("TRADE_SKILL_SHOW", vec![]);
|
||||
assert!(
|
||||
s.eval::<bool>("return BenillaTradeSkillFrame:IsShown()")
|
||||
.unwrap(),
|
||||
s.eval::<bool>("return TradeSkillFrame:IsShown()").unwrap(),
|
||||
"the window opens on TRADE_SKILL_SHOW"
|
||||
);
|
||||
// 2 headers + 3 recipes.
|
||||
@@ -119,32 +132,32 @@ fn collapse_all_tab_and_filter_dropdowns_work_end_to_end() {
|
||||
|
||||
// The CollapseAll tab carries its GlobalString text (a cross-chunk local would render nil).
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaTradeSkillCollapseAllButton:GetText()")
|
||||
s.eval::<String>("return TradeSkillCollapseAllButton:GetText()")
|
||||
.unwrap(),
|
||||
"All"
|
||||
);
|
||||
|
||||
// Fold everything through the tab: the click never calls Update() itself — the engine's
|
||||
// touched flag + TRADE_SKILL_UPDATE (pump) is the whole repaint path, the ref's own contract.
|
||||
s.run("BenillaTradeSkillCollapseAllButton:Click()").unwrap();
|
||||
s.run("TradeSkillCollapseAllButton:Click()").unwrap();
|
||||
pump(&mut s);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return GetNumTradeSkills()").unwrap(),
|
||||
2,
|
||||
"collapse-all leaves only the two headers"
|
||||
);
|
||||
s.run("BenillaTradeSkillCollapseAllButton:Click()").unwrap();
|
||||
s.run("TradeSkillCollapseAllButton:Click()").unwrap();
|
||||
pump(&mut s);
|
||||
assert_eq!(s.eval::<i64>("return GetNumTradeSkills()").unwrap(), 5);
|
||||
|
||||
// The dropdown capsules default to the "All …" texts (the OnShow/Initialize dance).
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaTradeSkillSubClassDropDownText:GetText()")
|
||||
s.eval::<String>("return TradeSkillSubClassDropDownText:GetText()")
|
||||
.unwrap(),
|
||||
"All Subclasses"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaTradeSkillInvSlotDropDownText:GetText()")
|
||||
s.eval::<String>("return TradeSkillInvSlotDropDownText:GetText()")
|
||||
.unwrap(),
|
||||
"All Slots"
|
||||
);
|
||||
@@ -160,7 +173,7 @@ fn collapse_all_tab_and_filter_dropdowns_work_end_to_end() {
|
||||
);
|
||||
|
||||
// A REAL menu-row click: open the SubClass menu, click "Trade Goods" (row 3: All + 2 groups).
|
||||
s.run("ToggleDropDownMenu(1, nil, BenillaTradeSkillSubClassDropDown)")
|
||||
s.run("ToggleDropDownMenu(1, nil, TradeSkillSubClassDropDown)")
|
||||
.unwrap();
|
||||
s.run("DropDownList1Button3:Click()").unwrap();
|
||||
pump(&mut s);
|
||||
@@ -175,16 +188,16 @@ fn collapse_all_tab_and_filter_dropdowns_work_end_to_end() {
|
||||
"Trade Goods"
|
||||
);
|
||||
// The capsule follows the picked row on the next initialize (OnShow re-runs it).
|
||||
s.run("BenillaTradeSkillSubClassDropDown:Hide() BenillaTradeSkillSubClassDropDown:Show()")
|
||||
s.run("TradeSkillSubClassDropDown:Hide() TradeSkillSubClassDropDown:Show()")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaTradeSkillSubClassDropDownText:GetText()")
|
||||
s.eval::<String>("return TradeSkillSubClassDropDownText:GetText()")
|
||||
.unwrap(),
|
||||
"Trade Goods"
|
||||
);
|
||||
|
||||
// The "All Subclasses" row (row 1) restores the full list.
|
||||
s.run("ToggleDropDownMenu(1, nil, BenillaTradeSkillSubClassDropDown)")
|
||||
s.run("ToggleDropDownMenu(1, nil, TradeSkillSubClassDropDown)")
|
||||
.unwrap();
|
||||
s.run("DropDownList1Button1:Click()").unwrap();
|
||||
pump(&mut s);
|
||||
@@ -200,18 +213,17 @@ fn craft_collapse_tab_loads_with_text_and_stays_hidden_for_a_flat_list() {
|
||||
load_ui(&s);
|
||||
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaCraftCollapseAllButton:GetText()")
|
||||
s.eval::<String>("return CraftCollapseAllButton:GetText()")
|
||||
.unwrap(),
|
||||
"All"
|
||||
);
|
||||
s.fire_event("CRAFT_SHOW", vec![]);
|
||||
assert!(
|
||||
s.eval::<bool>("return BenillaCraftFrame:IsShown()")
|
||||
.unwrap(),
|
||||
s.eval::<bool>("return CraftFrame:IsShown()").unwrap(),
|
||||
"the craft window opens on CRAFT_SHOW"
|
||||
);
|
||||
assert!(
|
||||
!s.eval::<bool>("return BenillaCraftExpandButtonFrame:IsShown()")
|
||||
!s.eval::<bool>("return CraftExpandButtonFrame:IsShown()")
|
||||
.unwrap(),
|
||||
"zero headers → the tab hides (ref l.269-282's own scan)"
|
||||
);
|
||||
@@ -240,13 +252,13 @@ fn reagent_slots_carry_the_questitemtemplate_shape_in_both_windows() {
|
||||
s.set_trade_skill(Some(state()));
|
||||
s.fire_event("TRADE_SKILL_SHOW", vec![]);
|
||||
s.fire_event("CRAFT_SHOW", vec![]);
|
||||
for w in ["BenillaTradeSkillReagent", "BenillaCraftReagent"] {
|
||||
for w in ["TradeSkillReagent", "CraftReagent"] {
|
||||
for i in 1..=3 {
|
||||
s.run(&format!("{w}{i}:Show()")).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
for w in ["BenillaTradeSkillReagent", "BenillaCraftReagent"] {
|
||||
for w in ["TradeSkillReagent", "CraftReagent"] {
|
||||
let num = |expr: &str| {
|
||||
s.eval::<f64>(&format!("return {expr}"))
|
||||
.unwrap_or_else(|e| panic!("{expr}: {e}"))
|
||||
@@ -276,22 +288,22 @@ fn reagent_slots_carry_the_questitemtemplate_shape_in_both_windows() {
|
||||
"{w} row step"
|
||||
);
|
||||
|
||||
// The icon: 39×39 flush in the row's TOPLEFT corner.
|
||||
// The icon (`$parentIconTexture`, the reference's own region name): 39×39 flush in the row's TOPLEFT corner.
|
||||
assert_eq!(
|
||||
(
|
||||
num(&format!("{w}1Icon:GetWidth()")),
|
||||
num(&format!("{w}1Icon:GetHeight()"))
|
||||
num(&format!("{w}1IconTexture:GetWidth()")),
|
||||
num(&format!("{w}1IconTexture:GetHeight()"))
|
||||
),
|
||||
(39.0, 39.0),
|
||||
"{w}1 icon"
|
||||
);
|
||||
assert_eq!(
|
||||
num(&format!("{w}1Icon:GetLeft()")),
|
||||
num(&format!("{w}1IconTexture:GetLeft()")),
|
||||
num(&format!("{w}1:GetLeft()")),
|
||||
"{w}1 icon flush left"
|
||||
);
|
||||
assert_eq!(
|
||||
num(&format!("{w}1Icon:GetTop()")),
|
||||
num(&format!("{w}1IconTexture:GetTop()")),
|
||||
num(&format!("{w}1:GetTop()")),
|
||||
"{w}1 icon flush top"
|
||||
);
|
||||
@@ -313,7 +325,7 @@ fn reagent_slots_carry_the_questitemtemplate_shape_in_both_windows() {
|
||||
"{w}1 plate size"
|
||||
);
|
||||
assert_eq!(
|
||||
num(&format!("{w}1NameFrame:GetLeft()")) - num(&format!("{w}1Icon:GetRight()")),
|
||||
num(&format!("{w}1NameFrame:GetLeft()")) - num(&format!("{w}1IconTexture:GetRight()")),
|
||||
-10.0,
|
||||
"{w}1 plate rides the icon's right edge"
|
||||
);
|
||||
@@ -338,17 +350,17 @@ fn reagent_slots_carry_the_questitemtemplate_shape_in_both_windows() {
|
||||
|
||||
// The count rides the ICON's bottom-right corner (-4, +1) — not a line below the name.
|
||||
assert_eq!(
|
||||
num(&format!("{w}1Count:GetRight()")) - num(&format!("{w}1Icon:GetRight()")),
|
||||
num(&format!("{w}1Count:GetRight()")) - num(&format!("{w}1IconTexture:GetRight()")),
|
||||
-4.0,
|
||||
"{w}1 count x"
|
||||
);
|
||||
assert_eq!(
|
||||
num(&format!("{w}1Count:GetBottom()")) - num(&format!("{w}1Icon:GetBottom()")),
|
||||
num(&format!("{w}1Count:GetBottom()")) - num(&format!("{w}1IconTexture:GetBottom()")),
|
||||
1.0,
|
||||
"{w}1 count y"
|
||||
);
|
||||
assert!(
|
||||
num(&format!("{w}1Count:GetBottom()")) >= num(&format!("{w}1Icon:GetBottom()")),
|
||||
num(&format!("{w}1Count:GetBottom()")) >= num(&format!("{w}1IconTexture:GetBottom()")),
|
||||
"{w}1 count sits ON the icon, not below the row"
|
||||
);
|
||||
}
|
||||
@@ -360,7 +372,7 @@ fn reagent_slots_carry_the_questitemtemplate_shape_in_both_windows() {
|
||||
/// Neither was subtle. Both survived because this suite drove the window's tabs, dropdowns and
|
||||
/// reagent slots without ever clicking or hovering a LIST ROW:
|
||||
///
|
||||
/// * `BenillaTradeSkillFrame_Update` addressed `BenillaTradeSkillHighlight` — the *texture* — for
|
||||
/// * `TradeSkillFrame_Update` addressed `TradeSkillHighlight` — the *texture* — for
|
||||
/// the Hide/SetPoint/Show that the reference does on `TradeSkillHighlightFrame`, the *frame*
|
||||
/// (Blizzard_TradeSkillUI.lua l.99/142-143; only l.200's `SetVertexColor` is the texture's).
|
||||
/// The frame is declared `hidden="true"`, so it never once became visible and no selection ever
|
||||
@@ -392,22 +404,22 @@ fn a_row_click_shows_the_selection_glow_and_a_row_hover_shows_nothing() {
|
||||
// any click — exactly what the director's screenshot should have shown and didn't.
|
||||
let glow_on_row = |s: &UiScript, n: i64| {
|
||||
let (glow, row) = (
|
||||
s.eval::<f64>("return BenillaTradeSkillHighlightFrame:GetTop()")
|
||||
s.eval::<f64>("return TradeSkillHighlightFrame:GetTop()")
|
||||
.unwrap(),
|
||||
s.eval::<f64>(&format!("return BenillaTradeSkillSkill{n}:GetTop()"))
|
||||
s.eval::<f64>(&format!("return TradeSkillSkill{n}:GetTop()"))
|
||||
.unwrap(),
|
||||
);
|
||||
(glow - row).abs() < 0.01
|
||||
};
|
||||
assert!(
|
||||
s.eval::<bool>("return BenillaTradeSkillHighlightFrame:IsShown()")
|
||||
s.eval::<bool>("return TradeSkillHighlightFrame:IsShown()")
|
||||
.unwrap(),
|
||||
"the show-time auto-selection glows — the FRAME, not just its texture"
|
||||
);
|
||||
assert!(glow_on_row(&s, 2), "and it is parked on the first recipe");
|
||||
|
||||
// A click on the OTHER Mail recipe moves it, rather than leaving it at the window's TOPLEFT.
|
||||
s.run("BenillaTradeSkillSkill3:Click()").unwrap();
|
||||
s.run("TradeSkillSkill3:Click()").unwrap();
|
||||
pump(&mut s);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return GetTradeSkillSelectionIndex()")
|
||||
@@ -416,17 +428,17 @@ fn a_row_click_shows_the_selection_glow_and_a_row_hover_shows_nothing() {
|
||||
"the click selected row 3"
|
||||
);
|
||||
assert!(
|
||||
s.eval::<bool>("return BenillaTradeSkillHighlightFrame:IsShown()")
|
||||
s.eval::<bool>("return TradeSkillHighlightFrame:IsShown()")
|
||||
.unwrap()
|
||||
&& glow_on_row(&s, 3),
|
||||
"the glow followed the click to row 3"
|
||||
);
|
||||
|
||||
// Fold every group away and no recipe row is visible to carry it.
|
||||
s.run("BenillaTradeSkillCollapseAllButton:Click()").unwrap();
|
||||
s.run("TradeSkillCollapseAllButton:Click()").unwrap();
|
||||
pump(&mut s);
|
||||
assert!(
|
||||
!s.eval::<bool>("return BenillaTradeSkillHighlightFrame:IsShown()")
|
||||
!s.eval::<bool>("return TradeSkillHighlightFrame:IsShown()")
|
||||
.unwrap(),
|
||||
"no recipe row on screen → no glow (headers never take the selection)"
|
||||
);
|
||||
@@ -434,30 +446,20 @@ fn a_row_click_shows_the_selection_glow_and_a_row_hover_shows_nothing() {
|
||||
// The hover half. Positive control first, so a `GetScript` that answered nil for everything
|
||||
// could not quietly pass the real assertions below: the reagent slot DOES tooltip on hover.
|
||||
assert!(
|
||||
s.eval::<bool>("return BenillaTradeSkillReagent1:GetScript(\"OnEnter\") ~= nil")
|
||||
s.eval::<bool>("return TradeSkillReagent1:GetScript(\"OnEnter\") ~= nil")
|
||||
.unwrap(),
|
||||
"control: a reagent slot has an OnEnter, so GetScript reports real handlers"
|
||||
);
|
||||
// The list row has no hover script at all, in the template or as a global.
|
||||
for handler in ["OnEnter", "OnLeave"] {
|
||||
assert!(
|
||||
s.eval::<bool>(&format!(
|
||||
"return BenillaTradeSkillSkill2:GetScript(\"{handler}\") == nil"
|
||||
))
|
||||
.unwrap(),
|
||||
"a list row must carry no {handler} — the reference's rows never tooltip"
|
||||
);
|
||||
}
|
||||
for f in [
|
||||
"BenillaTradeSkillSkillButton_OnEnter",
|
||||
"BenillaTradeSkillSkillButton_OnLeave",
|
||||
] {
|
||||
assert!(
|
||||
s.eval::<bool>(&format!("return getglobal(\"{f}\") == nil"))
|
||||
.unwrap(),
|
||||
"{f} must not exist"
|
||||
);
|
||||
}
|
||||
// The list row's hover is the trainer template's own — it recolours `$parentSubText` and
|
||||
// nothing else (ClassTrainerFrameTemplates.xml): driven with `this` set, it opens no tooltip.
|
||||
s.run("GameTooltip:Hide() this = TradeSkillSkill2 TradeSkillSkill2:GetScript(\"OnEnter\")()")
|
||||
.unwrap();
|
||||
assert!(
|
||||
!s.eval::<bool>("return GameTooltip:IsShown()").unwrap(),
|
||||
"a list row's hover opens no tooltip — the reference's rows never tooltip"
|
||||
);
|
||||
s.run("this = TradeSkillSkill2 TradeSkillSkill2:GetScript(\"OnLeave\")()")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// **The recipe list lights its rows white — under the cursor, and on the selected one.** The
|
||||
@@ -498,14 +500,14 @@ fn a_hovered_or_selected_recipe_row_paints_its_label_white() {
|
||||
// The label really is the button's own ButtonText now — a child FontString would leave
|
||||
// GetFontString() nil and take every per-state font with it.
|
||||
assert!(
|
||||
s.eval::<bool>("return BenillaTradeSkillSkill2:GetFontString() ~= nil")
|
||||
s.eval::<bool>("return TradeSkillSkill2:GetFontString() ~= nil")
|
||||
.unwrap(),
|
||||
"the row label is the Button's ButtonText, the only region per-state fonts reach"
|
||||
);
|
||||
|
||||
let row_color = |s: &mut UiScript, n: i64| -> [f32; 4] {
|
||||
let text = s
|
||||
.eval::<String>(&format!("return BenillaTradeSkillSkill{n}:GetText()"))
|
||||
.eval::<String>(&format!("return TradeSkillSkill{n}:GetText()"))
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
s.extract()
|
||||
@@ -528,11 +530,11 @@ fn a_hovered_or_selected_recipe_row_paints_its_label_white() {
|
||||
s.resolve();
|
||||
let (x, y) = (
|
||||
s.eval::<f64>(&format!(
|
||||
"return (BenillaTradeSkillSkill{n}:GetLeft() + BenillaTradeSkillSkill{n}:GetRight()) / 2"
|
||||
"return (TradeSkillSkill{n}:GetLeft() + TradeSkillSkill{n}:GetRight()) / 2"
|
||||
))
|
||||
.unwrap(),
|
||||
s.eval::<f64>(&format!(
|
||||
"return (BenillaTradeSkillSkill{n}:GetTop() + BenillaTradeSkillSkill{n}:GetBottom()) / 2"
|
||||
"return (TradeSkillSkill{n}:GetTop() + TradeSkillSkill{n}:GetBottom()) / 2"
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -567,7 +569,7 @@ fn a_hovered_or_selected_recipe_row_paints_its_label_white() {
|
||||
);
|
||||
|
||||
// Click row 3: the white follows the selection, and row 2 falls back to its difficulty colour.
|
||||
s.run("BenillaTradeSkillSkill3:Click()").unwrap();
|
||||
s.run("TradeSkillSkill3:Click()").unwrap();
|
||||
pump(&mut s);
|
||||
park_cursor_off_the_list(&mut s);
|
||||
assert_eq!(
|
||||
@@ -624,14 +626,14 @@ fn a_hovered_or_selected_craft_row_paints_its_label_white() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
assert!(
|
||||
s.eval::<bool>("return BenillaCraft1:GetFontString() ~= nil")
|
||||
s.eval::<bool>("return Craft1:GetFontString() ~= nil")
|
||||
.unwrap(),
|
||||
"the craft row name is the Button's ButtonText"
|
||||
);
|
||||
|
||||
let row_color = |s: &mut UiScript, n: i64| -> [f32; 4] {
|
||||
let text = s
|
||||
.eval::<String>(&format!("return BenillaCraft{n}:GetText()"))
|
||||
.eval::<String>(&format!("return Craft{n}:GetText()"))
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
s.extract()
|
||||
@@ -651,7 +653,7 @@ fn a_hovered_or_selected_craft_row_paints_its_label_white() {
|
||||
const MEDIUM: [f32; 4] = [1.0, 1.0, 0.0, 1.0]; // CraftTypeColor["medium"]
|
||||
|
||||
// Row 1 is the show-time selection; row 2 is not.
|
||||
s.run("SelectCraft(1); BenillaCraftFrame_Update()").unwrap();
|
||||
s.run("SelectCraft(1); CraftFrame_Update()").unwrap();
|
||||
s.resolve();
|
||||
s.mouse_move(1000.0, 20.0);
|
||||
assert_eq!(
|
||||
@@ -668,16 +670,16 @@ fn a_hovered_or_selected_craft_row_paints_its_label_white() {
|
||||
// Hover row 2.
|
||||
s.resolve();
|
||||
let (x, y) = (
|
||||
s.eval::<f64>("return (BenillaCraft2:GetLeft() + BenillaCraft2:GetRight()) / 2")
|
||||
s.eval::<f64>("return (Craft2:GetLeft() + Craft2:GetRight()) / 2")
|
||||
.unwrap(),
|
||||
s.eval::<f64>("return (BenillaCraft2:GetTop() + BenillaCraft2:GetBottom()) / 2")
|
||||
s.eval::<f64>("return (Craft2:GetTop() + Craft2:GetBottom()) / 2")
|
||||
.unwrap(),
|
||||
);
|
||||
s.mouse_move(x as f32, y as f32);
|
||||
assert_eq!(row_color(&mut s, 2), WHITE, "hovered: white");
|
||||
|
||||
// And the selection follows a click, releasing the old row's lock.
|
||||
s.run("SelectCraft(2); BenillaCraftFrame_Update()").unwrap();
|
||||
s.run("SelectCraft(2); CraftFrame_Update()").unwrap();
|
||||
s.resolve();
|
||||
s.mouse_move(1000.0, 20.0);
|
||||
assert_eq!(row_color(&mut s, 2), WHITE);
|
||||
|
||||
@@ -98,7 +98,9 @@ pub use stable_slot_prices::{load_stable_slot_prices, StableSlotPrices};
|
||||
mod page_text_material;
|
||||
pub use page_text_material::{load_page_text_material_catalog, PageTextMaterialCatalog};
|
||||
mod stationery;
|
||||
pub use stationery::{load_stationery_catalog, StationeryCatalog, STATIONERY_DEFAULT};
|
||||
pub use stationery::{
|
||||
load_stationery_catalog, StationeryCatalog, StationeryRow, STATIONERY_DEFAULT,
|
||||
};
|
||||
mod lock;
|
||||
pub use lock::{
|
||||
load_lock_catalog, LockCatalog, LockSlot, GO_STATE_ACTIVE, GO_STATE_ACTIVE_ALTERNATIVE,
|
||||
|
||||
@@ -7,12 +7,15 @@
|
||||
//!
|
||||
//! The 5875 schema was read byte-level from the real `patch.MPQ` file (VERIFIED at decision time):
|
||||
//! WDBC header `record_count = 5`, `field_count = 4`, `record_size = 16`, string block 62 bytes —
|
||||
//! four 4-byte fields, the third a string ref. Only fields 0 (`ID`) and 2 (`Texture`) are consumed
|
||||
//! here; fields 1 and 3 are unsigned columns whose *meaning* is unconfirmed (field 1 reads like a
|
||||
//! linked item id, field 3 a small enum) and which the reader never needs — declared as filler so
|
||||
//! the field indices align. The verified rows: `1/41 → STATIONERYTEST`, `61 → GMSTATIONERY`,
|
||||
//! `62 → AUCTIONSTATIONERY`, `64 → STATIONERY_VAL` (both target BLPs exist in the archive;
|
||||
//! MPQ path lookup is case-insensitive, so the uppercase DBC string resolves the mixed-case file).
|
||||
//! four 4-byte fields, the third a string ref: `{ID, ItemID, Texture, Flags}`. Fields 1 and 3
|
||||
//! were filler until wow-re carved the send side (`ui/scratch/stationery-bindings.md`, 1970):
|
||||
//! `ItemID` is the stationery ITEM the player buys or carries to use the paper, and `Flags & 1`
|
||||
//! marks the one always available (`41 Default Stationery`, BuyPrice 0). The client's usable list
|
||||
//! is `(Flags & 1 || the player carries ItemID) && the item's template is cached`, sorted by
|
||||
//! BuyPrice ascending — the `GetNumStationeries`/`GetStationeryInfo` surface. The verified rows:
|
||||
//! `1/41 → STATIONERYTEST`, `61 → GMSTATIONERY`, `62 → AUCTIONSTATIONERY`, `64 → STATIONERY_VAL`
|
||||
//! (both target BLPs exist in the archive; MPQ path lookup is case-insensitive, so the uppercase
|
||||
//! DBC string resolves the mixed-case file).
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -29,8 +32,23 @@ const STATIONERY: &str = "DBFilesClient\\Stationery.dbc";
|
||||
/// [`StationeryCatalog::DEFAULT_TEXTURE`] fallback.
|
||||
pub const STATIONERY_DEFAULT: u32 = 41;
|
||||
|
||||
/// `Stationery.dbc`: stationery id → texture basename (the `Interface\Stationery\<basename>N` stem).
|
||||
/// One `Stationery.dbc` row (the module doc's four columns).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct StationeryRow {
|
||||
/// The stationery id a mail carries on the wire and `SelectStationery` stores.
|
||||
pub id: u32,
|
||||
/// The stationery item — bought or carried to use this paper.
|
||||
pub item: u32,
|
||||
/// The texture basename (the `Interface\Stationery\<basename>N` stem).
|
||||
pub texture: String,
|
||||
/// `& 1`: always available, carried or not.
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
/// `Stationery.dbc`: stationery id → texture basename (the `Interface\Stationery\<basename>N` stem),
|
||||
/// and the rows whole for the send side's usable list.
|
||||
pub struct StationeryCatalog {
|
||||
rows: Vec<StationeryRow>,
|
||||
by_id: HashMap<u32, String>,
|
||||
}
|
||||
|
||||
@@ -48,6 +66,17 @@ impl StationeryCatalog {
|
||||
.map(String::as_str)
|
||||
.unwrap_or(Self::DEFAULT_TEXTURE)
|
||||
}
|
||||
|
||||
/// The texture basename for a stationery id the table carries — `None` for an id gap, which
|
||||
/// is what `GetSelectedStationeryTexture` answers with (no default there).
|
||||
pub fn texture_of(&self, id: u32) -> Option<&str> {
|
||||
self.by_id.get(&id).map(String::as_str)
|
||||
}
|
||||
|
||||
/// Every row, in DBC order.
|
||||
pub fn rows(&self) -> &[StationeryRow] {
|
||||
&self.rows
|
||||
}
|
||||
}
|
||||
|
||||
/// Load `Stationery.dbc` into an id → basename map (see the module doc for the verified schema).
|
||||
@@ -57,17 +86,24 @@ pub fn load_stationery_catalog(chain: &mut Chain) -> Result<StationeryCatalog> {
|
||||
.context("reading Stationery.dbc")?;
|
||||
let mut schema = Schema::new("Stationery");
|
||||
schema.add_field(SchemaField::new("ID", FieldType::UInt32));
|
||||
// Field 1: an unsigned column (reads like a linked item id) — unconsumed filler.
|
||||
schema.add_field(SchemaField::new("Item", FieldType::UInt32));
|
||||
schema.add_field(SchemaField::new("Texture", FieldType::String));
|
||||
// Field 3: a small unsigned enum — unconsumed filler.
|
||||
schema.add_field(SchemaField::new("Flags", FieldType::UInt32));
|
||||
let set = parse(&bytes, schema, "Stationery.dbc")?;
|
||||
let mut rows = Vec::new();
|
||||
let mut by_id = HashMap::new();
|
||||
for r in set.records() {
|
||||
if let (Some(id), Some(tex)) = (u32_at(r, 0), str_at(&set, r, 2)) {
|
||||
by_id.insert(id, tex);
|
||||
if let (Some(id), Some(item), Some(tex), Some(flags)) =
|
||||
(u32_at(r, 0), u32_at(r, 1), str_at(&set, r, 2), u32_at(r, 3))
|
||||
{
|
||||
by_id.insert(id, tex.clone());
|
||||
rows.push(StationeryRow {
|
||||
id,
|
||||
item,
|
||||
texture: tex,
|
||||
flags,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(StationeryCatalog { by_id })
|
||||
Ok(StationeryCatalog { rows, by_id })
|
||||
}
|
||||
|
||||
@@ -480,6 +480,9 @@ pub enum SessionEvent {
|
||||
/// One battleground queue slot's state (`SMSG_BATTLEFIELD_STATUS`), the client's three-slot
|
||||
/// queue that `AcceptBattlefieldPort` answers out of (decision 1963).
|
||||
BattlefieldStatus(crate::messages::BattlefieldStatus),
|
||||
/// The battleground scoreboard (`MSG_PVP_LOG_DATA`), rows in wire order — the app resolves
|
||||
/// the names, derives each row's team and pushes the sorted board (decision 1972).
|
||||
PvpLogData(crate::messages::PvpLogData),
|
||||
/// The meeting-stone queue state (`SMSG 0x295`): the area queued for and a status byte
|
||||
/// (decision 1963).
|
||||
MeetingStoneSetQueue { area: u32, status: u8 },
|
||||
|
||||
@@ -784,6 +784,7 @@ pub fn decode(packet: ServerPacket) -> Vec<SessionEvent> {
|
||||
vec![SessionEvent::AreaSpiritHealerTime { healer, ms }]
|
||||
}
|
||||
ServerPacket::BattlefieldStatus(status) => vec![SessionEvent::BattlefieldStatus(status)],
|
||||
ServerPacket::PvpLogData(data) => vec![SessionEvent::PvpLogData(data)],
|
||||
ServerPacket::MeetingStoneSetQueue { area, status } => {
|
||||
vec![SessionEvent::MeetingStoneSetQueue { area, status }]
|
||||
}
|
||||
|
||||
@@ -17,14 +17,21 @@ pub struct BattlefieldStatus {
|
||||
/// Read into `+0x10`; no Lua reader was found for it (parked by the carve).
|
||||
pub unknown: u32,
|
||||
pub status: u32,
|
||||
/// Status 2: the queue wait so far, in ms (`+0x14 = now + v`).
|
||||
/// Status 2: the port deadline's delta, in ms (`+0x14 = now + Δ`, `GetBattlefieldPortExpiration`).
|
||||
pub time_ms: Option<u32>,
|
||||
/// Status 3: the two dwords the client parks beside the selected battlefield.
|
||||
/// Status 3 (`battlefield-verb-family.md` §4.2, 1972): `(Δ₁, Δ₂)` — the instance's expiration
|
||||
/// delta (`[0xb6ebb8] = now + Δ₁`, `GetBattlefieldInstanceExpiration`) and its elapsed run time
|
||||
/// (`[0xb6ebbc] = now − Δ₂`, `GetBattlefieldInstanceRunTime`).
|
||||
pub in_progress: Option<(u32, u32)>,
|
||||
/// Status 1 (§4.2): `(estimated wait ms, raw; Δ waited)` — `[slot+0x18]` and
|
||||
/// `[slot+0x1c] = now − Δ`, the `GetBattlefieldEstimatedWaitTime`/`GetBattlefieldTimeWaited` pair.
|
||||
/// 1963's reader dropped this tail; the reference reads it (1972).
|
||||
pub queued: Option<(u32, u32)>,
|
||||
}
|
||||
|
||||
/// Parse `SMSG_BATTLEFIELD_STATUS`: `u32 slot`, `u32 mapId`, and — only for a non-zero map —
|
||||
/// `u8 bracket`, `u32`, `u32 status`, then the status-conditional tail.
|
||||
/// `u8 bracket`, `u32`, `u32 status`, then the status-conditional tail: two `u32` for status 1,
|
||||
/// one for status 2, two for status 3.
|
||||
pub(super) fn read_battlefield_status(r: &mut impl Read) -> io::Result<BattlefieldStatus> {
|
||||
let slot = read_u32_le(r)?;
|
||||
let map_id = read_u32_le(r)?;
|
||||
@@ -37,6 +44,7 @@ pub(super) fn read_battlefield_status(r: &mut impl Read) -> io::Result<Battlefie
|
||||
status: 0,
|
||||
time_ms: None,
|
||||
in_progress: None,
|
||||
queued: None,
|
||||
});
|
||||
}
|
||||
let bracket = read_u8(r)?;
|
||||
@@ -52,6 +60,11 @@ pub(super) fn read_battlefield_status(r: &mut impl Read) -> io::Result<Battlefie
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let queued = if status == 1 {
|
||||
Some((read_u32_le(r)?, read_u32_le(r)?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(BattlefieldStatus {
|
||||
slot,
|
||||
map_id,
|
||||
@@ -60,9 +73,82 @@ pub(super) fn read_battlefield_status(r: &mut impl Read) -> io::Result<Battlefie
|
||||
status,
|
||||
time_ms,
|
||||
in_progress,
|
||||
queued,
|
||||
})
|
||||
}
|
||||
|
||||
/// One scoreboard row of `MSG_PVP_LOG_DATA` (VERIFIED at the bytes, handler `0x4aab30`; wow-re
|
||||
/// `battlefield-verb-family.md` §2.3/§4.3, 1972). The client's 0x40-byte block, wire order.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PvpLogRow {
|
||||
pub guid: u64,
|
||||
/// Wire field 2 — read before the kills, stored at `+0x1c`.
|
||||
pub rank: u32,
|
||||
pub killing_blows: u32,
|
||||
pub honorable_kills: u32,
|
||||
pub deaths: u32,
|
||||
pub honor_gained: u32,
|
||||
/// The extra-stat dwords, at most eight stored — the client consumes and discards the rest.
|
||||
pub stats: Vec<u32>,
|
||||
}
|
||||
|
||||
/// `MSG_PVP_LOG_DATA` inbound: the whole scoreboard, rows in wire order (nothing here sorts).
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default)]
|
||||
pub struct PvpLogData {
|
||||
/// `u8 != 0` — the battleground has ended; `LeaveBattlefield` sends nothing until this is set.
|
||||
pub ended: bool,
|
||||
/// Read only when `ended`: `0` = Horde, `1` = Alliance (`GetBattlefieldWinner`).
|
||||
pub winner: Option<u8>,
|
||||
pub rows: Vec<PvpLogRow>,
|
||||
}
|
||||
|
||||
/// Parse `MSG_PVP_LOG_DATA` (§4.3): `u8 ended`, `u8 winner` iff ended, `u32 count`, `count` rows of
|
||||
/// `u64 guid, u32 rank, u32 kb, u32 hk, u32 deaths, u32 honor, u32 statCount, statCount × u32`.
|
||||
/// The client stores no more than eight stats per row and clamps nothing else — a count past its
|
||||
/// 80 blocks is its own anomaly (§10); ours keeps every row the wire carries.
|
||||
pub(super) fn read_pvp_log_data(r: &mut impl Read) -> io::Result<PvpLogData> {
|
||||
let ended = read_u8(r)? != 0;
|
||||
let winner = if ended { Some(read_u8(r)?) } else { None };
|
||||
let count = read_u32_le(r)?;
|
||||
let mut rows = Vec::with_capacity(count.min(80) as usize);
|
||||
for _ in 0..count {
|
||||
let guid = crate::wire::read_u64_le(r)?;
|
||||
let rank = read_u32_le(r)?;
|
||||
let killing_blows = read_u32_le(r)?;
|
||||
let honorable_kills = read_u32_le(r)?;
|
||||
let deaths = read_u32_le(r)?;
|
||||
let honor_gained = read_u32_le(r)?;
|
||||
let stat_count = read_u32_le(r)?;
|
||||
let mut stats = Vec::with_capacity(stat_count.min(8) as usize);
|
||||
for i in 0..stat_count {
|
||||
let v = read_u32_le(r)?;
|
||||
if i < 8 {
|
||||
stats.push(v);
|
||||
}
|
||||
}
|
||||
rows.push(PvpLogRow {
|
||||
guid,
|
||||
rank,
|
||||
killing_blows,
|
||||
honorable_kills,
|
||||
deaths,
|
||||
honor_gained,
|
||||
stats,
|
||||
});
|
||||
}
|
||||
Ok(PvpLogData {
|
||||
ended,
|
||||
winner,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
|
||||
/// Body of `CMSG_LEAVE_BATTLEFIELD` (VERIFIED, `0x4abe60`): `u32 mapId` — the active slot's map,
|
||||
/// or the literal 0 when no slot is active.
|
||||
pub fn leave_battlefield(map_id: u32) -> Vec<u8> {
|
||||
map_id.to_le_bytes().to_vec()
|
||||
}
|
||||
|
||||
/// Body of `CMSG_BATTLEFIELD_PORT` (VERIFIED, `0x4ab3b0`): `u32 mapId` then a genuinely
|
||||
/// one-byte `accept`, normalised to 0/1 before it reaches the wire.
|
||||
pub fn battlefield_port(map_id: u32, accept: bool) -> Vec<u8> {
|
||||
@@ -100,3 +186,60 @@ mod tests {
|
||||
assert_eq!(battlefield_port(489, false), vec![0xE9, 1, 0, 0, 0]);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod pvp_log_tests {
|
||||
use super::*;
|
||||
|
||||
/// The scoreboard reader: the winner byte only when ended, the rank BEFORE the kills, and no
|
||||
/// more than eight stats kept while every one is consumed.
|
||||
#[test]
|
||||
fn the_scoreboard_reads_its_conditional_winner_and_keeps_eight_stats() {
|
||||
let mut body = vec![1u8, 0u8, 1, 0, 0, 0];
|
||||
body.extend_from_slice(&7u64.to_le_bytes());
|
||||
for v in [5u32, 3, 9, 2, 120, 9] {
|
||||
body.extend_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
for v in 1u32..=9 {
|
||||
body.extend_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
let d = read_pvp_log_data(&mut body.as_slice()).unwrap();
|
||||
assert!(d.ended);
|
||||
assert_eq!(d.winner, Some(0));
|
||||
assert_eq!(d.rows.len(), 1);
|
||||
let r = &d.rows[0];
|
||||
assert_eq!(
|
||||
(
|
||||
r.guid,
|
||||
r.rank,
|
||||
r.killing_blows,
|
||||
r.honorable_kills,
|
||||
r.deaths,
|
||||
r.honor_gained
|
||||
),
|
||||
(7, 5, 3, 9, 2, 120)
|
||||
);
|
||||
assert_eq!(r.stats, vec![1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
|
||||
let body = vec![0u8, 0, 0, 0, 0];
|
||||
let d = read_pvp_log_data(&mut body.as_slice()).unwrap();
|
||||
assert!(!d.ended && d.winner.is_none() && d.rows.is_empty());
|
||||
assert_eq!(leave_battlefield(489), vec![0xE9, 1, 0, 0]);
|
||||
}
|
||||
|
||||
/// Status 1 carries the two wait dwords 1963's reader dropped.
|
||||
#[test]
|
||||
fn a_queued_status_carries_its_wait_pair() {
|
||||
let mut body = vec![0u8, 0, 0, 0];
|
||||
body.extend_from_slice(&489u32.to_le_bytes());
|
||||
body.push(3);
|
||||
body.extend_from_slice(&0u32.to_le_bytes());
|
||||
body.extend_from_slice(&1u32.to_le_bytes());
|
||||
body.extend_from_slice(&30000u32.to_le_bytes());
|
||||
body.extend_from_slice(&5000u32.to_le_bytes());
|
||||
let s = read_battlefield_status(&mut body.as_slice()).unwrap();
|
||||
assert_eq!(s.status, 1);
|
||||
assert_eq!(s.queued, Some((30000, 5000)));
|
||||
assert!(s.time_ms.is_none() && s.in_progress.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user