diff --git a/crates/benilla-app/assets/ui/BinderConfirm.xml b/crates/benilla-app/assets/ui/BinderConfirm.xml deleted file mode 100644 index f9f8dc9f..00000000 --- a/crates/benilla-app/assets/ui/BinderConfirm.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - self:RegisterEvent("CONFIRM_BINDER") - - BenillaBinderConfirmDriver_OnEvent() - - - diff --git a/crates/benilla-app/assets/ui/ConfirmSummon.xml b/crates/benilla-app/assets/ui/ConfirmSummon.xml deleted file mode 100644 index 0232d3c2..00000000 --- a/crates/benilla-app/assets/ui/ConfirmSummon.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - self:RegisterEvent("CONFIRM_SUMMON") - - BenillaConfirmSummonDriver_OnEvent() - - - diff --git a/crates/benilla-app/assets/ui/DeathFrame.xml b/crates/benilla-app/assets/ui/DeathFrame.xml deleted file mode 100644 index a00d638d..00000000 --- a/crates/benilla-app/assets/ui/DeathFrame.xml +++ /dev/null @@ -1,310 +0,0 @@ - - - - - - - - - - self:RegisterEvent("PLAYER_DEAD") - self:RegisterEvent("PLAYER_ALIVE") - self:RegisterEvent("PLAYER_UNGHOST") - self:RegisterEvent("RESURRECT_REQUEST") - self:RegisterEvent("CONFIRM_XP_LOSS") - self:RegisterEvent("CORPSE_IN_RANGE") - self:RegisterEvent("CORPSE_IN_INSTANCE") - self:RegisterEvent("CORPSE_OUT_OF_RANGE") - - BenillaDeathDriver_OnEvent() - - - diff --git a/crates/benilla-app/assets/ui/DuelFrame.xml b/crates/benilla-app/assets/ui/DuelFrame.xml deleted file mode 100644 index 59177c3c..00000000 --- a/crates/benilla-app/assets/ui/DuelFrame.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - self:RegisterEvent("DUEL_REQUESTED") - self:RegisterEvent("DUEL_OUTOFBOUNDS") - self:RegisterEvent("DUEL_INBOUNDS") - self:RegisterEvent("DUEL_FINISHED") - - BenillaDuelDriver_OnEvent() - - - diff --git a/crates/benilla-app/assets/ui/EnchantConfirm.xml b/crates/benilla-app/assets/ui/EnchantConfirm.xml deleted file mode 100644 index 0cdcfaa1..00000000 --- a/crates/benilla-app/assets/ui/EnchantConfirm.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - self:RegisterEvent("BIND_ENCHANT") - self:RegisterEvent("REPLACE_ENCHANT") - self:RegisterEvent("CURRENT_SPELL_CAST_CHANGED") - - BenillaEnchantConfirmDriver_OnEvent() - - - diff --git a/crates/benilla-app/assets/ui/FriendsFrame.xml b/crates/benilla-app/assets/ui/FriendsFrame.xml deleted file mode 100644 index 92b2bb15..00000000 --- a/crates/benilla-app/assets/ui/FriendsFrame.xml +++ /dev/null @@ -1,3405 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - BenillaGuildRankCheckbox_OnClick(self) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ScrollFrame_OnLoad() - - - - FauxScrollFrame_OnVerticalScroll(FRIENDS_FRAME_FRIEND_HEIGHT, FriendsList_Update) - - - - - BenillaFauxScrollFrame_OnMouseWheel(FriendsFrameFriendsScrollFrame, arg1) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ScrollFrame_OnLoad() - - - - FauxScrollFrame_OnVerticalScroll(FRIENDS_FRAME_IGNORE_HEIGHT, IgnoreList_Update) - - - - - BenillaFauxScrollFrame_OnMouseWheel(FriendsFrameIgnoreScrollFrame, arg1) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - this:ClearFocus() - WhoFrameEditBox_OnEnterPressed() - this:ClearFocus() - - - - - - - - - - ScrollFrame_OnLoad() - - - - FauxScrollFrame_OnVerticalScroll(FRIENDS_FRAME_WHO_HEIGHT, WhoList_Update) - - - - - - SetWhoToUI(0) - SetWhoToUI(1) - SetWhoToUI(0) - BenillaFauxScrollFrame_OnMouseWheel(WhoListScrollFrame, arg1) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - RegisterForSave("SHOW_OFFLINE_GUILD_MEMBERS") - this:RegisterEvent("VARIABLES_LOADED") - - - if event == "VARIABLES_LOADED" then - SetGuildRosterShowOffline(SHOW_OFFLINE_GUILD_MEMBERS) - this:SetChecked(GetGuildRosterShowOffline()) - end - - - BenillaGuildOfflineToggle_OnClick(self:GetChecked()) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ScrollFrame_OnLoad() - - - - FauxScrollFrame_OnVerticalScroll(FRIENDS_FRAME_GUILD_HEIGHT, GuildStatus_Update) - - - - - - - - - - - - - - - - - GuildRoster() - BenillaGuild_HideSatellitesExcept("GuildInfoFrame") - BenillaFauxScrollFrame_OnMouseWheel(GuildListScrollFrame, arg1) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - GuildControlPopupFrameDropDown_OnLoad() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - this:SetText(GuildControlGetRankName(1)) - this:ClearFocus() - - GuildControlPopup_MarkDirty() - - self:ClearFocus() - GuildControlPopup_MarkDirty() - - this:ClearFocus() - - - - - - GuildControlPopupFrame_OnLoad() - GuildControlPopupFrame_OnShow() - GuildControlPopupFrame_OnHide() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - GuildInfoFrame_ClaimFocus() - GuildInfoFrame_ClaimFocus() - - - - - - this:ClearFocus() - - GuildInfoEditBox_FollowCaret(self) - - - - - - - - BenillaGuildPane_TooltipBackdrop(self) - GuildInfoFrame_Update() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - BenillaGuildPane_TooltipBackdrop(self, 0.5, 0.25) - StaticPopup_Show("SET_GUILDPLAYERNOTE") - - - - - - - - - - - - - - - - - - - - BenillaGuildPane_TooltipBackdrop(self, 0.5, 0.25) - StaticPopup_Show("SET_GUILDOFFICERNOTE") - - - - - - - - - - - - - - - FriendsFrame_OnLoad() - FriendsFrame_OnShow() - FriendsFrame_OnHide() - FriendsFrame_OnEvent() - - - - - - - - - - this:RegisterEvent("GUILD_INVITE_REQUEST") - this:RegisterEvent("GUILD_INVITE_CANCEL") - - BenillaGuildInviteDriver_OnEvent() - - - diff --git a/crates/benilla-app/assets/ui/QuestShareFrame.xml b/crates/benilla-app/assets/ui/QuestShareFrame.xml deleted file mode 100644 index cc510130..00000000 --- a/crates/benilla-app/assets/ui/QuestShareFrame.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - self:RegisterEvent("QUEST_ACCEPT_CONFIRM") - BenillaQuestShareDriver_OnEvent() - - - diff --git a/crates/benilla-app/assets/ui/TalentWipeConfirm.xml b/crates/benilla-app/assets/ui/TalentWipeConfirm.xml deleted file mode 100644 index c906f822..00000000 --- a/crates/benilla-app/assets/ui/TalentWipeConfirm.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - self:RegisterEvent("CONFIRM_TALENT_WIPE") - - BenillaTalentWipeConfirmDriver_OnEvent() - - - diff --git a/crates/benilla-app/assets/ui/TrainerFrame.xml b/crates/benilla-app/assets/ui/TrainerFrame.xml deleted file mode 100644 index dc414b2d..00000000 --- a/crates/benilla-app/assets/ui/TrainerFrame.xml +++ /dev/null @@ -1,892 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - BenillaTrainerFilterDropDown_OnLoad() - BenillaTrainerFilterDropDown_OnLoad() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - BenillaTrainerList_OnVerticalScroll() - - - - - - - - - - SmallMoneyFrame_OnLoad(); MoneyFrame_SetType("STATIC"); - - - - - - - - - - - - - - - BenillaTrainerFrame_OnLoad(self) - BenillaTrainerFrame_OnEvent(self) - BenillaTrainerFrame_OnMouseWheel(arg1) - BenillaTrainerFrame_ApplyFilter(); PlaySound("igCharacterInfoOpen") - CloseTrainer(); PlaySound("igCharacterInfoClose") - - - diff --git a/crates/benilla-app/assets/ui/UIParent.xml b/crates/benilla-app/assets/ui/UIParent.xml index bcf1bc76..124f45a5 100644 --- a/crates/benilla-app/assets/ui/UIParent.xml +++ b/crates/benilla-app/assets/ui/UIParent.xml @@ -33,11 +33,48 @@ this:RegisterEvent("VARIABLES_LOADED") + this:RegisterEvent("TRAINER_SHOW") + this:RegisterEvent("TRAINER_CLOSED") + this:RegisterEvent("GUILD_INVITE_REQUEST") + this:RegisterEvent("GUILD_INVITE_CANCEL") this:RegisterEvent("MIRROR_TIMER_START") this:RegisterEvent("PLAYER_ENTERING_WORLD") this:RegisterEvent("READY_CHECK") this:RegisterEvent("START_LOOT_ROLL") this:RegisterEvent("CONFIRM_LOOT_ROLL") + -- The dialog-raising events below (ref UIParent.lua l.76-127), registered here + -- since the dialog engine became the reference's file (1960); what the engine does + -- not produce yet is left unregistered and named in that record, never armed blind. + this:RegisterEvent("PLAYER_DEAD") + this:RegisterEvent("PLAYER_ALIVE") + this:RegisterEvent("PLAYER_UNGHOST") + this:RegisterEvent("RESURRECT_REQUEST") + this:RegisterEvent("PARTY_INVITE_REQUEST") + this:RegisterEvent("PARTY_INVITE_CANCEL") + this:RegisterEvent("PLAYER_CAMPING") + this:RegisterEvent("PLAYER_QUITING") + this:RegisterEvent("LOGOUT_CANCEL") + this:RegisterEvent("LOOT_BIND_CONFIRM") + this:RegisterEvent("EQUIP_BIND_CONFIRM") + this:RegisterEvent("AUTOEQUIP_BIND_CONFIRM") + this:RegisterEvent("USE_BIND_CONFIRM") + this:RegisterEvent("DELETE_ITEM_CONFIRM") + this:RegisterEvent("QUEST_ACCEPT_CONFIRM") + this:RegisterEvent("CURSOR_UPDATE") + this:RegisterEvent("DUEL_REQUESTED") + this:RegisterEvent("DUEL_OUTOFBOUNDS") + this:RegisterEvent("DUEL_INBOUNDS") + this:RegisterEvent("DUEL_FINISHED") + this:RegisterEvent("CONFIRM_XP_LOSS") + this:RegisterEvent("CORPSE_IN_RANGE") + this:RegisterEvent("CORPSE_IN_INSTANCE") + this:RegisterEvent("CORPSE_OUT_OF_RANGE") + this:RegisterEvent("BIND_ENCHANT") + this:RegisterEvent("REPLACE_ENCHANT") + this:RegisterEvent("CURRENT_SPELL_CAST_CHANGED") + this:RegisterEvent("CONFIRM_TALENT_WIPE") + this:RegisterEvent("CONFIRM_BINDER") + this:RegisterEvent("CONFIRM_SUMMON") UIParent_OnEvent(event) - if FCF_OnUpdate then FCF_OnUpdate(arg1) end + if FCF_OnUpdate then FCF_OnUpdate(arg1) end if ButtonPulse_OnUpdate then ButtonPulse_OnUpdate(arg1) end if UnitPopup_OnUpdate then UnitPopup_OnUpdate(arg1) end @@ -337,6 +374,13 @@ UIParentLoadAddOn("Blizzard_GMSurveyUI") end + -- ref UIParent.lua l.162-164, verbatim — the trainer window's loader, reached from the + -- TRAINER_SHOW arm below. The addon is a LoadOnDemand registry row read off the chain + -- (`ui_script::addons`, 1957), so this is the reference's own mechanism, not a stand-in. + function ClassTrainerFrame_LoadUI() + UIParentLoadAddOn("Blizzard_TrainerUI") + 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. @@ -379,6 +423,35 @@ end return end + -- The trainer arms (ref UIParent.lua l.590-602), verbatim: `Blizzard_TrainerUI` is a + -- LoadOnDemand addon off the player's chain, loaded here on the first trainer the + -- way the reference loads it — which is when its ADDON_LOADED arm pushes the saved + -- filters, after the list packet that resets them (1957). The guards are the + -- reference's own, and are what keeps the per-window harnesses that load this file + -- without the trainer safe. + if ( event == "TRAINER_SHOW" ) then + ClassTrainerFrame_LoadUI() + if ( ClassTrainerFrame_Show ) then + ClassTrainerFrame_Show() + end + return + end + if ( event == "TRAINER_CLOSED" ) then + if ( ClassTrainerFrame_Hide ) then + ClassTrainerFrame_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 + StaticPopup_Show("GUILD_INVITE", arg1, arg2) + return + end + if ( event == "GUILD_INVITE_CANCEL" ) then + StaticPopup_Hide("GUILD_INVITE") + return + end -- The mirror bars' START driver (ref UIParent.lua l.97 + l.374-377). It lived on a -- hidden `MirrorTimerDriver` frame in our own MirrorTimer.xml until 1751 window 7, -- on the reasoning that "our UIParent.xml is script-only — it has no frame of its own @@ -428,6 +501,190 @@ end return end + -- ── The dialog-raising arms, ref UIParent.lua l.237-268 and l.284-360, l.378-453, + -- l.533-552, verbatim (1960). Until the dialog engine became the reference's own file + -- these rode nine hidden driver frames — one per dialog file, plus two in UiPanels.xml + -- — each explaining that it existed because benilla had no UIParent OnEvent to put the + -- arm in. It does, and the reference's placement is the one that survives the dialogs + -- being someone else's. Two guards are ours, each marked. ───────────────────────────── + if ( event == "PLAYER_DEAD" ) then + if ( not StaticPopup_Visible("DEATH") ) then + CloseAllWindows(1) + if ( GetReleaseTimeRemaining() > 0 or GetReleaseTimeRemaining() == -1 ) then + StaticPopup_Show("DEATH") + end + end + return + end + if ( event == "PLAYER_ALIVE" ) then + StaticPopup_Hide("DEATH") + StaticPopup_Hide("RESURRECT_NO_SICKNESS") + return + end + if ( event == "PLAYER_UNGHOST" ) then + StaticPopup_Hide("RESURRECT") + StaticPopup_Hide("RESURRECT_NO_SICKNESS") + StaticPopup_Hide("RESURRECT_NO_TIMER") + StaticPopup_Hide("SKINNED") + StaticPopup_Hide("SKINNED_REPOP") + return + end + if ( event == "RESURRECT_REQUEST" ) then + if ( ResurrectHasSickness() ) then + StaticPopup_Show("RESURRECT", arg1) + elseif ( ResurrectHasTimer() ) then + StaticPopup_Show("RESURRECT_NO_SICKNESS", arg1) + else + StaticPopup_Show("RESURRECT_NO_TIMER", arg1) + end + return + end + if ( event == "PARTY_INVITE_REQUEST" ) then + StaticPopup_Show("PARTY_INVITE", arg1) + return + end + if ( event == "PARTY_INVITE_CANCEL" ) then + StaticPopup_Hide("PARTY_INVITE") + return + end + if ( event == "PLAYER_CAMPING" ) then + StaticPopup_Show("CAMP") + return + end + if ( event == "PLAYER_QUITING" ) then + StaticPopup_Show("QUIT") + return + end + if ( event == "LOGOUT_CANCEL" ) then + StaticPopup_Hide("CAMP") + StaticPopup_Hide("QUIT") + return + end + if ( event == "LOOT_BIND_CONFIRM" ) then + local dialog = StaticPopup_Show("LOOT_BIND") + if ( dialog ) then + dialog.data = arg1 + end + return + end + if ( event == "EQUIP_BIND_CONFIRM" ) then + StaticPopup_Hide("AUTOEQUIP_BIND") + local dialog = StaticPopup_Show("EQUIP_BIND") + if ( dialog ) then + dialog.data = arg1 + end + return + end + if ( event == "AUTOEQUIP_BIND_CONFIRM" ) then + StaticPopup_Hide("EQUIP_BIND") + local dialog = StaticPopup_Show("AUTOEQUIP_BIND") + if ( dialog ) then + dialog.data = arg1 + end + return + end + if ( event == "USE_BIND_CONFIRM" ) then + StaticPopup_Show("USE_BIND") + return + end + if ( event == "DELETE_ITEM_CONFIRM" ) then + -- Check quality + if ( arg2 >= 3 ) then + StaticPopup_Show("DELETE_GOOD_ITEM", arg1) + else + StaticPopup_Show("DELETE_ITEM", arg1) + end + return + end + if ( event == "QUEST_ACCEPT_CONFIRM" ) then + StaticPopup_Show("QUEST_ACCEPT", arg1, arg2) + return + end + if ( event == "CURSOR_UPDATE" ) then + StaticPopup_Hide("EQUIP_BIND") + StaticPopup_Hide("AUTOEQUIP_BIND") + return + end + if ( event == "DUEL_REQUESTED" ) then + StaticPopup_Show("DUEL_REQUESTED", arg1) + return + end + if ( event == "DUEL_OUTOFBOUNDS" ) then + StaticPopup_Show("DUEL_OUTOFBOUNDS") + return + end + if ( event == "DUEL_INBOUNDS" ) then + StaticPopup_Hide("DUEL_OUTOFBOUNDS") + return + end + if ( event == "DUEL_FINISHED" ) then + StaticPopup_Hide("DUEL_REQUESTED") + StaticPopup_Hide("DUEL_OUTOFBOUNDS") + return + end + if ( event == "CONFIRM_XP_LOSS" ) then + local resSicknessTime = GetResSicknessDuration() + if ( resSicknessTime ) then + local dialog = StaticPopup_Show("XP_LOSS", resSicknessTime) + if ( dialog ) then + dialog.data = resSicknessTime + end + else + local dialog = StaticPopup_Show("XP_LOSS_NO_SICKNESS") + if ( dialog ) then + dialog.data = 1 + end + end + -- Ours: the reference's `HideUIPanel(GossipFrame)` unguarded — the per-window + -- harnesses load this file without the gossip window. + if ( GossipFrame ) then + HideUIPanel(GossipFrame) + end + return + end + if ( event == "CORPSE_IN_RANGE" ) then + StaticPopup_Show("RECOVER_CORPSE") + return + end + if ( event == "CORPSE_IN_INSTANCE" ) then + StaticPopup_Show("RECOVER_CORPSE_INSTANCE") + return + end + if ( event == "CORPSE_OUT_OF_RANGE" ) then + StaticPopup_Hide("RECOVER_CORPSE") + StaticPopup_Hide("RECOVER_CORPSE_INSTANCE") + StaticPopup_Hide("XP_LOSS") + return + end + if ( event == "BIND_ENCHANT" ) then + StaticPopup_Show("BIND_ENCHANT") + return + end + if ( event == "REPLACE_ENCHANT" ) then + StaticPopup_Show("REPLACE_ENCHANT", arg1, arg2) + return + end + if ( event == "CURRENT_SPELL_CAST_CHANGED" ) then + StaticPopup_Hide("BIND_ENCHANT") + StaticPopup_Hide("REPLACE_ENCHANT") + StaticPopup_Hide("TRADE_REPLACE_ENCHANT") + return + end + if ( event == "CONFIRM_TALENT_WIPE" ) then + local dialog = StaticPopup_Show("CONFIRM_TALENT_WIPE") + if ( dialog ) then + MoneyFrame_Update(dialog:GetName().."MoneyFrame", arg1) + end + return + end + if ( event == "CONFIRM_BINDER" ) then + StaticPopup_Show("CONFIRM_BINDER", arg1) + return + end + if ( event == "CONFIRM_SUMMON" ) then + StaticPopup_Show("CONFIRM_SUMMON") + return + end -- The ready-check popup's opener (decision 1549), ref UIParent.lua l.570-572. The -- event is the engine's own `0x218`, fired by the `MSG_RAID_READY_CHECK` open -- handler; the frame and `ShowReadyCheck` live in RaidFrame.xml, so this is the one diff --git a/crates/benilla-app/assets/ui/UiPanels.xml b/crates/benilla-app/assets/ui/UiPanels.xml index af7b932b..f997b870 100644 --- a/crates/benilla-app/assets/ui/UiPanels.xml +++ b/crates/benilla-app/assets/ui/UiPanels.xml @@ -1001,1196 +1001,6 @@ end end - -- ── The shared dialog IS the ref's StaticPopup engine (decision 0308 §3) ───────────────── - -- Re-expressed from the patch-chain StaticPopup.lua (mpqx extraction, decision 0246): - -- the StaticPopupDialogs registry + StaticPopup_Show(which, arg1, arg2) + the OnUpdate - -- countdown/StartDelay machinery — the death arc's dialogs exercise nearly all of it, and - -- StaticPopup_Show/StaticPopupDialogs are 1.14 addon-API surface (0068). v1 scope: - -- · FOUR dialog instances, the reference's own count. It was 2 for a long time, on the - -- reasoning that the deepest nesting exercised was decline-res re-showing DEATH (Show - -- precedes the click's hide, so a free second instance is required) and that it should - -- "grow toward 4 when a third coexists". 1751 window 7 supplied that third caller from - -- an unexpected direction: the reference's own `MirrorTimer_Show` bounds its free-bar - -- search by this constant rather than by `MIRRORTIMER_NUMTIMERS` (MirrorTimer.lua - -- l.32, a copy-paste from StaticPopup.lua), so at 2 the third mirror bar was - -- unreachable for a new timer. The coupling is the reference's bug and this is not the - -- file to fix it in; matching its count is. - -- · info fields honored: text, button1, button2, DisplayButton2, OnShow(data), - -- OnHide(data), OnAccept(data, data2) (truthy return = keep open — RECOVER_CORPSE - -- waits for the server, XP_LOSS's first Accept re-asks), OnCancel(data, reason) - -- (reason ∈ "clicked"|"timeout"|"override"), OnUpdate(elapsed, dialog), timeout, - -- StartDelay, delayText, sound, whileDead, hideOnEscape, cancels, showAlert (the - -- 420 widen + DialogAlertIcon, decision 1068). - -- · hasEditBox + maxLetters + all three box hooks — EditBoxOnEnterPressed, - -- EditBoxOnTextChanged, EditBoxOnEscapePressed — are live (decision 0668's - -- ADD_FRIEND/ADD_IGNORE are the first customers; 1743's DELETE_GOOD_ITEM is the - -- first to gate button1 on what is typed): the box shows, caps its length, - -- opens empty, and re-anchors the buttons under itself. - -- · hasWideEditBox is live too (the guild arc's SET_GUILDMOTD / SET_GUILDPLAYERNOTE / - -- SET_GUILDOFFICERNOTE, registered in FriendsFrame.xml): the 350x64 centred box - -- replaces the narrow one, and the dialog widens to 420. See the widen arm below for - -- the reference's own oddity — the widen is keyed on `which`, not on hasWideEditBox. - -- · hasMoneyFrame is live (decision 1580's CONFIRM_TALENT_WIPE is the first customer, - -- and the reference's other five are all price confirms): a SmallMoneyFrameTemplate - -- child under the text, shown per entry, with the buttons dropping to -24 to clear it - -- and the frame growing 16px. The AMOUNT is not the engine's — the entry's own - -- OnShow/the event arm calls MoneyFrame_Update, exactly as the reference does. - -- · interruptCinematic is LIVE as of the cinematic arc: a dialog without it is - -- refused while one plays, the ref's own l.1454 gate (it was inert only while - -- 0196 acked every cinematic instantly, so nothing could be playing). - -- · Accepted but v1-inert: exclusive, notClosableByLogout, preferredIndex. - -- · text_arg1/2 stash on the DIALOG frame (the ref stashes them on the Text region; - -- our regions don't carry Lua fields). - -- · GetText("SECONDS"/"MINUTES") localization collapses to English singular/plural. - -- · Button widths stay the template's fixed 120 (the ref widens past 120/110 via - -- GetTextWidth; the widen law joins when a longer label first needs it). - -- · Resize runs per tick while shown, not only on text swaps: GetHeight's measure has a - -- frame of latency (the measured-width convention below), so the height self-corrects - -- the frame after any text change. - STATICPOPUP_NUMDIALOGS = 4 - StaticPopupDialogs = {} - - local function StaticPopupTimeUnit(n, minutes) - if minutes then - if n == 1 then return "minute" end - return "minutes" - end - if n == 1 then return "second" end - return "seconds" - end - - -- ref StaticPopup.lua l.1414-1426, `data` included. Ours took `which` alone until 1793 - -- found the arity drift; the reference's second argument is what makes a `multiple` dialog - -- addressable, and dropping it is silent in both directions (Lua discards a surplus - -- argument without complaint). - -- - -- The `info.multiple` gate is the reference's own and is why `data` is not simply an - -- equality: a dialog kind declared `multiple` can have several instances up at once, so it - -- is matched by data; every other kind has at most one, so its data is not consulted at - -- all. An unknown `which` answers nil rather than scanning — the reference's own early out. - function StaticPopup_FindVisible(which, data) - local info = StaticPopupDialogs[which] - if not info then - return nil - end - for index = 1, STATICPOPUP_NUMDIALOGS do - local frame = getglobal("StaticPopup" .. index) - if frame:IsShown() and frame.which == which - and (not info.multiple or frame.data == data) then - return frame - end - end - return nil - end - - -- Ref l.1870-1877 (returns the frame NAME — the ref's contract). - function StaticPopup_Visible(which) - local frame = StaticPopup_FindVisible(which) - if frame then - return frame:GetName() - end - return nil - end - - -- ref StaticPopup.lua l.1703-1710, `data` included (1793). With a `data` the reference - -- hides only the instance carrying it; without one it hides every instance of the kind, - -- which is what every one-argument caller means and gets. - -- - -- Note the reference does NOT test `IsShown()` here, where `FindVisible` does — hiding an - -- already-hidden dialog is a no-op, and the missing test is not an oversight to correct: - -- `Hide()` on a hidden frame still runs nothing, and adding the guard would change which - -- frames the loop touches for no visible difference. Transcribed as it is. - function StaticPopup_Hide(which, data) - for index = 1, STATICPOPUP_NUMDIALOGS do - local dialog = getglobal("StaticPopup" .. index) - if dialog.which == which and (not data or data == dialog.data) then - dialog:Hide() - end - end - end - - -- Ref l.1428-1438, all three legs. - -- - -- Note the ref measures the NARROW `EditBox` for the height even when the shown box is the - -- wide one — the wide box is 64 tall, anchored CENTER, and floats over a frame sized for a - -- 32-tall box. Transcribed as written: the guild MOTD dialog's look is that arithmetic. - -- - -- The money leg does NOT measure the coin row either: it is the plain height plus a flat - -- 16, which is the room the -24 button drop below leaves for it. Same arithmetic, same - -- reason — the ref sizes these from constants, not from what is in the frame. - function StaticPopup_Resize(dialog, which) - local text = getglobal(dialog:GetName() .. "Text") - local button1 = getglobal(dialog:GetName() .. "Button1") - local editBox = getglobal(dialog:GetName() .. "EditBox") - local info = StaticPopupDialogs[which] - if info and info.hasEditBox then - dialog:SetHeight(16 + text:GetHeight() + 8 + editBox:GetHeight() + 8 - + button1:GetHeight() + 16) - elseif info and info.hasMoneyFrame then - dialog:SetHeight(16 + text:GetHeight() + 8 + button1:GetHeight() + 32) - else - dialog:SetHeight(16 + text:GetHeight() + 8 + button1:GetHeight() + 16) - end - end - - -- Ref l.1441-1700. Returns the dialog, or nil when refused (whileDead gate / no free - -- instance) — calling info.OnCancel() on refusal exactly as the ref does. - function StaticPopup_Show(which, text_arg1, text_arg2, data) - local info = StaticPopupDialogs[which] - if not info then - return nil - end - - -- The whileDead gate (ref l.1447-1452). UnitIsDeadOrGhost lands with the death arc's - -- ghost predicate — guarded like CursorHasItem in ToggleGameMenu until then. - if UnitIsDeadOrGhost and UnitIsDeadOrGhost("player") and not info.whileDead then - if info.OnCancel then - info.OnCancel() - end - return nil - end - - -- The cinematic gate (ref l.1454-1459), the whileDead gate's twin: a fly-by owns the - -- screen, so a dialog that has not declared itself worth interrupting one is cancelled - -- rather than raised over it. `InCinematic` is guarded like the predicate above so a - -- VM without the binding behaves as it did before. - if InCinematic and InCinematic() and not info.interruptCinematic then - if info.OnCancel then - info.OnCancel() - end - return nil - end - - -- Showing X hides a visible X.cancels with an "override" cancel (ref l.1470-1485). - if info.cancels then - local frame = StaticPopup_FindVisible(info.cancels) - if frame then - frame:Hide() - local OnCancel = StaticPopupDialogs[frame.which].OnCancel - if OnCancel then - OnCancel(frame.data, "override") - end - end - end - - -- Showing DEATH sweeps every visible non-whileDead dialog (ref l.1501-1512). - if which == "DEATH" then - for index = 1, STATICPOPUP_NUMDIALOGS do - local frame = getglobal("StaticPopup" .. index) - if frame:IsShown() and not StaticPopupDialogs[frame.which].whileDead then - frame:Hide() - local OnCancel = StaticPopupDialogs[frame.which].OnCancel - if OnCancel then - OnCancel(frame.data, "override") - end - end - end - end - - -- A visible same-type dialog is overridden in place; else take a free instance - -- (ref l.1515-1553). - local dialog = StaticPopup_FindVisible(which) - if dialog then - if info.OnCancel then - info.OnCancel(dialog.data, "override") - end - dialog:Hide() - end - if not dialog then - for i = 1, STATICPOPUP_NUMDIALOGS do - local frame = getglobal("StaticPopup" .. i) - if not frame:IsShown() then - dialog = frame - break - end - end - end - if not dialog then - if info.OnCancel then - info.OnCancel() - end - return nil - end - - -- The text. Countdown/delay dialogs start blank — their OnUpdate writes it - -- (ref l.1556-1574's which-list, verbatim). - local text = getglobal(dialog:GetName() .. "Text") - if (which == "DEATH") or (which == "CAMP") or (which == "QUIT") - or (which == "DUEL_OUTOFBOUNDS") or (which == "RECOVER_CORPSE") - or (which == "RESURRECT") or (which == "RESURRECT_NO_SICKNESS") - or (which == "INSTANCE_BOOT") or (which == "CONFIRM_SUMMON") - or (which == "AREA_SPIRIT_HEAL") then - text:SetText(" ") - dialog.text_arg1 = text_arg1 - dialog.text_arg2 = text_arg2 - else - text:SetText(format(info.text, text_arg1, text_arg2)) - end - - -- The width/alert dress (ref l.1576-1589), an if/elseif in the ref's own order: every - -- Show resets width 320 / icon hidden; a GUILD MESSAGE dialog widens to 420 with NO - -- icon; otherwise a showAlert entry widens to 420 and shows the icon. HELP_TICKET's - -- 350 arm waits for that dialog. - -- - -- **The widen is keyed on `which`, not on `hasWideEditBox`** — that is the reference's - -- own shape, transcribed rather than improved: a fourth wide-box dialog would get the - -- 350x64 box inside an un-widened 320 frame until its name joins this list. (The ref's - -- list also names SET_GUILDPLAYERNOTE twice, l.1582; the duplicate is inert.) - local alertIcon = getglobal(dialog:GetName() .. "AlertIcon") - alertIcon:Hide() - dialog:SetWidth(320) - if (which == "SET_GUILDMOTD") or (which == "SET_GUILDPLAYERNOTE") - or (which == "SET_GUILDOFFICERNOTE") then - dialog:SetWidth(420) - elseif info.showAlert then - dialog:SetWidth(420) - alertIcon:Show() - end - - -- The edit box (ref l.1599-1624): shown only for a hasEditBox entry, capped at the - -- entry's maxLetters, and always empty on open — a name left over from the last time - -- the dialog ran would be sent by a reflexive Enter. `hasWideEditBox` picks the 350x64 - -- centred box INSTEAD of the narrow one; exactly one of the two is ever shown, and - -- neither is when the entry has no box at all (the ref's three-way, verbatim). - local editBox = getglobal(dialog:GetName() .. "EditBox") - local wideEditBox = getglobal(dialog:GetName() .. "WideEditBox") - if info.hasEditBox then - if info.hasWideEditBox then - wideEditBox:SetMaxLetters(info.maxLetters or 0) - wideEditBox:SetText("") - wideEditBox:Show() - editBox:Hide() - else - editBox:SetMaxLetters(info.maxLetters or 0) - editBox:SetText("") - editBox:Show() - wideEditBox:Hide() - end - else - editBox:Hide() - wideEditBox:Hide() - end - - -- The money frame (ref l.1627-1632): shown for a hasMoneyFrame entry, hidden - -- otherwise. The engine never fills it — the AMOUNT belongs to whoever raised the - -- dialog, who calls MoneyFrame_Update on it (the ref's own division: its - -- CONFIRM_TALENT_WIPE arm in UIParent.lua does exactly that with the event's arg1). - getglobal(dialog:GetName() .. "MoneyFrame"):Hide() - if info.hasMoneyFrame then - getglobal(dialog:GetName() .. "MoneyFrame"):Show() - end - - -- The buttons (ref l.1634-1676; fixed 120 widths — header note). Button2 shows only - -- when the entry names it AND DisplayButton2 (when present) allows it; a solo button1 - -- re-seats centered under the text. With an edit box the pair hangs off the BOX's - -- bottom instead of the text's (ref l.1641-1643), which is what leaves room for it; - -- with a money frame the pair stays on the TEXT and drops 24 instead of 8, which is - -- what leaves room for the coins (ref l.1644-1646). - local buttonAnchor = info.hasEditBox and (dialog:GetName() .. "EditBox") - or (dialog:GetName() .. "Text") - local buttonDrop = info.hasMoneyFrame and -24 or -8 - local button1 = getglobal(dialog:GetName() .. "Button1") - local button2 = getglobal(dialog:GetName() .. "Button2") - if info.button2 and (not info.DisplayButton2 or info.DisplayButton2()) then - button1:ClearAllPoints() - button2:ClearAllPoints() - button1:SetPoint("TOPRIGHT", buttonAnchor, "BOTTOM", -6, buttonDrop) - button2:SetPoint("LEFT", dialog:GetName() .. "Button1", "RIGHT", 13, 0) - button2:SetText(info.button2) - button2:Show() - else - button1:ClearAllPoints() - button1:SetPoint("TOP", buttonAnchor, "BOTTOM", 0, -8) - button2:Hide() - end - if info.button1 then - button1:SetText(info.button1) - button1:Show() - else - button1:Hide() - end - - -- Misc vars (ref l.1677-1690). `which` is set before Show so OnShow reads it. - dialog.which = which - dialog.timeleft = info.timeout or 0 - dialog.hideOnEscape = info.hideOnEscape - dialog.data = data - - if info.StartDelay then - dialog.startDelay = info.StartDelay() - button1:Disable() - else - dialog.startDelay = nil - button1:Enable() - end - - dialog:Show() - StaticPopup_Resize(dialog, which) - - if info.sound then - PlaySound(info.sound) - end - return dialog - end - - -- Ref l.1850-1868: a truthy OnAccept return keeps the dialog open. - function StaticPopup_OnClick(dialog, index) - local which = dialog.which - local info = StaticPopupDialogs[which] - if not info then - return - end - local dontHide = nil - if index == 1 then - if info.OnAccept then - dontHide = info.OnAccept(dialog.data, dialog.data2) - end - else - if info.OnCancel then - info.OnCancel(dialog.data, "clicked") - end - end - if not dontHide and (which == dialog.which) then - dialog:Hide() - end - end - - -- The edit box's three script hooks (ref StaticPopup.xml's template scripts, ref - -- l.1810-1829). All three are pure delegation to the entry's own hook of the same name; - -- `this` is the EDIT BOX while they run, which is what lets an entry body say - -- `this:GetParent()`. Enter runs the entry's EditBoxOnEnterPressed, which by convention - -- does the accept work and hides the dialog itself; text-changed drives a live gate on - -- button1 (DELETE_GOOD_ITEM's typed "DELETE" is the only 1.12 customer). - function StaticPopup_EditBoxOnEnterPressed(editBox) - local dialog = editBox:GetParent() - local info = StaticPopupDialogs[dialog.which] - if info and info.EditBoxOnEnterPressed then - info.EditBoxOnEnterPressed(dialog.data) - end - end - - function StaticPopup_EditBoxOnTextChanged(editBox) - local dialog = editBox:GetParent() - local info = StaticPopupDialogs[dialog.which] - if info and info.EditBoxOnTextChanged then - info.EditBoxOnTextChanged(dialog.data) - end - end - - -- Escape delegates like its two siblings — and where the entry names no hook, falls back - -- to what ESC would have done had the focused box not swallowed the key, i.e. the ordinary - -- StaticPopup_EscapePressed leg: the entry's OnCancel, then hide, and only for a - -- hideOnEscape entry. NAMED DIVERGENCE (1743): the reference has no fallback at all, so a - -- 1.12 dialog whose entry omits the hook simply eats the key — which is what its own - -- DELETE_GOOD_ITEM does, leaving a destroy confirm that ESC cannot dismiss. Every other - -- benilla popup closes on ESC and this one now does too; without the OnCancel leg it would - -- also have hidden while still holding the item, which is the half that made the old - -- unconditional `dialog:Hide()` wrong rather than merely lenient. - function StaticPopup_EditBoxOnEscapePressed(editBox) - local dialog = editBox:GetParent() - local info = StaticPopupDialogs[dialog.which] - editBox:ClearFocus() - if info and info.EditBoxOnEscapePressed then - info.EditBoxOnEscapePressed(dialog.data) - elseif dialog.hideOnEscape then - if info and info.OnCancel then - info.OnCancel(dialog.data, "clicked") - end - dialog:Hide() - end - end - - -- Ref l.1832-1848: the open/close pair sounds on EVERY path, then the entry's own hook. - function StaticPopup_OnShow() - PlaySound("igMainMenuOpen") - local info = StaticPopupDialogs[this.which] - if info and info.OnShow then - info.OnShow(this.data) - end - end - - function StaticPopup_OnHide() - PlaySound("igMainMenuClose") - local info = StaticPopupDialogs[this.which] - if info and info.OnHide then - info.OnHide(this.data) - end - end - - -- Ref l.1712-1808: the timeout countdown (expiry → OnCancel "timeout" + hide), the - -- per-tick countdown text for the timer dialogs, the StartDelay gate (button1 disabled, - -- delayText counts down, then the real text swaps in and the button enables), then the - -- entry's own OnUpdate. CONFIRM_SUMMON's special text format landed with its dialog - -- (decision 1747); INSTANCE_BOOT's joins when that dialog does, and its which-list slot - -- stays meanwhile so the gate is the ref's. - function StaticPopup_OnUpdate(dialog, elapsed) - local info = StaticPopupDialogs[dialog.which] - if not info then - return - end - if (dialog.timeleft or 0) > 0 then - local which = dialog.which - local timeleft = dialog.timeleft - elapsed - if timeleft <= 0 then - dialog.timeleft = 0 - if info.OnCancel then - info.OnCancel(dialog.data, "timeout") - end - dialog:Hide() - return - end - dialog.timeleft = timeleft - - if (which == "DEATH") or (which == "CAMP") or (which == "QUIT") - or (which == "DUEL_OUTOFBOUNDS") or (which == "INSTANCE_BOOT") - or (which == "CONFIRM_SUMMON") or (which == "AREA_SPIRIT_HEAL") then - local text = getglobal(dialog:GetName() .. "Text") - timeleft = ceil(timeleft) - local n, unit = timeleft, StaticPopupTimeUnit(timeleft, false) - if timeleft >= 60 then - n = ceil(timeleft / 60) - unit = StaticPopupTimeUnit(n, true) - end - -- The ref's own if/elseif over `which` (l.1746-1758), which exists because two - -- of these dialogs take MORE than the count and its unit word. CONFIRM_SUMMON - -- prefixes the summoner and the destination, both read live from the engine - -- every tick — which is what lets a name still resolving through the name cache - -- paint blank once and then fill itself in (decision 1747). INSTANCE_BOOT's arm - -- (GetBindLocation() first) joins when that dialog does; until then it falls - -- through here, which is wrong for it and harmless, since it cannot be shown. - if which == "CONFIRM_SUMMON" then - text:SetText(format(info.text, GetSummonConfirmSummoner(), - GetSummonConfirmAreaName(), n, unit)) - else - text:SetText(format(info.text, n, unit)) - end - end - end - if dialog.startDelay then - local which = dialog.which - local timeleft = dialog.startDelay - elapsed - if timeleft <= 0 then - dialog.startDelay = nil - local text = getglobal(dialog:GetName() .. "Text") - text:SetText(format(info.text, dialog.text_arg1, dialog.text_arg2)) - getglobal(dialog:GetName() .. "Button1"):Enable() - return - end - dialog.startDelay = timeleft - - if (which == "RECOVER_CORPSE") or (which == "RESURRECT") - or (which == "RESURRECT_NO_SICKNESS") then - local text = getglobal(dialog:GetName() .. "Text") - timeleft = ceil(timeleft) - if (which == "RESURRECT") or (which == "RESURRECT_NO_SICKNESS") then - if timeleft < 60 then - text:SetText(format(info.delayText, dialog.text_arg1, timeleft, - StaticPopupTimeUnit(timeleft, false))) - else - local m = ceil(timeleft / 60) - text:SetText(format(info.delayText, dialog.text_arg1, m, - StaticPopupTimeUnit(m, true))) - end - else - if timeleft < 60 then - text:SetText(format(info.delayText, timeleft, - StaticPopupTimeUnit(timeleft, false))) - else - local m = ceil(timeleft / 60) - text:SetText(format(info.delayText, m, - StaticPopupTimeUnit(m, true))) - end - end - end - end - StaticPopup_Resize(dialog, dialog.which) - if info.OnUpdate then - info.OnUpdate(elapsed, dialog) - end - end - - -- Ref l.1879-1893: only hideOnEscape dialogs respond — a shown DEATH popup deliberately - -- does NOT close on ESC. Returns truthy when something closed (the caller's chain stops). - function StaticPopup_EscapePressed() - local closed = nil - for index = 1, STATICPOPUP_NUMDIALOGS do - local frame = getglobal("StaticPopup" .. index) - if frame:IsShown() and frame.hideOnEscape then - local OnCancel = StaticPopupDialogs[frame.which].OnCancel - if OnCancel then - OnCancel(frame.data, "clicked") - end - frame:Hide() - closed = 1 - end - end - return closed - end - - -- Destroy-confirm strings — the real 1.12 GlobalStrings.lua (patch-2.MPQ, - -- Interface\FrameXML\GlobalStrings.lua l.921-923; YES/NO are l.5463/l.2794, the same pair - -- LootFrame.xml declares), quoted verbatim in the FALLBACK form: - -- the player's own localized GlobalStrings.lua runs off the patch chain BEFORE this file - -- (ui_script/mod.rs), so these only fill in for the harnesses that load no chain. They - -- were plain locals until 1743; a local could never be the localized text, and - -- DELETE_ITEM_CONFIRM_STRING in particular MUST be the player's own word — it is what the - -- good-item dialog below makes them type. - DELETE_ITEM = DELETE_ITEM or "Do you want to destroy %s?" - DELETE_GOOD_ITEM = DELETE_GOOD_ITEM - or "Do you want to destroy %s?\n\nType \"DELETE\" into the field to confirm." - DELETE_ITEM_CONFIRM_STRING = DELETE_ITEM_CONFIRM_STRING or "DELETE" - YES = YES or "Yes" - NO = NO or "No" - - -- ref GlobalStrings.lua l.2612 / l.2818 / l.465, fallback form (the player's own localized - -- file runs off the patch chain first). - -- ── The group-invite popup (ref StaticPopup.lua l.526-548) ─────────────────────────── - -- Moved here from our `PartyFrame.xml` when 1751 took that window to the player's chain. - -- This file is our `StaticPopup.lua`, which is where the reference declares it; the party - -- frames were only where ours was first needed. - -- The strings and the three flags are the reference's own (INVITATION = "%s invites you to - -- a group.", GlobalStrings:2586-area; a 60 s silent timeout, answerable while dead, - -- closable with ESC). - -- - -- The whole of its behaviour turns on ONE latch, `inviteAccepted`, and the four hooks below - -- are just the four moments that read or write it. It lives on the registry entry rather - -- than in an upvalue here because that is where the reference keeps it and an addon - -- inspecting the dialog expects to find it there — which is why each hook goes back through - -- `StaticPopupDialogs["PARTY_INVITE"]` instead of closing over a local. - -- - -- They are named rather than inlined so the entry below reads as the manifest it is: four - -- strings, three flags, four hooks. - - -- Every showing starts unanswered — including a re-show of the same entry, which is what a - -- second invite arriving while the first is up does. - local function PartyInvite_OnShow() - StaticPopupDialogs["PARTY_INVITE"].inviteAccepted = nil - end - - -- The only path that answers the server affirmatively, and the only one that latches. - local function PartyInvite_OnAccept() - AcceptGroup() - StaticPopupDialogs["PARTY_INVITE"].inviteAccepted = 1 - end - - local function PartyInvite_OnCancel() - DeclineGroup() - end - - -- The catch-all. EVERY way this dialog leaves the screen lands here — Decline, the 60 s - -- timeout, ESC, and another dialog overriding it — and the latch is what tells the one - -- that already said yes from the four that never answered at all. A clicked Decline - -- therefore declines twice (OnCancel, then here); see DOUBLE-DECLINE in the header, which - -- is the reference's behaviour and the wire tolerates it. - local function PartyInvite_OnHide() - if not StaticPopupDialogs["PARTY_INVITE"].inviteAccepted then - DeclineGroup() - end - end - - -- ── Two dialogs rehomed from our retired RaidFrame.xml (1874) ────────────────────────── - -- Their reference home is `StaticPopup.lua`, which we do not load — this file is our - -- stand-in for that engine, so this is where they belong. They came here because the raid - -- pane went to the chain and the reference's own `RaidFrame.lua` does not declare them: - -- deleting our file took `CONFIRM_RESET_INSTANCES` with it, and `UnitPopup.xml`'s "Reset - -- all instances" row calls `StaticPopup_Show` on exactly that name. - -- ── Add Member's dialog (ref StaticPopup.lua l.871-895) ───────────────────────────────── - StaticPopupDialogs["ADD_RAIDMEMBER"] = { - text = ADD_RAIDMEMBER_LABEL, - button1 = "Accept", - button2 = "Cancel", - hasEditBox = 1, - maxLetters = 12, - timeout = 0, - hideOnEscape = 1, - OnAccept = function() - InviteByName(getglobal("StaticPopup1EditBox"):GetText()) - end, - OnShow = function() - getglobal("StaticPopup1EditBox"):SetFocus() - end, - EditBoxOnEnterPressed = function() - InviteByName(getglobal("StaticPopup1EditBox"):GetText()) - StaticPopup_Hide("ADD_RAIDMEMBER") - end, - } - - -- ── "Reset all instances" (ref StaticPopup.lua l.23-33; decision 1748) ────────────────── - -- Raised by UnitPopup's SELF menu, and registered HERE because this file is the instance - -- house — the same rule that keeps the pet dialogs in PetActionBar.xml. `ResetInstances()` - -- is the only call in the client that sends `CMSG_RESET_INSTANCES`; everything else in the - -- lockout family is the server talking. - -- - -- The answer is per instance rather than one verdict, and it arrives as CHAT_MSG_SYSTEM - -- lines the engine composes: an `SMSG_INSTANCE_RESET` per dungeon that reset, and — on - -- the group path only — an `SMSG_INSTANCE_RESET_FAILED` per dungeon that would not. - -- Nothing back at all is the ordinary answer for a character with nothing resettable. So - -- there is nothing to show here and no result to wait for. - StaticPopupDialogs["CONFIRM_RESET_INSTANCES"] = { - text = CONFIRM_RESET_INSTANCES, - button1 = YES, - button2 = NO, - timeout = 0, - whileDead = 1, - hideOnEscape = 1, - OnAccept = function() - ResetInstances() - end, - } - - - StaticPopupDialogs["PARTY_INVITE"] = { - text = INVITATION, - button1 = ACCEPT, - button2 = DECLINE, - sound = "igPlayerInvite", - timeout = 60, - whileDead = 1, - hideOnEscape = 1, - OnShow = PartyInvite_OnShow, - OnAccept = PartyInvite_OnAccept, - OnCancel = PartyInvite_OnCancel, - OnHide = PartyInvite_OnHide, - } - - -- ── The logout countdowns ──────────────────────────────────────────────────────────── - -- The CAMP and QUIT dialogs, moved here from our `GameMenuFrame.xml` when 1751 took that - -- window to the player's chain. This file is our `StaticPopup.lua`, which is where the - -- reference declares both (l.564 / l.586) — the ESC menu never owned them, it just - -- happened to be where ours were first needed. - CAMP_TIMER = CAMP_TIMER or "%d %s until logout" - QUIT_TIMER = QUIT_TIMER or "%d %s until exit" - QUIT_NOW = QUIT_NOW or "Exit now" - -- CANCEL before the tables below, not after: a dialog entry captures its button labels BY - -- VALUE at load, so a `button1 = CANCEL` built while CANCEL is still nil silently falls - -- back to the popup engine's default ("Yes") rather than failing. It reads as a wrong - -- label on a real dialog and as nothing at all in a test that does not check the text. - CANCEL = CANCEL or "Cancel" - - -- Dismissing a running countdown means "I didn't mean it": call the logout off. Only while - -- it is still RUNNING, though — the popup engine zeroes `timeleft` before hiding a dialog - -- that expired, so reaching zero leaves the logout alone. (Backwards, and every logout in - -- the field is cancelled by its own dialog at t=0 and the character never leaves.) One - -- function because both dialogs mean exactly this; `this` is the popup frame, which the - -- engine has bound around the handler. - local function CancelLogoutIfStillCounting() - if this.timeleft > 0 then - CancelLogout() - end - end - - StaticPopupDialogs["CAMP"] = { - text = CAMP_TIMER, - button1 = CANCEL, - timeout = 20, - whileDead = 1, - hideOnEscape = 1, - OnAccept = function() - CancelLogout() - end, - OnHide = CancelLogoutIfStillCounting, - } - - StaticPopupDialogs["QUIT"] = { - text = QUIT_TIMER, - button1 = QUIT_NOW, - button2 = CANCEL, - timeout = 20, - whileDead = 1, - hideOnEscape = 1, - -- A force-quit needs no server round trip, so the countdown it was narrating is over - -- the moment the button is pressed: zero `timeleft` so the OnHide below stands down. - OnAccept = function() - ForceQuit() - this.timeleft = 0 - end, - OnHide = CancelLogoutIfStillCounting, - } - - LOOT_NO_DROP = LOOT_NO_DROP or "Looting this item will bind it to you." - OKAY = OKAY or "Okay" - - -- ref StaticPopup.lua l.601-611, verbatim. `OnAccept`'s argument is `dialog.data`, the - -- event's row — and `LootSlot` is the CONTINUATION verb, so this line is the only thing in - -- the client that ever completes a bind-deferred take (decision 1744). - -- - -- It lives here rather than in the loot window because this file is our `StaticPopup.lua` - -- and that is the reference's own home for it. Our transcription of `LootFrame.xml` - -- declared it; the stock file does not, so 1751 moved it where it always belonged. - -- - -- No OnCancel (declining leaves the row in the window, and the app drops the pending stash - -- when the window turns over) and no OnUpdate poll: the loot window's own LOOT_CLOSED - -- hides it (`LootFrame.lua:53-54`). - StaticPopupDialogs["LOOT_BIND"] = { - text = LOOT_NO_DROP, - button1 = OKAY, - button2 = CANCEL, - OnAccept = function(slot) - LootSlot(slot) - end, - timeout = 0, - exclusive = 1, - hideOnEscape = 1, - } - - CONFIRM_LOOT_DISTRIBUTION = CONFIRM_LOOT_DISTRIBUTION - or "You wish to assign %s to %s. Is this correct?" -- GlobalStrings.lua l.848 - - -- The master-loot confirmation the group-loot dropdown raises for an at-or-above-threshold - -- item (ref StaticPopup.lua l.85-94, verbatim). It lives HERE because that is the - -- reference's own home for it — this file is our `StaticPopup.lua` — and because 1751 took - -- `LootFrame.xml` to the player's chain: our transcription used to declare this dialog - -- itself, and the stock file does not. - -- - -- `data` is the candidate index the dropdown stashed; the SLOT is re-read live from - -- `LootFrame.selectedSlot` at accept time, exactly as the reference does. That is not a - -- shortcut — it is why the row click hides this popup first (`LootFrame.lua:156`), so an - -- accept can never land on a row the player has since clicked away from. - StaticPopupDialogs["CONFIRM_LOOT_DISTRIBUTION"] = { - text = CONFIRM_LOOT_DISTRIBUTION, - button1 = YES, - button2 = NO, - OnAccept = function(data) - GiveMasterLoot(LootFrame.selectedSlot, data) - end, - timeout = 0, - hideOnEscape = 1, - } - - -- Ref StaticPopup.lua l.659-679, verbatim — its own OnUpdate poll (cursor emptied by any - -- other path → hide) replaces the old bespoke `deleteItemFlow` CURSOR_UPDATE driver. - -- exclusive is v1-inert (engine header note); showAlert is live since 1068 — the ref's - -- own "display the error image" case is this very dialog. - -- CONFIRM_LOOT_ROLL — the BoP roll's "this will bind to you" gate, keyed BARE like every - -- other entry (the key is `StaticPopup_Show`'s argument, not a frame name). - -- - -- It moved here from our own GroupLootFrame.xml when 1838 retired that file. The reference - -- keeps it in `StaticPopup.lua`; we have no counterpart to that file, and our own - -- convention is that a dialog whose window has migrated comes to sit beside DELETE_ITEM - -- below — the same move `CloseMenus` made when the dropdown kit went to the chain. - -- - -- The C `RollOnLoot` is what diverts a BoP Need/Greed here rather than rolling: the gate - -- lives in the binding, not in Lua (decision 0594, verified in the 5875 binary), and only - -- `OnAccept`'s `ConfirmLootRoll` re-enters past it. `id`/`rollType` arrive as data/data2, - -- set by the `CONFIRM_LOOT_ROLL` arm in UIParent.xml's own OnEvent. - -- - -- The three strings are quoted verbatim rather than read from GlobalStrings, which is this - -- file's existing idiom for exactly one reason: a bare global is nil in a harness that has - -- not loaded the chain's GlobalStrings, and a `format()` over nil aborts the whole inline - -- chunk — taking every function defined after it down with it. - StaticPopupDialogs["CONFIRM_LOOT_ROLL"] = { - text = "Looting this item will bind it to you.", -- LOOT_NO_DROP - button1 = "Okay", -- OKAY - button2 = "Cancel", -- CANCEL - OnAccept = function(id, rollType) - ConfirmLootRoll(id, rollType) - end, - timeout = 0, - whileDead = 1, - exclusive = 1, - hideOnEscape = 1 - } - - StaticPopupDialogs["DELETE_ITEM"] = { - text = DELETE_ITEM, - button1 = YES, - button2 = NO, - OnAccept = function() - DeleteCursorItem() - end, - OnCancel = function() - ClearCursor() - end, - OnUpdate = function() - if not CursorHasItem() then - StaticPopup_Hide("DELETE_ITEM") - end - end, - timeout = 0, - whileDead = 1, - exclusive = 1, - showAlert = 1, - hideOnEscape = 1, - } - - -- Ref StaticPopup.lua l.680-724, verbatim — the RARE-or-better destroy. Same accept, - -- cancel and auto-hide poll as DELETE_ITEM above; what it adds is the typed confirmation: - -- the edit box takes focus on open, button1 starts DISABLED, and only the exact - -- DELETE_ITEM_CONFIRM_STRING (case-insensitively, via the ref's own strupper) enables it. - -- - -- `this` is the dialog inside OnShow/OnHide and the EDIT BOX inside the two EditBox hooks — - -- the reference's own convention, which is why those two reach the dialog through - -- `this:GetParent()`. Our engine sets the same `this` on both paths (StaticPopup_OnShow / - -- the template's /), so the bodies transcribe unchanged. - -- - -- The one guard added: the ref's OnHide reads ChatFrameEditBox unguarded, which raises in a - -- harness that loads no ChatFrame.xml. Same `and` the bag's shift-link arm carries. - StaticPopupDialogs["DELETE_GOOD_ITEM"] = { - text = DELETE_GOOD_ITEM, - button1 = YES, - button2 = NO, - OnAccept = function() - DeleteCursorItem() - end, - OnCancel = function() - ClearCursor() - end, - OnUpdate = function() - if not CursorHasItem() then - StaticPopup_Hide("DELETE_GOOD_ITEM") - end - end, - timeout = 0, - whileDead = 1, - exclusive = 1, - showAlert = 1, - hideOnEscape = 1, - hasEditBox = 1, - maxLetters = 32, - OnShow = function() - getglobal(this:GetName() .. "Button1"):Disable() - getglobal(this:GetName() .. "EditBox"):SetFocus() - end, - OnHide = function() - if ChatFrameEditBox and ChatFrameEditBox:IsVisible() then - ChatFrameEditBox:SetFocus() - end - getglobal(this:GetName() .. "EditBox"):SetText("") - end, - EditBoxOnEnterPressed = function() - if getglobal(this:GetParent():GetName() .. "Button1"):IsEnabled() == 1 then - DeleteCursorItem() - this:GetParent():Hide() - end - end, - EditBoxOnTextChanged = function() - local editBox = getglobal(this:GetParent():GetName() .. "EditBox") - if strupper(editBox:GetText()) == DELETE_ITEM_CONFIRM_STRING then - getglobal(this:GetParent():GetName() .. "Button1"):Enable() - else - getglobal(this:GetParent():GetName() .. "Button1"):Disable() - end - end, - } - - -- ── The equip / auto-equip / use soulbind confirms (decision 1750) ──────────────────── - -- ref GlobalStrings.lua l.1459 / l.4453 / l.2818 / l.465, fallback form. - EQUIP_NO_DROP = EQUIP_NO_DROP or "Equipping this item will bind it to you." - USE_NO_DROP = USE_NO_DROP or "Using this item will bind it to you." - OKAY = OKAY or "Okay" - CANCEL = CANCEL or "Cancel" - - -- ref StaticPopup.lua l.612-647, verbatim, twice: EQUIP_BIND and AUTOEQUIP_BIND are the - -- SAME entry down to the last field — same text, same three hooks — and differ only in - -- which event raises them. Built from one factory rather than transcribed twice, because - -- two copies of a nine-field table is exactly how the two drift apart. - -- - -- `data` is a 0-based index into the client's pending-equip array, NOT a slot (the RE - -- corrected benilla's assumption here). It is opaque: the event hands it out and these - -- hooks hand it straight back. - -- - -- **OnCancel AND OnHide both cancel, and that is deliberate, not redundant.** OnHide is - -- what retires a record when `exclusive = 1` makes a second bind question hide this one; - -- without it the superseded item's pending record leaks. The doubling it causes on the - -- ordinary paths is harmless by construction — an Accept runs OnAccept then OnHide, so the - -- app sees [accept, cancel] for one index and the cancel finds the record already taken. - local function equip_bind_entry() - return { - text = EQUIP_NO_DROP, - button1 = OKAY, - button2 = CANCEL, - OnAccept = function(index) - EquipPendingItem(index) - end, - OnCancel = function(index) - CancelPendingEquip(index) - end, - OnHide = function(index) - CancelPendingEquip(index) - end, - timeout = 0, - exclusive = 1, - whileDead = 1, - hideOnEscape = 1, - } - end - StaticPopupDialogs["EQUIP_BIND"] = equip_bind_entry() - StaticPopupDialogs["AUTOEQUIP_BIND"] = equip_bind_entry() - - -- ref StaticPopup.lua l.648-658. No index, no cancel hook: the use arm's pending state is - -- a single cell in the client rather than an array element, and declining just drops it. - StaticPopupDialogs["USE_BIND"] = { - text = USE_NO_DROP, - button1 = OKAY, - button2 = CANCEL, - OnAccept = function() - ConfirmBindOnUse() - end, - timeout = 0, - exclusive = 1, - hideOnEscape = 1, - } - - -- The bank-slot purchase confirm (ref StaticPopup.lua l.47-60), transcribed verbatim. - -- - -- **It is HERE because its window is the reference's own file now** (1751). Every other - -- dialog in this client is declared by the window that shows it; the bank window is - -- `Interface\FrameXML\BankFrame.xml`, executed off the player's chain, and the reference - -- keeps this entry in `StaticPopup.lua` — the one file benilla replaces with the engine - -- above. So the house's StaticPopup home carries it, and `BankFramePurchaseButton`'s - -- `StaticPopup_Show("CONFIRM_BUY_BANK_SLOT")` finds it. Left out, the Purchase button - -- would be a silent no-op. - -- - -- The coin row is real (1580 made `hasMoneyFrame` live): `BankFrame.nextSlotCost` is set - -- by the reference's own `UpdateBagSlotStatus` off `GetBankSlotCost`, and this OnShow is - -- the reference's own line for reading it. - CONFIRM_BUY_BANK_SLOT = CONFIRM_BUY_BANK_SLOT - or "Do you want to purchase a bank slot for:" - StaticPopupDialogs["CONFIRM_BUY_BANK_SLOT"] = { - text = CONFIRM_BUY_BANK_SLOT, - button1 = YES, - button2 = NO, - OnAccept = function() - PurchaseSlot() - end, - OnShow = function() - MoneyFrame_Update(this:GetName() .. "MoneyFrame", BankFrame.nextSlotCost) - end, - hasMoneyFrame = 1, - timeout = 0, - hideOnEscape = 1, - } - - -- The guild-charter rename box, ref StaticPopup.lua l.133-160 — **in the reference's own - -- home, which is this file**. It lived in our PetitionFrame.xml on the reasoning that "a - -- popup belongs to the window that raises it"; that is a fine rule and it is not 1.12's, - -- which keeps every dialog in one registry. 1751's twelfth window made the charter the - -- reference's own file, whose Rename button calls `StaticPopup_Show("RENAME_GUILD")` and - -- registers nothing, so the entry had to come here. - -- - -- 24 letters is the server's MAX_CHARTER_NAME, so the box cannot compose a name that will - -- be refused for length. Accept and Enter run the same two lines; the reference spells - -- them out twice (l.139-147), and its Enter arm hides the dialog while its Accept arm does - -- not — the popup engine hides on a plain Accept anyway. - StaticPopupDialogs["RENAME_GUILD"] = { - text = RENAME_GUILD_LABEL, - button1 = ACCEPT, - button2 = CANCEL, - hasEditBox = 1, - maxLetters = 24, - OnAccept = function() - RenamePetition(getglobal(this:GetParent():GetName() .. "EditBox"):GetText()) - end, - EditBoxOnEnterPressed = function() - RenamePetition(getglobal(this:GetParent():GetName() .. "EditBox"):GetText()) - this:GetParent():Hide() - end, - OnShow = function() - getglobal(this:GetName() .. "EditBox"):SetFocus() - end, - OnHide = function() - if ChatFrameEditBox and ChatFrameEditBox:IsVisible() then - ChatFrameEditBox:SetFocus() - end - getglobal(this:GetName() .. "EditBox"):SetText("") - end, - timeout = 0, - exclusive = 1, - whileDead = 1, - hideOnEscape = 1, - } - - -- The quest log's abandon confirm and its with-items twin — ref StaticPopup.lua l.749-775, - -- in the reference's own home, which is this file (the same move RENAME_GUILD made). They - -- lived in our QuestLogFrame.xml until 1944 took that window stock; stock's Abandon button - -- calls `StaticPopup_Show("ABANDON_QUEST", GetAbandonQuestName())` and registers nothing. - -- The wording is the GlobalStrings key by name, as this file spells its dialogs (the - -- reference wraps the same globals in TEXT()); the with-items twin is declared as the - -- reference declares it, whatever `GetAbandonQuestItems()` answers. - StaticPopupDialogs["ABANDON_QUEST"] = { - text = ABANDON_QUEST_CONFIRM, - button1 = YES, - button2 = NO, - OnAccept = function() - AbandonQuest() - PlaySound("igQuestLogAbandonQuest") - end, - timeout = 0, - whileDead = 1, - exclusive = 1, - hideOnEscape = 1, - } - StaticPopupDialogs["ABANDON_QUEST_WITH_ITEMS"] = { - text = ABANDON_QUEST_CONFIRM_WITH_ITEMS, - button1 = YES, - button2 = NO, - OnAccept = function() - AbandonQuest() - PlaySound("igQuestLogAbandonQuest") - end, - timeout = 0, - whileDead = 1, - exclusive = 1, - hideOnEscape = 1, - } - - -- The pet arc's three (decision 1066; ref StaticPopup.lua l.738, l.1069, l.365). ABANDON_PET - -- is the plain confirm. RENAME_PET is a CHAIN of two, which is the reference's own shape and - -- not a flourish: the first collects the name, the second reads it back ("Name your pet - -- 'Bruce'?") before anything is sent — a rename is one-shot, so a typo is permanent the way - -- the abandon is. That second Show happens while the first dialog is still up, which is - -- why the engine's two instances are both needed. Registered beside the other dialogs since - -- the pet bar became the reference's file (1953). - StaticPopupDialogs["ABANDON_PET"] = { - text = ABANDON_PET, - button1 = OKAY, - button2 = CANCEL, - OnAccept = function() - PetAbandon(); - end, - timeout = 0, - exclusive = 1, - hideOnEscape = 1, - } - - StaticPopupDialogs["RENAME_PET"] = { - text = PET_RENAME_LABEL, - button1 = ACCEPT, - button2 = CANCEL, - hasEditBox = 1, - -- The 12-letter cap is the reference's, and it is the client's only name check: the - -- server re-validates with CheckPetName and refuses with SMSG_PET_NAME_INVALID. - maxLetters = 12, - OnAccept = function() - -- `this` is the clicked button, so its parent is THIS dialog — never - -- StaticPopup1 by name, which would read the wrong box the moment this dialog - -- lands in the second instance (the chain below is exactly that case). - local text = getglobal(this:GetParent():GetName() .. "EditBox"):GetText() - local dialog = StaticPopup_Show("PETRENAMECONFIRM", text) - if dialog then - dialog.data = text - end - end, - EditBoxOnEnterPressed = function() - local text = getglobal(this:GetParent():GetName() .. "EditBox"):GetText() - local dialog = StaticPopup_Show("PETRENAMECONFIRM", text) - if dialog then - dialog.data = text - end - this:GetParent():Hide() - end, - OnShow = function() - getglobal(this:GetName() .. "EditBox"):SetFocus() - end, - timeout = 0, - exclusive = 1, - hideOnEscape = 1, - } - - StaticPopupDialogs["PETRENAMECONFIRM"] = { - -- "Name your pet '%s'?" — the name arrives as text_arg1 AND as `data`; the first - -- fills the question, the second is what actually gets sent. - text = PET_RENAME_CONFIRMATION, - button1 = YES, - button2 = NO, - OnAccept = function(data) - PetRename(data); - end, - timeout = 0, - hideOnEscape = 1, - } - - -- The skills page's unlearn confirm (ref StaticPopup.lua l.1121-1133), registered beside - -- the other dialogs since the page became the reference's file (1956). - StaticPopupDialogs["UNLEARN_SKILL"] = { - text = UNLEARN_SKILL, - button1 = UNLEARN, - button2 = CANCEL, - OnAccept = function(index) - AbandonSkill(index) - end, - timeout = 60, - exclusive = 1, - whileDead = 1, - showAlert = 1, - hideOnEscape = 1 - } - - -- ref UIParent.lua l.324-343 + l.356-360, the four arms on one driver. Each equip arm - -- hides the OTHER dialog first — the two questions are mutually exclusive by construction - -- (one item, one destination), and the hide is what cancels the record the new question - -- replaces. - function BenillaBindConfirmDriver_OnEvent(frame) - if event == "EQUIP_BIND_CONFIRM" then - StaticPopup_Hide("AUTOEQUIP_BIND") - local dialog = StaticPopup_Show("EQUIP_BIND") - if dialog then - dialog.data = arg1 - end - elseif event == "AUTOEQUIP_BIND_CONFIRM" then - StaticPopup_Hide("EQUIP_BIND") - local dialog = StaticPopup_Show("AUTOEQUIP_BIND") - if dialog then - dialog.data = arg1 - end - elseif event == "PARTY_INVITE_REQUEST" then - -- ref UIParent.lua's own arms. Same move as the logout three and the loot bind - -- confirm: the popup-raising event belongs beside the popup, not in whichever - -- window happened to need it first. - StaticPopup_Show("PARTY_INVITE", arg1) - elseif event == "PARTY_INVITE_CANCEL" then - StaticPopup_Hide("PARTY_INVITE") - elseif event == "PLAYER_CAMPING" then - -- ref UIParent.lua l.304-315. These three lived on our GameMenuFrame.xml's own - -- hidden listener, on the reasoning that benilla has no UIParent frame; they are - -- here now for the same reason the loot bind confirm is — 1751 took that window - -- to the chain, and the reference's placement (UIParent, beside every other - -- popup-raising event) is the one that survives the window being someone else's. - StaticPopup_Show("CAMP") - elseif event == "PLAYER_QUITING" then - StaticPopup_Show("QUIT") - elseif event == "LOGOUT_CANCEL" then - StaticPopup_Hide("CAMP") - StaticPopup_Hide("QUIT") - elseif event == "LOOT_BIND_CONFIRM" then - -- ref UIParent.lua l.317-323. The row rides on `dialog.data` rather than as a - -- text arg — LOOT_NO_DROP has no format specifier, so the sentence is the same - -- whatever binds; the number is only there to hand back to `LootSlot`. - -- - -- This arm used to live in our own `LootFrame.xml`, on the window's event - -- listener, on the reasoning that LOOT_CLOSED already had to hide the dialog and - -- the arc stayed in one file. 1751 took that window to the chain, and the - -- reference's placement — UIParent, beside every other bind confirm — turned out - -- to be the one that survives the window being someone else's. - local dialog = StaticPopup_Show("LOOT_BIND") - if dialog then - dialog.data = arg1 - end - elseif event == "USE_BIND_CONFIRM" then - StaticPopup_Show("USE_BIND") - elseif event == "CURSOR_UPDATE" then - -- ref l.356-360: the player picked something else up, so the standing question is - -- stale. Both hides run their OnHide, which cancels the record. - StaticPopup_Hide("EQUIP_BIND") - StaticPopup_Hide("AUTOEQUIP_BIND") - end - end - - -- BenillaDeleteItemConfirmDriver's OnEvent — DELETE_ITEM_CONFIRM(name, quality) is the - -- engine's world-drop of an item payload (decision 0216 §3, cursor.rs::world_drop) → - -- ref UIParent.lua:344-352, now whole: the ref's `arg2 >= 3` fork routes RARE-or-better - -- (quality 3..6 — blue, epic, legendary, artifact) to the typed-confirmation variant and - -- everything below it to the plain one. 0216 §3's deferral is closed (1743). - function BenillaDeleteItemConfirmDriver_OnEvent(frame) - if event == "DELETE_ITEM_CONFIRM" then - if arg2 >= 3 then - StaticPopup_Show("DELETE_GOOD_ITEM", arg1) - else - StaticPopup_Show("DELETE_ITEM", arg1) - end - end - end - -- ── The shared frame-fade kit — ref UIParent.lua:1103-1219 is the specification (the -- function names are its published API and the arithmetic is the same lerp); the code is -- ours. FADEFRAMES is a plain ARRAY walked by index rather than by `pairs`, because @@ -2731,212 +1541,4 @@ only consumer and loads long after that entry, so it has been inheriting the reference's since the swap. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SmallMoneyFrame_OnLoad(); MoneyFrame_SetType("STATIC") - - - - - - - - StaticPopup_OnShow() - StaticPopup_OnHide() - StaticPopup_OnUpdate(this, arg1) - - - - - - - - - - - - - - - - - - - - - - - self:RegisterEvent("DELETE_ITEM_CONFIRM") - - BenillaDeleteItemConfirmDriver_OnEvent(self) - - - - - - - - - - self:RegisterEvent("EQUIP_BIND_CONFIRM") - self:RegisterEvent("AUTOEQUIP_BIND_CONFIRM") - self:RegisterEvent("USE_BIND_CONFIRM") - self:RegisterEvent("LOOT_BIND_CONFIRM") - self:RegisterEvent("PLAYER_CAMPING") - self:RegisterEvent("PLAYER_QUITING") - self:RegisterEvent("LOGOUT_CANCEL") - self:RegisterEvent("PARTY_INVITE_REQUEST") - self:RegisterEvent("PARTY_INVITE_CANCEL") - self:RegisterEvent("CURSOR_UPDATE") - - BenillaBindConfirmDriver_OnEvent(self) - - diff --git a/crates/benilla-app/assets/ui/UnitPopup.xml b/crates/benilla-app/assets/ui/UnitPopup.xml deleted file mode 100644 index ebc205f2..00000000 --- a/crates/benilla-app/assets/ui/UnitPopup.xml +++ /dev/null @@ -1,885 +0,0 @@ - - - - - - - - - - UnitPopup_OnUpdate(arg1) - - - diff --git a/crates/benilla-app/assets/ui/benilla.toc b/crates/benilla-app/assets/ui/benilla.toc index a9a1c19a..f4c5410d 100644 --- a/crates/benilla-app/assets/ui/benilla.toc +++ b/crates/benilla-app/assets/ui/benilla.toc @@ -162,9 +162,11 @@ MoneyFrame.xml # `every_chain_xml_brings_its_own_lua` is now a gate so neither can recur. Interface\FrameXML\MoneyInputFrame.lua Interface\FrameXML\MoneyInputFrame.xml -# The UIPanel slot manager (decision 0084 §2): script-only, no frames of its own — must load -# before any panel frame (Gossip/Merchant today) so ShowUIPanel/HideUIPanel already exist -# when those frames' OnLoad/OnEvent reference them. +# The UIPanel slot manager (decision 0084 §2) and the fade/flash/pulse kits — the UIParent.lua +# halves this engine still carries itself: script-only but for the kits' tick drivers, loaded +# before any panel frame so ShowUIPanel/HideUIPanel already exist when those frames' OnLoad/OnEvent +# 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 @@ -203,10 +205,13 @@ Interface\FrameXML\UIDropDownMenu.xml # needs for the tooltip backdrop its menu wears). Far above ChatFrame.xml, which is the one thing # that used it here — resolved at load, and now satisfied earlier rather than in the same file. Interface\FrameXML\UIMenu.xml -# The unit right-click popups (0434 phase 5): UnitPopupMenus/Buttons + UnitPopup_ShowMenu -# — after the dropdown kit it drives, before the unit/party frames whose DropDown -# children's OnLoad initialize into it. -UnitPopup.xml +# ══ THE UNIT MENUS — the reference's own, off the player's chain (1751; decision 1958) ══ +# UnitPopup.xml + UnitPopup.lua: UnitPopupButtons/Menus and UnitPopup_ShowMenu, the nine menus +# and every row's facts — after the dropdown kit it drives (its own position, FrameXML.toc +# l.16), before the unit/party frames whose DropDown children's OnLoad initialize into it. Its +# enable pass runs from UIParent's OnUpdate line, the reference's own hook. 0434's +# transcription is gone. +Interface\FrameXML\UnitPopup.xml # ══ THE CHAT-LINK TOOLTIP — the reference's own, off the player's chain (1751, window 6) ══ # `SetItemRef` (the chat-link click router) and `ItemRefTooltip` / `ItemRefCloseButton`. # @@ -311,35 +316,6 @@ Interface\FrameXML\ComboFrame.xml # transcription; consumes COMBAT_TEXT_UPDATE + the regen/health/power events, needs # only Fonts.xml (CombatTextFont) + UIParent before it. CombatText.xml -# The death arc's dialogs + event driver (decision 0308): registry entries on the shared -# StaticPopup engine, so after UiPanels.xml. -DeathFrame.xml -# The two duel dialogs + their event driver (decision 0633): registry entries on the same -# StaticPopup engine, and DUEL_OUTOFBOUNDS leans on its per-tick countdown branch. -DuelFrame.xml -# The escort-quest confirm + its event driver (decision 1733): one more registry entry on the same -# StaticPopup engine. Not QuestFrame.xml's or QuestLogFrame.xml's — it belongs to no window, the -# way the duel dialogs above belong to none. -QuestShareFrame.xml -# The two enchant-apply confirms + their event driver (decision 0928): the same StaticPopup -# registry again. Not CraftFrame's — the gate that raises them is the generic item-target -# bind, reached by a poison or a sharpening stone with no profession window open. -EnchantConfirm.xml -# The innkeeper's bind confirm + its event driver (decision 1331): the same StaticPopup registry. -# Not GossipFrame's, and the wire is why — the server closes the gossip menu BEFORE it asks, so -# this dialog is free-standing by the time it shows. Needs only UiPanels.xml, far above. -BinderConfirm.xml -# The summon confirm + its event driver (decision 1747): the same StaticPopup registry once more. -# Free-standing like the two around it, and for a stronger reason — a warlock's ritual, a meeting -# stone and a GM `.summon` all raise it with nothing whatever on screen. The only entry here whose -# event carries no arguments: the dialog reads the summoner, the destination and the clock back out -# of the engine every tick, so it needs UiPanels.xml's countdown branch as well as its registry. -ConfirmSummon.xml -# The class trainer's respec confirm + its event driver (decision 1580): the binder confirm's twin -# one line up, free-standing for the identical reason — the server closes the gossip menu before it -# asks. The only entry in the registry that raises a money frame, so it needs MoneyFrame.xml (far -# above, ahead of UiPanels.xml) as well as the StaticPopup engine itself. -TalentWipeConfirm.xml # ── MEASUREMENT (1938): the options window at the reference's slot (FrameXML.toc l.21, before # MainMenuBar l.34) with the four rows it inherits from. ── # The faux-scroll kit (decisions 0247/0250/0251): BenillaScrollBarTemplate (the draggable @@ -371,6 +347,17 @@ ScrollTemplates.xml Interface\FrameXML\UIPanelTemplates.lua Interface\FrameXML\UIPanelTemplates.xml +# ══ THE DIALOG ENGINE — the reference's own, off the player's chain (1751; decision 1960) ══ +# StaticPopup.xml + StaticPopup.lua: StaticPopupDialogs (all 76 of the reference's entries), +# StaticPopup_Show/Hide/Visible/Resize/OnUpdate, the four dialog instances, the edit boxes and the +# money frame. The reference's toc has it at l.23, right after UIPanelTemplates and UIOptionsFrame; +# here too it sits after UIPanelTemplates — the CloseButton it inherits (UIPanelCloseButton) is that +# file's, and UIPanelTemplates cannot rise above ScrollTemplates.xml (see above). Everything that +# registers a dialog of its own (Mail, Auction, the keybindings page) loads below this line. The +# event arms that raise these dialogs are UIParent.xml's, the reference's own home. 0308's engine, +# the 37 dialogs UiPanels.xml carried and the seven free-standing dialog files (Death, Duel, +# QuestShare, EnchantConfirm, BinderConfirm, ConfirmSummon, TalentWipeConfirm) are gone. +Interface\FrameXML\StaticPopup.xml # The Keybindings page MODULE (decision 1008, superseding 0997's standalone window): # the templates + script of the Options window's Keybindings category, over the @@ -828,13 +815,15 @@ SpellBookAdapters.xml # 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 (decision 0668): the O-key friends/ignore/who window over the social -# API (`benilla-ui/src/script/social.rs`) + `crate::ui_social`'s feed/drain. Needs -# UiPanels (the panel manager, `CharacterFrameTabButtonTemplate`, and the StaticPopup -# engine its two name-entry dialogs register into), ScrollTemplates (three faux lists), -# GameTooltip (the newbie tips) and UIDropDownMenu (the who list's variable column) — -# all loaded above. -FriendsFrame.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 +# RaidFrame.xml right below (the reference's own file split, its toc l.71-72). Over the social +# API (`benilla-ui/src/script/social.rs`) and `crate::ui_social`'s feed/drain, the guild seam, +# the party roster. Its twelve dialogs live with the other dialogs in UiPanels.xml; the +# GUILD_INVITE arms are UIParent.xml's, the reference's own home. 0668/1257/1549's transcription +# is gone. +Interface\FrameXML\FriendsFrame.xml # ══ THE RAID PANE — the reference's own, off the player's chain (1751, scoped by 1839) ══ # The social window's fourth tab, in its own file because RaidFrame is a child of FriendsFrame and # a child cannot be declared before its parent — the reference splits it the same way and for the @@ -888,11 +877,16 @@ TradeFrame.xml # rather than the `BENILLA_`-prefixed pair this file kept privately. Fonts.xml's own note said # those two tables would arrive "the day something does" read them; the sourced file is that day. Interface\FrameXML\ItemTextFrame.xml -# The trainer window (decision 0237 phase 3): the read-only class/profession trainer list. -# Loaded right after MerchantFrame per the director's brief (kept adjacent even though this -# v1 uses plain-text costs, not MerchantFrame's coin helpers) and before the other NPC -# session windows below. -TrainerFrame.xml +# ══ THE TRAINER WINDOW — the reference's own LoadOnDemand addon, off the chain (1751; decision 1957) ══ +# No row: `Blizzard_TrainerUI` is the first Blizzard addon benilla loads the way the reference +# does — on demand, from UIParent.xml's TRAINER_SHOW arm through `ClassTrainerFrame_LoadUI` → +# `UIParentLoadAddOn` → `LoadAddOn`, off a registry row `ui_script::addons` reads from the +# chain (its toc, its `## SavedVariables`, its ADDON_LOADED at the end of ITS load). The timing +# is the mechanism: the list packet resets the engine's filter mask, and the addon's +# ADDON_LOADED arm re-pushes the saved filters after it — which eager loading cannot do. Its +# list inherits `ClassTrainerListScrollFrameTemplate` from +# `Interface\FrameXML\ClassTrainerFrameTemplates.xml`, far below, resolved at the addon's +# load. 0237/0247/0251's transcription is gone. # ══ THE BANK WINDOW — the reference's own, off the player's chain (decision 1751, window 2) ══ # `BankFrame`, `BankFrameItem1..24`, `BankFrameBag1..6`, `BankFramePurchaseInfo` and the # `BANK_CONTAINER`/`NUM_BAG_SLOTS`/`NUM_BANKGENERIC_SLOTS`/`NUM_BANKBAGSLOTS` constants — the real diff --git a/crates/benilla-app/src/bindings.rs b/crates/benilla-app/src/bindings.rs index 4e88eb31..a189f10d 100644 --- a/crates/benilla-app/src/bindings.rs +++ b/crates/benilla-app/src/bindings.rs @@ -1178,7 +1178,7 @@ mod tests { assert_eq!( s.eval::(&format!("return {ROW}9Description:GetText()")) .unwrap(), - "JUMP" + crate::ui_script::keybindings_tests::label(&s, "BINDING_NAME_JUMP", "JUMP") ); assert!(s.bind_capture_armed()); let mut app = vm_harness(s); diff --git a/crates/benilla-app/src/capture/probes/dress_census.rs b/crates/benilla-app/src/capture/probes/dress_census.rs index b6e3d4b6..1c369ae1 100644 --- a/crates/benilla-app/src/capture/probes/dress_census.rs +++ b/crates/benilla-app/src/capture/probes/dress_census.rs @@ -82,11 +82,14 @@ type DressQuery = ( ); /// One line per streamed player within [`DEFAULT_RADIUS`], contradictions first. +#[allow(clippy::too_many_arguments)] fn fire_dress_census( mut probe: ResMut, time: ProbeClock, names: Res, - body: Query<&Transform, With>, + body: Query<(Entity, &Transform), With>, + // The self body's parts, one line each (`DRESS_PART`) — the draw-population inventory. + body_parts: crate::entities::BodyPartsDesc, entities: Query, // The body's draw population (`parts=`/`mats=`): every mesh part hanging under the unit — // body batches, each attach model's batches, cards — and how many DISTINCT materials @@ -105,7 +108,7 @@ fn fire_dress_census( } else { -1.0 }; - let Ok(body) = body.single() else { + let Ok((self_entity, body)) = body.single() else { println!("DRESS_CENSUS t={now:.1} NO BODY — not in world, nothing measured"); return; }; @@ -178,6 +181,9 @@ fn fire_dress_census( for (_, _, line) in &rows { println!("{line}"); } + for line in body_parts.describe(self_entity, &children) { + println!("{line}"); + } } /// Mesh parts under `unit` (every depth) and the distinct materials they bind. diff --git a/crates/benilla-app/src/capture/probes/live_fps.rs b/crates/benilla-app/src/capture/probes/live_fps.rs index 4529b6ca..62850b8b 100644 --- a/crates/benilla-app/src/capture/probes/live_fps.rs +++ b/crates/benilla-app/src/capture/probes/live_fps.rs @@ -422,15 +422,19 @@ fn drive_live_fps( let ld = |a: &std::sync::atomic::AtomicU64| { a.load(std::sync::atomic::Ordering::Relaxed) }; + let fx = &benilla_world::particles::render::EFFECT_DRAW_STATS; format!( - " wgpu_bufs={} wgpu_texs={} wgpu_bgs={} draws=o{}/t{}/s{}/ui{}", + " wgpu_bufs={} wgpu_texs={} wgpu_bgs={} draws=o{}/t{}/s{}/ui{} \ + fx={}/{}", ld(&c.0.buffers), ld(&c.0.textures), ld(&c.0.bind_groups), ld(&c.0.draws_opaque), ld(&c.0.draws_transparent), ld(&c.0.draws_shadow), - ld(&c.0.draws_ui) + ld(&c.0.draws_ui), + fx[0].load(std::sync::atomic::Ordering::Relaxed), + fx[1].load(std::sync::atomic::Ordering::Relaxed) ) }) .unwrap_or_default(); diff --git a/crates/benilla-app/src/entities.rs b/crates/benilla-app/src/entities.rs index d9c3fb21..a224b395 100644 --- a/crates/benilla-app/src/entities.rs +++ b/crates/benilla-app/src/entities.rs @@ -120,7 +120,7 @@ use chain_beam::{simulate_chain_beams, spawn_chain_beams}; // The container feed reads the icon column off the same catalog resource (one DBC parse). // The one InventoryType → equipment-slot table (`attach::preview`): the dressing-room feed places a // tried-on item by the very same map the preview it feeds dresses by (decision 1060). -pub(crate) use attach::equip_slot; +pub(crate) use attach::{equip_slot, BodyPartsDesc}; pub(crate) use equipment::ItemDisplays; pub(crate) use equipment::{BoneAttach, Equipment}; // The instruments' read of what a body is actually WEARING vs what it resolved (`WOW_DRESS_CENSUS`). diff --git a/crates/benilla-app/src/entities/attach/mod.rs b/crates/benilla-app/src/entities/attach/mod.rs index 4537343c..e0c0a53d 100644 --- a/crates/benilla-app/src/entities/attach/mod.rs +++ b/crates/benilla-app/src/entities/attach/mod.rs @@ -39,6 +39,50 @@ mod preview; pub(crate) use preview::equip_slot; pub(super) use preview::{build_dressup_preview, build_glue_pet, build_glue_preview}; mod redress; + +/// The instruments' view of one body's draw population — what `WOW_DRESS_CENSUS` prints per +/// part (`DRESS_PARTS`): index, merge group, blend, and the material id, so "why nine draws +/// for one body" is a line of output and not a reading of the M2. Lives here because the +/// group and index components are this module's private bookkeeping (0026's dev seam). +#[derive(bevy::ecs::system::SystemParam)] +pub(crate) struct BodyPartsDesc<'w, 's> { + parts: Query< + 'w, + 's, + ( + &'static dress::DressedPart, + Option<&'static merge::DressedGroup>, + &'static benilla_world::model_render::ModelPart, + &'static MeshMaterial3d, + ), + >, +} + +impl BodyPartsDesc<'_, '_> { + /// One line per drawn part under `unit`, sorted by part index. + pub(crate) fn describe(&self, unit: Entity, children: &Query<&Children>) -> Vec { + let mut rows: Vec<(u32, String)> = children + .iter_descendants(unit) + .filter_map(|e| self.parts.get(e).ok()) + .map(|(part, group, model, mat)| { + let group = group + .map(|g| format!("{:?}", &g.0[..])) + .unwrap_or_else(|| "-".into()); + ( + part.index, + format!( + "DRESS_PART #{:<3} group={group:<14} blend={:?} mat={:?}", + part.index, + model.blend, + mat.0.id() + ), + ) + }) + .collect(); + rows.sort_by_key(|(i, _)| *i); + rows.into_iter().map(|(_, l)| l).collect() + } +} pub(super) use redress::redress_player_looks; /// Set up the skinned instance shared by creatures/players (decision 0019) and animated diff --git a/crates/benilla-app/src/ui_chat/input/mod.rs b/crates/benilla-app/src/ui_chat/input/mod.rs index a6f16287..43a4f860 100644 --- a/crates/benilla-app/src/ui_chat/input/mod.rs +++ b/crates/benilla-app/src/ui_chat/input/mod.rs @@ -233,6 +233,24 @@ pub(super) fn drain_chat_input( if msg.is_empty() { continue; } + // A line with no slash is SPEECH — which is also how a `.gm`-style server command + // travels (the server reads the dot off a SAY). That is the seam's contract + // (`UiScript::push_chat_input`): a typed line takes the stock `ChatEdit_SendText` → + // `SendChatMessage` route since 1948, and a probe's line, which never touched the edit + // box, must reach the wire the same way. The migration dropped this branch with the + // old edit-state sender, and every `WOW_PROBE_CHAT` rig — the crowd raid's forty + // `.partybot add`s, the probe shield's `.cheat god on` — went silent on the server. + if !msg.starts_with('/') { + let cmd = ClientCommand::Chat { + kind: super::edit::SendType::Say.wire(), + target: None, + text: msg.to_string(), + }; + if commands.0.send(cmd).is_err() { + warn!("chat: not connected; line dropped"); + } + continue; + } queue.push((msg.to_string(), parse_line(&table, msg))); } for (msg, parsed) in queue { @@ -697,15 +715,6 @@ pub(super) fn drain_chat_input( // over the same binding the popup row calls, so it enters the same intent queue // (decision 0646 §3; the `/duel` reasoning above, verbatim). ParsedChat::Pvp => script.queue_pvp_toggle(), - // Run the reference's own slash body (see the variant's doc). The argument is a Lua - // string literal, so it is escaped: a `/who` filter legitimately contains quotes - // (`z-"Elwynn Forest"`), and a newline would end the statement. - ParsedChat::Social { verb, arg } => { - let lua = format!("{}(\"{}\")", verb.lua_fn(), parse::escape_lua_string(&arg)); - if let Err(e) = script.run(&lua) { - warn!("ui_chat(social): {e}"); - } - } ParsedChat::Help => { // An honest benilla summary (the ref's HELP_TEXT_LINE pages are a settings-era // nicety; this stays useful and never stale-quotes them). diff --git a/crates/benilla-app/src/ui_chat/input/parse.rs b/crates/benilla-app/src/ui_chat/input/parse.rs index 40cc1e5f..3848fd8b 100644 --- a/crates/benilla-app/src/ui_chat/input/parse.rs +++ b/crates/benilla-app/src/ui_chat/input/parse.rs @@ -27,32 +27,10 @@ pub(in crate::ui_chat) fn escape_lua_string(s: &str) -> String { .collect() } -/// Which social slash command was typed — the selector for the Lua body it runs -/// ([`ParsedChat::Social`], decision 0668). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(in crate::ui_chat) enum SocialVerb { - /// `/who [filter]` — SLASH_WHO 1-2. - Who, - /// `/friends [name]` (aliases `/friend`) — SLASH_FRIENDS 1-4. - Friends, - /// `/removefriend ` (alias `/remfriend`) — SLASH_REMOVEFRIEND 1-4. - RemoveFriend, - /// `/ignore [name]` — SLASH_IGNORE 1-2. Toggles: an ignored name is un-ignored. - Ignore, - /// `/unignore [name]` — SLASH_UNIGNORE 1-2. - Unignore, -} - -impl SocialVerb { - /// The Lua function in `FriendsFrame.xml` holding this verb's reference body. - pub(in crate::ui_chat) fn lua_fn(self) -> &'static str { - match self { - Self::Who => "BenillaSlashWho", - Self::Friends => "BenillaSlashFriends", - Self::RemoveFriend => "BenillaSlashRemoveFriend", - Self::Ignore => "BenillaSlashIgnore", - Self::Unignore => "BenillaSlashUnignore", - } +/// A social slash line as the reference runs it: `SlashCmdList[""]()` (1959). +fn social_body(key: &str, args: &str) -> ParsedChat { + ParsedChat::Lua { + body: format!("SlashCmdList[\"{key}\"](\"{}\")", escape_lua_string(args)), } } @@ -156,13 +134,6 @@ pub(in crate::ui_chat) enum ParsedChat { /// `/pvp` — `TogglePVP()` (decision 0646 §3). Takes no argument: the binding has no state /// form, and the server reads the toggle from our current preference. Pvp, - /// The social verbs (decision 0668) — `/who`, `/friends`, `/removefriend`, `/ignore`, - /// `/unignore`. Each carries the raw argument (`/who`'s is a whole filter string, not a - /// name), and each runs the reference's OWN `SlashCmdList` body, transcribed into - /// `FriendsFrame.xml` as `BenillaSlash*`: those bodies do more than send (a bare `/who` - /// opens the panel and fills its edit box, a bare `/ignore` opens the ignore list), and - /// keeping them in the FrameXML is what stops that behaviour being re-derived here. - Social { verb: SocialVerb, arg: String }, /// `/partytest [lead|raid|invite|mark|ping|off]` — the party-frame dev instrument (decision 0434, the /// `/chattest` pattern): a synthetic roster through the real apply path (`lead` = the same /// roster with US leading, for the leader-only popup rows), a fake pending invite for the @@ -341,28 +312,15 @@ fn slash_command(index: SlashIndex, args: &str) -> ParsedChat { }, S::DuelCancel => ParsedChat::Forfeit, S::Pvp => ParsedChat::Pvp, - // The social verbs (decision 0668). The argument is passed WHOLE: `/who`'s is a filter - // expression (`z-"Elwynn Forest" 1-10`), not a name. - S::Who => ParsedChat::Social { - verb: SocialVerb::Who, - arg: args.to_string(), - }, - S::Friends => ParsedChat::Social { - verb: SocialVerb::Friends, - arg: args.to_string(), - }, - S::RemoveFriend => ParsedChat::Social { - verb: SocialVerb::RemoveFriend, - arg: args.to_string(), - }, - S::Ignore => ParsedChat::Social { - verb: SocialVerb::Ignore, - arg: args.to_string(), - }, - S::Unignore => ParsedChat::Social { - verb: SocialVerb::Unignore, - arg: args.to_string(), - }, + // The social verbs (decision 0668): the reference's own `SlashCmdList` bodies, which the + // stock ChatFrame.lua carries since 1948 — and whose parser claims these lines before + // they ever reach here, so these rows are the shape kept for the day the arm is pruned + // (1948's follow-on). The argument is passed WHOLE: `/who`'s is a filter expression. + S::Who => social_body("WHO", args), + S::Friends => social_body("FRIENDS", args), + S::RemoveFriend => social_body("REMOVEFRIEND", args), + S::Ignore => social_body("IGNORE", args), + S::Unignore => social_body("UNIGNORE", args), // The one-line reference bodies over globals benilla already implements (decision 0881, // the 0668 posture): `InitiateTrade("target")`, `InspectUnit("target")`, // `SetLootMethod(...)`, `RunScript(msg)`. Running the reference's own call keeps the diff --git a/crates/benilla-app/src/ui_chat/tests.rs b/crates/benilla-app/src/ui_chat/tests.rs index dc256c75..afc30f90 100644 --- a/crates/benilla-app/src/ui_chat/tests.rs +++ b/crates/benilla-app/src/ui_chat/tests.rs @@ -451,6 +451,8 @@ fn chat_vm() -> benilla_ui::script::UiScript { "Interface\\FrameXML\\UIPanelTemplates.lua", "Interface\\FrameXML\\UIPanelTemplates.xml", "UiPanels.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", "Interface\\FrameXML\\FloatingChatFrame.xml", ] { crate::ui_script::load_ui_for_test(&s, file); diff --git a/crates/benilla-app/src/ui_items/drain.rs b/crates/benilla-app/src/ui_items/drain.rs index 8b0984b0..489dfd42 100644 --- a/crates/benilla-app/src/ui_items/drain.rs +++ b/crates/benilla-app/src/ui_items/drain.rs @@ -1176,6 +1176,11 @@ mod bind_confirm_tests { "Interface\\FrameXML\\Fonts.xml", "MoneyFrame.xml", "UiPanels.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", + "UIParent.xml", ] { crate::ui_script::load_ui_for_test(s, file); } diff --git a/crates/benilla-app/src/ui_pet/drain.rs b/crates/benilla-app/src/ui_pet/drain.rs index 964db50a..bfcedb40 100644 --- a/crates/benilla-app/src/ui_pet/drain.rs +++ b/crates/benilla-app/src/ui_pet/drain.rs @@ -69,10 +69,16 @@ pub(super) fn drain_pet_actions( return; }; let pressed = script.take_pet_actions(); + let orders = script.take_pet_orders(); let toggles = script.take_pet_autocast_toggles(); let stops = script.take_pet_stop_attacks(); let writes = script.take_pet_set_actions(); - if pressed.is_empty() && toggles.is_empty() && stops == 0 && writes.is_empty() { + if pressed.is_empty() + && orders.is_empty() + && toggles.is_empty() + && stops == 0 + && writes.is_empty() + { return; } let pet_guid = bar.spells.pet_guid; @@ -96,10 +102,19 @@ pub(super) fn drain_pet_actions( // an ATTACK press latch (see [`possessing`]) — which for an ordinary pet is never. let possessing = possessing(pet_store, pet.self_guid.0); - for slot in pressed { - let Some(entry) = slot_entry(&bar, slot) else { - continue; - }; + // A pressed slot and a one-shot order (`PetAttack` & co., 1958) are the same press: the + // slot's own packed word, or the word the verb synthesized — both re-enter the reference's + // one dispatcher (`0x4bd1d0`), so they share one loop. The slot number only ever labelled logs. + let presses: Vec<(u32, PetActionEntry)> = pressed + .into_iter() + .filter_map(|slot| slot_entry(&bar, slot).map(|e| (slot, e))) + .chain( + orders + .into_iter() + .map(|packed| (0, PetActionEntry::from(packed))), + ) + .collect(); + for (slot, entry) in presses { // The spell arm's early exit (wow-re §10.1, `0x4bd240`–`0x4bd2ad`): a press on a spell the // pet is already running takes the aura OFF and **returns** — `CMSG_PET_ACTION` never // leaves, so it is a cancel, not a re-cast. Nothing is latched locally either: the icon diff --git a/crates/benilla-app/src/ui_script/action_bar_tests.rs b/crates/benilla-app/src/ui_script/action_bar_tests.rs index cebdf175..7a77b8b3 100644 --- a/crates/benilla-app/src/ui_script/action_bar_tests.rs +++ b/crates/benilla-app/src/ui_script/action_bar_tests.rs @@ -207,6 +207,10 @@ fn load_action_bar(s: &UiScript) { // reference declares them in UIOptionsFrame_Init (1938) — and the manifest loads it before // the bars. super::test_ui::load_ui(s, "UiPanels.xml"); + // The dialog engine — the keybindings page registers its two confirms into its table (1960). + super::test_ui::load_ui(s, r"Interface\FrameXML\BasicControls.xml"); // `TEXT` + super::test_ui::load_ui(s, r"Interface\FrameXML\LocaleProperties.lua"); // `GetText` + super::test_ui::load_ui(s, r"Interface\FrameXML\StaticPopup.xml"); super::test_ui::load_ui(s, "Interface\\FrameXML\\UIDropDownMenu.xml"); super::test_ui::load_ui(s, "ScrollTemplates.xml"); super::test_ui::load_ui(s, "KeyBindingsPage.xml"); @@ -1371,6 +1375,9 @@ fn the_main_bar_pages_and_a_bonus_page_still_outranks_it() { "ScrollTemplates.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", "KeyBindingsPage.xml", "OptionsFrame.xml", "Interface\\FrameXML\\MultiActionBars.xml", diff --git a/crates/benilla-app/src/ui_script/addons.rs b/crates/benilla-app/src/ui_script/addons.rs index dd643450..b3598d35 100644 --- a/crates/benilla-app/src/ui_script/addons.rs +++ b/crates/benilla-app/src/ui_script/addons.rs @@ -370,7 +370,56 @@ fn root_from(home: Option) -> Option { /// runs on one machine load the same interface. Real ordering constraints between addons are /// expressed by `## Dependencies:` and honoured by [`load_third_party`]'s recursion, which is the /// mechanism the reference actually uses. +/// The reference's own LoadOnDemand addons, as its `Interface\\AddOns\\` folder names them — the +/// twelve `Blizzard_*` folders on a 1.12 install, each a `.pub` decoy on disk with the real files +/// inside the archive (the InspectUI header in the manifest). Probed against the chain, so a +/// chain without one simply has no such addon. +const BLIZZARD_ADDONS: [&str; 12] = [ + "Blizzard_AuctionUI", + "Blizzard_BattlefieldMinimap", + "Blizzard_BindingUI", + "Blizzard_CombatText", + "Blizzard_CraftUI", + "Blizzard_GMSurveyUI", + "Blizzard_InspectUI", + "Blizzard_MacroUI", + "Blizzard_RaidUI", + "Blizzard_TalentUI", + "Blizzard_TradeSkillUI", + "Blizzard_TrainerUI", +]; + +/// 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. +fn chain_addons() -> Vec { + 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)); + toc.load_on_demand().then(|| Addon { + name: (*name).to_string(), + toc, + source: Source::Chain, + }) + }) + .collect() +} + fn discover() -> Vec { + let mut found = discover_folder(); + found.extend(chain_addons()); + found +} + +/// The player's own addons — every folder under the AddOns root with a `.toc`. +fn discover_folder() -> Vec { let Some(root) = root() else { return Vec::new(); }; @@ -424,7 +473,9 @@ fn manifest_in(dir: &Path, name: &str) -> Option { /// Build the AddOn API's registry row for one discovered addon — every `.toc` directive the /// eleven verbs can be asked about, read once here rather than re-parsed per call. fn info_for(addon: &Addon) -> benilla_ui::script::AddOnInfo { - info_from_toc(&addon.name, &addon.toc) + let mut info = info_from_toc(&addon.name, &addon.toc); + info.chain = matches!(addon.source, Source::Chain); + info } /// [`info_for`]'s body, over the two things it actually reads. @@ -462,6 +513,7 @@ pub(crate) fn info_from_toc(name: &str, toc: &Toc) -> benilla_ui::script::AddOnI // The version gate's dword (decision 1292) — the client's own parse: leading integer, // 0 when the manifest is silent (and 0 is out of date, not "unknown"). interface: toc.interface_version(), + chain: false, // `info_for` stamps the chain rows; the harness's rows are folder addons } } @@ -553,7 +605,9 @@ impl InstalledAddOn { /// everything as enabled. pub(crate) fn installed(identity: Option<&(String, String)>) -> Vec { let disabled = disabled_set(identity); - discover() + // The player's own folder only: the glue's list is what a player can toggle, and the + // reference's Blizzard addons are not in it. + discover_folder() .into_iter() .map(|addon| InstalledAddOn { enabled: !disabled.contains(&addon.name.to_ascii_lowercase()), @@ -697,6 +751,8 @@ pub(super) fn load_third_party( // rather than answer whatever a previous session left, and an addon that asks before any // exist is asking a real question. let mut infos: Vec<_> = addons.iter().map(info_for).collect(); + // The chain-sourced rows read through the reference's own store (1957). + script.set_addon_chain_reader(Box::new(super::reference_ui::read)); if let Some(text) = enable_state_path(identity).and_then(|p| std::fs::read_to_string(p).ok()) { for (name, enabled) in parse_enable_state(&text) { if let Some(i) = infos @@ -925,6 +981,44 @@ 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. + #[test] + fn the_chain_carries_blizzards_load_on_demand_addons_as_registry_rows() { + let _data = benilla_formats::wow_data_or_skip!(); + let rows = chain_addons(); + assert!( + rows.iter().any(|a| a.name == "Blizzard_TrainerUI"), + "the trainer addon is a row: {:?}", + rows.iter().map(|a| &a.name).collect::>() + ); + for a in &rows { + assert!(a.toc.load_on_demand(), "{} is LoadOnDemand", a.name); + assert!( + matches!(a.source, Source::Chain), + "{} comes off the chain", + a.name + ); + assert!( + info_for(a).chain, + "{}'s registry row is marked chain-sourced", + a.name + ); + } + let eager = super::super::manifest::chain_addons(&Addon::builtin().toc.files); + for e in &eager { + 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" + ); + } + // The glue's list is the player's folder alone. + assert!(installed(None) + .iter() + .all(|a| !a.name.starts_with("Blizzard_"))); + } + use super::*; /// Build an addon over a temp AddOns root, so the walk can be tested without an install. @@ -1607,7 +1701,12 @@ mod tests { assert!(failures.is_empty(), "load errors: {failures:?}"); // Discovery saw it at all — the `.toc` decoded rather than read as absent. - assert_eq!(script.eval::("return GetNumAddOns()").ok(), Some(1)); + // The one in the folder, plus the chain's Blizzard LoadOnDemand rows on a machine that + // has a chain (1957) — the registry is one list, as the reference's is. + assert_eq!( + script.eval::("return GetNumAddOns()").ok(), + Some(1 + chain_addons().len() as i64) + ); assert_eq!( script .eval::("return GetAddOnMetadata('Umlaut', 'Title')") @@ -1705,7 +1804,12 @@ mod tests { let mut script = UiScript::new().unwrap(); let _ = load_third_party(&mut script, None, true); - assert_eq!(script.eval::("return GetNumAddOns()").ok(), Some(1)); + // The one in the folder, plus the chain's Blizzard LoadOnDemand rows on a machine that + // has a chain (1957) — the registry is one list, as the reference's is. + assert_eq!( + script.eval::("return GetNumAddOns()").ok(), + Some(1 + chain_addons().len() as i64) + ); assert_eq!( script .eval::>( @@ -1861,7 +1965,12 @@ mod tests { let _ = load_third_party(&mut script, None, true); // Discovered and described, but NOT run. - assert_eq!(script.eval::("return GetNumAddOns()").ok(), Some(1)); + // The one in the folder, plus the chain's Blizzard LoadOnDemand rows on a machine that + // has a chain (1957) — the registry is one list, as the reference's is. + assert_eq!( + script.eval::("return GetNumAddOns()").ok(), + Some(1 + chain_addons().len() as i64) + ); assert_eq!( script .eval::("return IsAddOnLoadOnDemand(1) == 1") @@ -1986,8 +2095,14 @@ mod tests { save_enable_state(&script, Some(&id)); let written = std::fs::read_to_string(home.join("addons/Realm-Char.txt")).unwrap(); + // The folder's two in registry order, then the chain's Blizzard rows (1957) — every + // registry row has a line, the reference's own one-line-per-addon format. + let mut expected = String::from("Drop: disabled\nKeep: enabled\n"); + for a in chain_addons() { + expected.push_str(&format!("{}: enabled\n", a.name)); + } assert_eq!( - written, "Drop: disabled\nKeep: enabled\n", + written, expected, "the reference's own one-line-per-addon format" ); diff --git a/crates/benilla-app/src/ui_script/binder_tests.rs b/crates/benilla-app/src/ui_script/binder_tests.rs index c84d646d..dcabdbeb 100644 --- a/crates/benilla-app/src/ui_script/binder_tests.rs +++ b/crates/benilla-app/src/ui_script/binder_tests.rs @@ -16,7 +16,11 @@ fn setup() -> UiScript { load_xml(&s, "UiPanels.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); - load_xml(&s, "BinderConfirm.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + 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, "UIParent.xml"); s.set_binder_pending(true); s } diff --git a/crates/benilla-app/src/ui_script/buff_tests.rs b/crates/benilla-app/src/ui_script/buff_tests.rs index aa3a8aec..92edb8ef 100644 --- a/crates/benilla-app/src/ui_script/buff_tests.rs +++ b/crates/benilla-app/src/ui_script/buff_tests.rs @@ -37,6 +37,9 @@ fn harness() -> UiScript { load_xml(&s, "UiPanels.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"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); load_xml(&s, "UIParent.xml"); load_xml(&s, "Cooldown.xml"); load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml"); diff --git a/crates/benilla-app/src/ui_script/cast_tests.rs b/crates/benilla-app/src/ui_script/cast_tests.rs index 9404d969..d5feb42a 100644 --- a/crates/benilla-app/src/ui_script/cast_tests.rs +++ b/crates/benilla-app/src/ui_script/cast_tests.rs @@ -398,6 +398,8 @@ fn managed_positions_track_the_bottom_bar_stack() { load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua"); load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml"); load_xml(&s, "UiPanels.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); load_xml(&s, "Interface\\FrameXML\\FloatingChatFrame.xml"); // The loader's post-load bootstrap, replayed with no bars in existence: the bare bases. diff --git a/crates/benilla-app/src/ui_script/character_tests.rs b/crates/benilla-app/src/ui_script/character_tests.rs index 83205115..c6c8fef1 100644 --- a/crates/benilla-app/src/ui_script/character_tests.rs +++ b/crates/benilla-app/src/ui_script/character_tests.rs @@ -1209,6 +1209,10 @@ fn an_addons_tab_click_selects_through_the_generic_entry_point() { load_xml(&s, "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"); + 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, "UIParent.xml"); // A conforming row: tabs named `Tab1..N` (what `PanelTemplates_UpdateTabs` getglobals) diff --git a/crates/benilla-app/src/ui_script/chat_options_tests.rs b/crates/benilla-app/src/ui_script/chat_options_tests.rs index d6f6d89a..1f0f4aab 100644 --- a/crates/benilla-app/src/ui_script/chat_options_tests.rs +++ b/crates/benilla-app/src/ui_script/chat_options_tests.rs @@ -48,6 +48,10 @@ fn chat_with_menu() -> UiScript { "ScrollTemplates.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\StaticPopup.xml", "Interface\\FrameXML\\ColorPickerFrame.xml", "Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from "Interface\\FrameXML\\GlobalStrings.lua", diff --git a/crates/benilla-app/src/ui_script/chat_resize_tests.rs b/crates/benilla-app/src/ui_script/chat_resize_tests.rs index e54b3dfb..80540903 100644 --- a/crates/benilla-app/src/ui_script/chat_resize_tests.rs +++ b/crates/benilla-app/src/ui_script/chat_resize_tests.rs @@ -26,6 +26,10 @@ fn chat_ui() -> UiScript { "ScrollTemplates.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", "Interface\\FrameXML\\ColorPickerFrame.xml", "Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from "Interface\\FrameXML\\GlobalStrings.lua", diff --git a/crates/benilla-app/src/ui_script/chat_tests.rs b/crates/benilla-app/src/ui_script/chat_tests.rs index 5e498554..fd08bcaa 100644 --- a/crates/benilla-app/src/ui_script/chat_tests.rs +++ b/crates/benilla-app/src/ui_script/chat_tests.rs @@ -29,6 +29,8 @@ fn chat_frame() -> UiScript { // bar and the chat files; `FCF_OnUpdate` — the dock's hover, fade and drag driver — runs // from UIParent's OnUpdate, as in the reference. load_xml(&s, "UiPanels.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); load_xml(&s, "UIParent.xml"); // `FCF_ValidateChatFramePosition` (a tab-drag stop) reads `MainMenuBar:GetHeight()`; the bar's // own load-time chain precedes it, as in the action-bar harness. @@ -994,6 +996,10 @@ fn the_chat_menu_builds_its_rows_on_the_references_kit() { "UiPanels.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", "GameTooltip.xml", r"Interface\FrameXML\UIMenu.xml", "Interface\\FrameXML\\GlobalStrings.lua", diff --git a/crates/benilla-app/src/ui_script/cinematic_tests.rs b/crates/benilla-app/src/ui_script/cinematic_tests.rs index 1c6e8ad2..cf0fd768 100644 --- a/crates/benilla-app/src/ui_script/cinematic_tests.rs +++ b/crates/benilla-app/src/ui_script/cinematic_tests.rs @@ -28,6 +28,10 @@ fn ui_with_the_cinematic_frame() -> UiScript { load_xml(&s, "UiPanels.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"); + 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, "Interface\\FrameXML\\CinematicFrame.xml"); s.resolve(); s diff --git a/crates/benilla-app/src/ui_script/color_picker_tests.rs b/crates/benilla-app/src/ui_script/color_picker_tests.rs index 6bebc046..b1eff707 100644 --- a/crates/benilla-app/src/ui_script/color_picker_tests.rs +++ b/crates/benilla-app/src/ui_script/color_picker_tests.rs @@ -26,6 +26,10 @@ fn picker() -> UiScript { "ScrollTemplates.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", "Interface\\FrameXML\\ColorPickerFrame.xml", ] { load_xml(&s, file); diff --git a/crates/benilla-app/src/ui_script/combo_frame_tests.rs b/crates/benilla-app/src/ui_script/combo_frame_tests.rs index f8b48742..44da3c04 100644 --- a/crates/benilla-app/src/ui_script/combo_frame_tests.rs +++ b/crates/benilla-app/src/ui_script/combo_frame_tests.rs @@ -23,11 +23,15 @@ fn load_combo_frame() -> UiScript { load_xml(&s, "MoneyFrame.xml"); load_xml(&s, "GameTooltip.xml"); load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); - load_xml(&s, "UnitPopup.xml"); + load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); + load_xml(&s, "Interface\\FrameXML\\UnitPopup.xml"); load_xml(&s, "MoneyFrame.xml"); load_xml(&s, "UiPanels.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua"); load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml"); load_xml(&s, "Interface\\FrameXML\\BuffFrame.xml"); diff --git a/crates/benilla-app/src/ui_script/death_tests.rs b/crates/benilla-app/src/ui_script/death_tests.rs index a47de347..a6cac816 100644 --- a/crates/benilla-app/src/ui_script/death_tests.rs +++ b/crates/benilla-app/src/ui_script/death_tests.rs @@ -15,7 +15,11 @@ fn setup() -> UiScript { load_xml(&s, "UiPanels.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); - load_xml(&s, "DeathFrame.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + 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, "UIParent.xml"); s } @@ -38,7 +42,7 @@ fn death_popup_counts_down_and_release_queues_repop() { assert_eq!( s.eval::("return StaticPopup1Text:GetText()") .unwrap(), - "5 minutes until release", + "5 Minutes until release", "the countdown renders through the engine's DEATH per-tick text" ); // ESC must NOT close it (no hideOnEscape on DEATH — the ref's law). @@ -249,7 +253,7 @@ fn resurrect_request_picks_variant_and_answers() { fn xp_loss_two_step_confirm_then_range_hide() { let mut s = setup(); s.set_death(DeathUiState { - sickness_duration: Some("8 minutes".into()), + sickness_duration: Some("8 Minutes".into()), spirit_healer_in_range: true, ..Default::default() }); @@ -262,7 +266,7 @@ fn xp_loss_two_step_confirm_then_range_hide() { .eval::("return StaticPopup1Text:GetText()") .unwrap(); assert!( - text.contains("afflicted by 8 minutes of Resurrection Sickness"), + text.contains("afflicted by 8 Minutes of Resurrection Sickness"), "the sickness duration formats into CONFIRM_XP_LOSS: {text}" ); // First Accept: the AGAIN text swaps in, the dialog stays, nothing queues. XP_LOSS's verbatim @@ -318,7 +322,7 @@ fn xp_loss_two_step_confirm_then_range_hide() { fn xp_loss_cancel_then_reconfirm_reshows_with_the_alert_dress() { let mut s = setup(); s.set_death(DeathUiState { - sickness_duration: Some("8 minutes".into()), + sickness_duration: Some("8 Minutes".into()), spirit_healer_in_range: true, ..Default::default() }); @@ -414,7 +418,7 @@ fn corpse_range_events_drive_recover_corpse() { assert_eq!( s.eval::("return StaticPopup1Text:GetText()") .unwrap(), - "2 seconds until resurrection" + "2 Seconds until resurrection" ); s.tick(1.6); assert_eq!( diff --git a/crates/benilla-app/src/ui_script/delete_item_tests.rs b/crates/benilla-app/src/ui_script/delete_item_tests.rs index 1dab4349..c7fa3caf 100644 --- a/crates/benilla-app/src/ui_script/delete_item_tests.rs +++ b/crates/benilla-app/src/ui_script/delete_item_tests.rs @@ -61,6 +61,17 @@ fn setup() -> UiScript { load_xml(&s, "UiPanels.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"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + // 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, r"Interface\FrameXML\UIDropDownMenu.xml"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); + load_xml(&s, r"Interface\FrameXML\FloatingChatFrame.xml"); // declares ChatFrameEditBox + load_xml(&s, "UIParent.xml"); s.set_money(0); s } @@ -437,13 +448,14 @@ fn no_on_the_typed_confirm_clears_and_leaves_the_box_empty_for_next_time() { assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); } -/// ESC out of the focused box: 1743's named divergence. The reference's `DELETE_GOOD_ITEM` names -/// no `EditBoxOnEscapePressed`, so in 1.12 the focused box swallows the key and the destroy -/// confirm cannot be dismissed with ESC at all. benilla's engine falls back to the ordinary -/// hideOnEscape leg — the entry's `OnCancel` (`ClearCursor()`), then hide — so the item is -/// released rather than left held under a dialog that will not close. +/// ESC inside the typed confirm's focused box is SWALLOWED, the reference's own behaviour (1960): +/// the stock `DELETE_GOOD_ITEM` entry defines no `EditBoxOnEscapePressed`, so the box's +/// `StaticPopup_EditBoxOnEscapePressed` finds nothing to call and the dialog stays up with the +/// item still on the cursor — the player answers with No. (ADD_FRIEND and its kin hide on ESC +/// because their entries say so; this one does not.) Our engine used to fall through to a +/// cancel here, which the earlier form of this test pinned. #[test] -fn escape_out_of_the_typed_confirm_cancels_the_way_every_other_popup_does() { +fn escape_in_the_typed_confirms_box_is_swallowed_as_the_reference_leaves_it() { let mut s = setup(); drop_in_world(&mut s, 871, "Flurry Axe", 4); assert!(s @@ -452,10 +464,15 @@ fn escape_out_of_the_typed_confirm_cancels_the_way_every_other_popup_does() { assert!(s.key_input("ESCAPE"), "the focused box consumes ESCAPE"); assert!( - !s.eval::("return StaticPopup1:IsVisible()").unwrap(), - "ESC closes it" + s.eval::("return StaticPopup1:IsVisible()").unwrap(), + "the dialog stays: the entry names no escape handler" ); - assert!(s.cursor_item().is_none(), "and runs OnCancel's ClearCursor"); + assert!( + s.eval::("return StaticPopup1EditBox:HasFocus()") + .unwrap(), + "and the box keeps focus" + ); + assert!(s.cursor_item().is_some(), "the item is still on the cursor"); assert!(s.take_container_destroys().is_empty(), "ESC never destroys"); assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); } diff --git a/crates/benilla-app/src/ui_script/dressup_tests.rs b/crates/benilla-app/src/ui_script/dressup_tests.rs index ff554ac6..e6a5422d 100644 --- a/crates/benilla-app/src/ui_script/dressup_tests.rs +++ b/crates/benilla-app/src/ui_script/dressup_tests.rs @@ -34,7 +34,9 @@ const JERKY_LINK: &str = "|cffffffff|Hitem:117|h[Tough Jerky]|h|r"; /// with these, in manifest order. const ROOM_UI: &[&str] = &[ "Interface\\FrameXML\\UIDropDownMenu.xml", - "UnitPopup.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", // `TEXT`, which UnitPopup.lua reads at file scope + "Interface\\FrameXML\\UnitPopup.xml", "Interface\\FrameXML\\ItemRef.xml", "Interface\\FrameXML\\MerchantFrame.xml", "Interface\\FrameXML\\StackSplitFrame.xml", @@ -46,6 +48,8 @@ const ROOM_UI: &[&str] = &[ "Interface\\FrameXML\\UIPanelTemplates.lua", "Interface\\FrameXML\\UIPanelTemplates.xml", "UiPanels.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", "Interface\\FrameXML\\FloatingChatFrame.xml", ]; @@ -60,6 +64,10 @@ fn load_room(s: &UiScript) { "UiPanels.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\StaticPopup.xml", "UIParent.xml", "GameTooltip.xml", "Cooldown.xml", @@ -115,6 +123,8 @@ fn shown_paper_doll() -> UiScript { "Interface\\FrameXML\\UIPanelTemplates.lua", "Interface\\FrameXML\\UIPanelTemplates.xml", "UiPanels.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", "Interface\\FrameXML\\FloatingChatFrame.xml", ] { super::test_ui::load_ui_strict(&s, file); diff --git a/crates/benilla-app/src/ui_script/duel_tests.rs b/crates/benilla-app/src/ui_script/duel_tests.rs index e6e8fac7..2f458503 100644 --- a/crates/benilla-app/src/ui_script/duel_tests.rs +++ b/crates/benilla-app/src/ui_script/duel_tests.rs @@ -15,7 +15,11 @@ fn setup() -> UiScript { load_xml(&s, "UiPanels.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); - load_xml(&s, "DuelFrame.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + 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, "UIParent.xml"); s } @@ -77,7 +81,7 @@ fn out_of_bounds_counts_down_and_inbounds_dismisses_it() { assert_eq!( s.eval::("return StaticPopup1Text:GetText()") .unwrap(), - "Exiting duel area, you will forfeit in 10 seconds.", + "Exiting duel area, you will forfeit in 10 Seconds.", "the engine's countdown branch fills %d %s" ); s.fire_event("DUEL_INBOUNDS", vec![]); diff --git a/crates/benilla-app/src/ui_script/durability_tests.rs b/crates/benilla-app/src/ui_script/durability_tests.rs index 29216d43..22dd7b01 100644 --- a/crates/benilla-app/src/ui_script/durability_tests.rs +++ b/crates/benilla-app/src/ui_script/durability_tests.rs @@ -20,6 +20,10 @@ fn harness() -> UiScript { "UiPanels.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", "UIParent.xml", "GameTooltip.xml", "MinimapCluster.xml", @@ -393,6 +397,9 @@ fn the_quest_tracker_stacks_below_the_durability_guy() { "UiPanels.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", "UIParent.xml", "GameTooltip.xml", "MinimapCluster.xml", diff --git a/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs b/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs index fbb7031d..98e86be5 100644 --- a/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs +++ b/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs @@ -20,7 +20,11 @@ fn setup() -> UiScript { load_xml(&s, "UiPanels.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); - load_xml(&s, "EnchantConfirm.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + 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, "UIParent.xml"); s } diff --git a/crates/benilla-app/src/ui_script/errors_tests.rs b/crates/benilla-app/src/ui_script/errors_tests.rs index 09ff622f..b26d5f66 100644 --- a/crates/benilla-app/src/ui_script/errors_tests.rs +++ b/crates/benilla-app/src/ui_script/errors_tests.rs @@ -116,6 +116,9 @@ fn an_error_toast_draws_over_an_open_panel_window() { load_xml(&s, "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"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml"); load_xml(&s, "Cooldown.xml"); load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); // BenillaMoney_* — QuestLogDetail's reward money row diff --git a/crates/benilla-app/src/ui_script/escape_tests.rs b/crates/benilla-app/src/ui_script/escape_tests.rs index 6a5fab8a..efd70e0f 100644 --- a/crates/benilla-app/src/ui_script/escape_tests.rs +++ b/crates/benilla-app/src/ui_script/escape_tests.rs @@ -155,6 +155,8 @@ fn escape_is_consumed_by_a_focused_editbox_and_leaves_windows_open() { load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua"); load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml"); load_xml(&s, "UiPanels.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); load_xml(&s, "Interface\\FrameXML\\FloatingChatFrame.xml"); s.set_money(0); s.set_container(0, Some(one_item_backpack())); @@ -191,6 +193,10 @@ fn escape_closes_the_options_window_before_opening_the_menu() { load_xml(&s, "UiPanels.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"); + 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\\UIDropDownMenu.xml"); load_xml(&s, "ScrollTemplates.xml"); // the Keybindings page's faux-scroll kit (1008) @@ -251,6 +257,8 @@ fn escape_closes_an_open_stack_split_frame() { load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua"); load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml"); load_xml(&s, "UiPanels.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); load_xml(&s, "Interface\\FrameXML\\FloatingChatFrame.xml"); load_xml(&s, "Interface\\FrameXML\\StackSplitFrame.xml"); s.set_money(0); @@ -499,6 +507,10 @@ fn an_addon_frame_registered_in_uispecialframes_closes_on_escape() { load_xml(&s, "UiPanels.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"); + 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, "GameMenuFrame.xml"); // The addon's three lines, verbatim in shape. diff --git a/crates/benilla-app/src/ui_script/fade_tests.rs b/crates/benilla-app/src/ui_script/fade_tests.rs index 2776ef2f..4a551302 100644 --- a/crates/benilla-app/src/ui_script/fade_tests.rs +++ b/crates/benilla-app/src/ui_script/fade_tests.rs @@ -20,6 +20,10 @@ fn an_idle_fade_driver_parks_itself_off_the_tick() { load_xml(&s, "UiPanels.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"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); s.resolve(); s.run( "BENILLA_TEST_FADE_TICKS = 0\n\ @@ -59,6 +63,10 @@ fn a_started_fade_still_ramps_and_the_driver_reparks_after() { load_xml(&s, "UiPanels.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"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); s.resolve(); s.run(r#"CreateFrame("Frame", "BenillaFadeProbe")"#) .unwrap(); @@ -120,6 +128,10 @@ fn a_flash_alternates_then_stops_and_the_driver_reparks() { load_xml(&s, "UiPanels.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"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); s.resolve(); // Parked until something asks — the whole point of the driver over UIParent's own OnUpdate. diff --git a/crates/benilla-app/src/ui_script/frame_flag_gate.rs b/crates/benilla-app/src/ui_script/frame_flag_gate.rs index 682e5563..5cabbad1 100644 --- a/crates/benilla-app/src/ui_script/frame_flag_gate.rs +++ b/crates/benilla-app/src/ui_script/frame_flag_gate.rs @@ -192,12 +192,6 @@ const KNOWN: &[Known] = &[ "the enchant slot: ours inherits BenillaTradeEnchantItemTemplate, the reference's the \ ordinary PlayerTradeItemTemplate. A structural difference in our own trade window.", }, - Known { - frame: "WhoFrameDropDown", - flag: Flag::Mouse, - why: "the reference's /who sort dropdown carries its own handlers over the shared \ - UIDropDownMenuTemplate; ours takes the template alone.", - }, Known { frame: "WorldStateAlwaysUpFrame", flag: Flag::Mouse, @@ -603,11 +597,11 @@ fn the_shipped_frames_carry_the_references_flags() { } assert!( - // 1948 retired our ChatFrame.xml, 1952 our SpellBookFrame.xml, 1953 our PetActionBar.xml - // and 1956 our SkillFrame.xml (their frames are the reference's own now), which took the - // paired count from the low 400s to the high 310s; the floor guards the pairing, not the - // census. - compared > 300, + // 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, "only {compared} frames compared — the pairing broke, and the sweep guards nothing" ); assert!( diff --git a/crates/benilla-app/src/ui_script/friends_tests.rs b/crates/benilla-app/src/ui_script/friends_tests.rs index 1056c9ca..b2b4a223 100644 --- a/crates/benilla-app/src/ui_script/friends_tests.rs +++ b/crates/benilla-app/src/ui_script/friends_tests.rs @@ -15,50 +15,9 @@ use super::test_ui::load_ui_strict as load_xml; fn setup() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, "MoneyFrame.xml"); - load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); - load_xml(&s, "UiPanels.xml"); - load_xml(&s, "GameTooltip.xml"); - load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); - // The row right-click menu is the shared UnitPopup "FRIEND" menu, so the window's slice of - // the manifest includes it (it loads well before FriendsFrame.xml in the real order). - load_xml(&s, "UIParent.xml"); - load_xml(&s, "UnitPopup.xml"); - load_xml(&s, "ScrollTemplates.xml"); - // The guild pane's frames inherit the reference's shared UIPanelButtonTemplate / - // UIPanelCloseButton / UIPanelScrollFrameTemplate rather than a private copy (decision 1257), - // so this file's slice needs the shared kit — and the strict `load_xml` above is what would - // otherwise let those frames load art-less and silent. - load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); - load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); - load_xml(&s, "FriendsFrame.xml"); - // The social window's fourth tab lives in its own file, and it is part of THIS window's - // manifest slice now: `BENILLA_FRIENDS_SUBFRAMES` names "RaidFrame", and both - // `FriendsFrame_ShowSubFrame` and `FriendsFrame_OnHide` resolve every name in that list - // through `getglobal` and call `:Hide()` on it. The reference's list names it too and never - // guards, because there RaidFrame.xml is FrameXML and always loaded — so the guard belongs in - // the harness's load order, not in shipped Lua defending against a state the client cannot be - // in (decision 1549). - // Stock `RaidFrame_OnLoad` reconciles the party frames the moment it loads - // (`RaidOptionsFrame_UpdatePartyFrames` -> `HidePartyFrame`/`ShowPartyFrame`), so this pane - // drags in the whole unit-frame cluster — and it must be the REAL frames: loading only - // `PartyFrame.lua` for the two names gets "attempt to index a nil value" the first time one - // iterates. The manifest already seats these far above RaidFrame (1874). - // The reference's own `BasicControls.xml`, which is manifest entry 3 — far above the pane - // (1874). It is here for the error/message pair and `TEXT`. NOTE: unlike `raid_tests`, this - // harness does not load `UIParent.xml`, so `UIParentLoadAddOn` — which 1881 moved there out - // of BasicControls — is absent. Nothing this file exercises reaches it; if a future - // assertion drives `RaidFrame_LoadUI`, that is the line to add. - load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\TextStatusBar.lua"); - load_xml(&s, r"Interface\FrameXML\TextStatusBar.xml"); - load_xml(&s, r"Interface\FrameXML\BuffFrame.xml"); - load_xml(&s, r"Interface\FrameXML\UnitFrame.xml"); - load_xml(&s, r"Interface\FrameXML\CombatFeedback.xml"); - load_xml(&s, r"Interface\FrameXML\PartyFrame.xml"); - load_xml(&s, r"Interface\FrameXML\RaidFrame.xml"); - load_xml(&s, r"Interface\AddOns\Blizzard_RaidUI\Blizzard_RaidUI.xml"); + for f in super::test_ui::SOCIAL_UI { + load_xml(&s, f); + } s } @@ -171,17 +130,17 @@ fn friend_rows_render_the_online_and_offline_templates() { ); assert_eq!( - s.eval::("return FriendsFrameFriendButton1NameLocation:GetText()") + s.eval::("return FriendsFrameFriendButton1ButtonTextNameLocation:GetText()") .unwrap(), "Onerogue |cffffffff- Elwynn Forest|r " ); assert_eq!( - s.eval::("return FriendsFrameFriendButton1Info:GetText()") + s.eval::("return FriendsFrameFriendButton1ButtonTextInfo:GetText()") .unwrap(), "Level 60 Rogue" ); assert_eq!( - s.eval::("return FriendsFrameFriendButton2NameLocation:GetText()") + s.eval::("return FriendsFrameFriendButton2ButtonTextNameLocation:GetText()") .unwrap(), "|cff999999Twomage - Offline|r" ); @@ -270,8 +229,19 @@ fn the_friend_buttons_queue_their_verbs() { benilla_ui::script::PartyRequest::InviteName(n) if n == "Twomage" ))); + // Send Message is the reference's `ChatFrame_OpenChat("/w Twomage ")`: the box shows with + // the text PENDING (`editBox.setText = 1`), applied by its next OnUpdate, whose OnTextSet + // runs the parse that turns the line into a whisper to the friend (1959). s.run("FriendsFrame_SendMessage()").unwrap(); - assert_eq!(s.take_tell_requests(), vec!["Twomage".to_string()]); + s.tick(0.05); + assert!(s + .eval::("return ChatFrameEditBox:IsVisible()") + .unwrap()); + assert_eq!( + s.eval::<(String, String)>("return ChatFrameEditBox.chatType, ChatFrameEditBox.tellTarget") + .unwrap(), + ("WHISPER".to_string(), "Twomage".to_string()) + ); } /// The Ignore toggle-tab swaps tab 1's list without leaving the tab, and the ignore rows render @@ -304,12 +274,12 @@ fn the_ignore_list_is_the_other_half_of_tab_one() { "Ignore List" ); assert_eq!( - s.eval::("return FriendsFrameIgnoreButton1Name:GetText()") + s.eval::("return FriendsFrameIgnoreButton1ButtonTextName:GetText()") .unwrap(), "Spammer" ); assert_eq!( - s.eval::("return FriendsFrameIgnoreButton2Name:GetText()") + s.eval::("return FriendsFrameIgnoreButton2ButtonTextName:GetText()") .unwrap(), "Ninja" ); @@ -328,9 +298,12 @@ fn the_ignore_list_is_the_other_half_of_tab_one() { assert!(s .eval::("return FriendsListFrame:IsVisible()") .unwrap()); + // The reference's `ShowIgnorePanel` shows the WINDOW (its tab switch is commented out in the + // stock file), so the list that was up stays up — the friends list here (1959). s.run("ShowIgnorePanel()").unwrap(); + assert!(s.eval::("return FriendsFrame:IsVisible()").unwrap()); assert!(s - .eval::("return IgnoreListFrame:IsVisible()") + .eval::("return FriendsListFrame:IsVisible()") .unwrap()); } @@ -483,10 +456,13 @@ fn the_who_buttons_need_a_selected_row() { }, "WHO_LIST_UPDATE", ); + // The reference keeps the selection across answers: `WhoList_Update` enables the buttons + // whenever `WhoFrame.selectedWho` is set, and its WHO_LIST_UPDATE arm clears nothing. (Our + // transcription dropped it; 1959.) assert!( - !s.eval::("return WhoFrameAddFriendButton:IsEnabled() ~= 0") + s.eval::("return WhoFrameAddFriendButton:IsEnabled() ~= 0") .unwrap(), - "a fresh answer drops the old selection" + "a fresh answer keeps the selection" ); } @@ -527,57 +503,6 @@ fn the_who_edit_box_sends_its_filter() { ); } -/// The slash bodies are the reference's: a NAMED `/friends` befriends, a bare one refreshes; a -/// bare `/who` opens the panel and fills the edit box with the default filter. -#[test] -fn the_slash_bodies_match_the_reference() { - let _data = benilla_formats::wow_data_or_skip!(); - let mut s = setup(); - // The WhoFrame's OnLoad routes results to chat until it is shown; drain that so each - // assertion below sees only what its own call queued. - let _ = s.take_social_requests(); - - s.run("BenillaSlashFriends(\"Onerogue\")").unwrap(); - assert_eq!( - s.take_social_requests(), - vec![SocialRequest::AddFriend("Onerogue".to_string())] - ); - s.run("BenillaSlashFriends(\"\")").unwrap(); - assert_eq!( - s.take_social_requests(), - vec![SocialRequest::RefreshFriends] - ); - - // `/ignore ` toggles rather than adds — the ref's AddOrDelIgnore. - s.run("BenillaSlashIgnore(\"Spammer\")").unwrap(); - assert_eq!( - s.take_social_requests(), - vec![SocialRequest::ToggleIgnore("Spammer".to_string())] - ); - - // A bare `/who` opens the Who panel and sends the default filter, which it also shows. - s.run("BenillaSlashWho(\"\")").unwrap(); - assert!(s.eval::("return WhoFrame:IsVisible()").unwrap()); - let requests = s.take_social_requests(); - let sent = requests - .iter() - .find_map(|r| match r { - SocialRequest::Who(filter) => Some(filter.clone()), - _ => None, - }) - .expect("a bare /who still sends a query"); - assert!( - sent.starts_with("z-\""), - "the default filter is zone-scoped: {sent}" - ); - assert_eq!( - s.eval::("return WhoFrameEditBox:GetText()") - .unwrap(), - sent, - "and the edit box shows what was sent" - ); -} - /// The Add Friend button with no friendly target opens the name-entry dialog — the first /// customer of the popup engine's `hasEditBox` capability. Accepting sends what was typed. #[test] @@ -604,7 +529,9 @@ fn add_friend_without_a_target_opens_the_name_dialog() { let _ = s.take_social_requests(); s.run("StaticPopup1EditBox:SetText(\"Onerogue\")").unwrap(); - s.run("StaticPopup_OnClick(StaticPopup1, 1)").unwrap(); + // Through the button: the stock dialog's OnAccept reads `this:GetParent()`, which only a + // real click seats. + s.run("StaticPopup1Button1:Click()").unwrap(); assert_eq!( s.take_social_requests(), vec![SocialRequest::AddFriend("Onerogue".to_string())] @@ -684,7 +611,14 @@ fn right_clicking_a_who_row_opens_the_friend_menu() { Some(1), "the menu has a Whisper row" ); - assert_eq!(s.take_tell_requests(), vec!["Tigole".to_string()]); + // The stock WHISPER row is `ChatFrame_SendTell(name)`: the chat box opens on the tell, its + // pending text applied by the box's next OnUpdate (1959). + s.tick(0.05); + assert_eq!( + s.eval::<(String, String)>("return ChatFrameEditBox.chatType, ChatFrameEditBox.tellTarget") + .unwrap(), + ("WHISPER".to_string(), "Tigole".to_string()) + ); } /// An OFFLINE friend's right-click opens nothing — there is no verb to offer them, which is the @@ -742,104 +676,3 @@ fn selecting_a_row_reads_back_in_the_same_tick() { .take_social_requests() .contains(&SocialRequest::SelectFriend(2))); } - -/// **The window's geometry, diffed against the reference FrameXML itself.** -/// -/// Every number in `FriendsFrame.xml` is a transcription of one in the reference's own file, and a -/// wrong one is invisible to every other test here: the frame loads, the clicks work, only the -/// *look* is wrong — so it surfaces as a screenshot from the director, one tab at a time. Two -/// rounds of that is what this replaces. Four of the five it caught on its first run were the -/// FriendsListFrame's values that had leaked into the IgnoreListFrame subtree, which is exactly -/// the failure mode of building a sibling pane by copy-adapting instead of transcribing. -/// -/// It scrapes both files for `` pairs per named element and compares the elements -/// that exist in both (ours carry a `Benilla` prefix). Deliberately narrow — it does not try to -/// understand the XML, only to notice that a number moved. Known-benign differences are listed -/// explicitly rather than filtered by a pattern, so a NEW difference can never hide inside an -/// exemption. Skips without the extracted reference. -#[test] -fn the_window_geometry_matches_the_reference_framexml() { - let _data = benilla_formats::wow_data_or_skip!(); - let Some(reference) = super::framexml_diff::reference("FriendsFrame.xml") else { - eprintln!("skipping: no extracted FrameXML"); - return; - }; - - // Differences that are ours on purpose. Each is a *deliberate* deviation with a reason, not a - // tolerance: the list is short and every entry names why. - const EXPECTED: &[&str] = &[ - // The ref omits an all-zero ; we write none at all. Same anchor, fewer bytes. - "FriendsFrameFriendButton2", - "FriendsFrameFriendButton3", - "FriendsFrameFriendButton4", - "FriendsFrameFriendButton5", - "FriendsFrameFriendButton6", - "FriendsFrameFriendButton7", - "FriendsFrameFriendButton8", - "FriendsFrameFriendButton9", - "FriendsFrameFriendButton10", - "FriendsFrameIgnoreButton2", - "FriendsFrameIgnoreButton3", - "FriendsFrameIgnoreButton4", - "FriendsFrameIgnoreButton5", - "FriendsFrameIgnoreButton6", - "FriendsFrameIgnoreButton7", - "FriendsFrameIgnoreButton8", - "FriendsFrameIgnoreButton9", - "FriendsFrameIgnoreButton10", - "FriendsFrameIgnoreButton11", - "FriendsFrameIgnoreButton12", - "FriendsFrameIgnoreButton13", - "FriendsFrameIgnoreButton14", - "FriendsFrameIgnoreButton15", - "FriendsFrameIgnoreButton16", - "FriendsFrameIgnoreButton17", - "FriendsFrameIgnoreButton18", - "FriendsFrameIgnoreButton19", - "FriendsFrameIgnoreButton20", - "WhoFrameButton2", - "WhoFrameButton3", - "WhoFrameButton4", - "WhoFrameButton5", - "WhoFrameButton6", - "WhoFrameButton7", - "WhoFrameButton8", - "WhoFrameButton9", - "WhoFrameButton10", - "WhoFrameButton11", - "WhoFrameButton12", - "WhoFrameButton13", - "WhoFrameButton14", - "WhoFrameButton15", - "WhoFrameButton16", - "WhoFrameButton17", - "FriendsFrameToggleTab2", - "IgnoreFrameToggleTab2", - "FriendsFrameTopLeft", - "FriendsFrameTopRight", - "FriendsFrameBottomLeft", - "FriendsFrameBottomRight", - "WhoFrameAddFriendButton", - "WhoFrameWhoButton", - // The ref writes an all-zero on these two row columns; we write none. - "FriendsFrameButtonTemplate/Info", - "FriendsFrameWhoButtonTemplate/Variable", - // Our column header carries the highlight's two anchors inline (the ref's sit in a - // separate block the scrape attributes to the same element). - "WhoFrameColumnHeaderTemplate/Right", - // The three faux-scroll frames: the ref decorates each with two UI-Character-ScrollBar - // trough textures. benilla's FauxScrollFrameTemplate draws its own complete bar - // (decisions 0247/0250/0251), so the ref's loose art would double it. - "FriendsFrameFriendsScrollFrame", - "FriendsFrameIgnoreScrollFrame", - "WhoListScrollFrame", - // …and the guild pane's, the fourth of the same kind (decision 1257). - "GuildListScrollFrame", - // The window's own close button predates UIPanelTemplates.xml shipping the reference's - // `UIPanelCloseButton`, so it states inline the 32x32 that template confers. The guild - // pane's two close buttons inherit it and therefore need no entry here. - "FriendsFrameCloseButton", - ]; - - super::framexml_diff::assert_geometry_matches("FriendsFrame.xml", &reference, EXPECTED, 210); -} diff --git a/crates/benilla-app/src/ui_script/game_menu_tests.rs b/crates/benilla-app/src/ui_script/game_menu_tests.rs index f669714f..c255a0ac 100644 --- a/crates/benilla-app/src/ui_script/game_menu_tests.rs +++ b/crates/benilla-app/src/ui_script/game_menu_tests.rs @@ -45,6 +45,10 @@ fn harness_with(extra: &[&str]) -> UiScript { "UiPanels.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", ] .into_iter() .chain(extra.iter().copied()) @@ -270,7 +274,9 @@ fn the_open_menu_takes_the_screen_and_refuses_every_other_panel() { // The loot window is the reference's own since 1751 — `test_ui::LOOT_UI` carries // what it needs and why, and PartyFrame's `MAX_PARTY_MEMBERS` is needed at LOAD. "Interface\\FrameXML\\UIDropDownMenu.xml", - "UnitPopup.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", // `TEXT`, which UnitPopup.lua reads at file scope + "Interface\\FrameXML\\UnitPopup.xml", "Interface\\FrameXML\\TextStatusBar.lua", "Interface\\FrameXML\\TextStatusBar.xml", "Interface\\FrameXML\\UnitFrame.xml", @@ -394,7 +400,7 @@ fn player_camping_opens_a_counting_dialog_whose_early_close_cancels() { assert_eq!( s.eval::("return StaticPopup1Text:GetText()") .unwrap(), - "20 seconds until logout", + "20 Seconds until logout", "the countdown text is the engine's, from the server's 20 s clock" ); // Cancel is the only button (the ref's CAMP_NOW is commented out in 1.12). @@ -457,7 +463,7 @@ fn player_quiting_offers_exit_now_and_logout_cancel_closes_it() { assert_eq!( s.eval::("return StaticPopup1Text:GetText()") .unwrap(), - "20 seconds until exit" + "20 Seconds until exit" ); assert_eq!( s.eval::("return StaticPopup1Button1:GetText()") @@ -618,7 +624,9 @@ fn nothing_opens_behind_the_world_map_and_escape_closes_it_first() { "Interface\\FrameXML\\MerchantFrame.xml", // The loot window is the reference's own since 1751 — see `test_ui::LOOT_UI` for what // each of these buys; `PartyFrame`'s MAX_PARTY_MEMBERS is needed at LOAD time. - "UnitPopup.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", // `TEXT`, which UnitPopup.lua reads at file scope + "Interface\\FrameXML\\UnitPopup.xml", "Interface\\FrameXML\\TextStatusBar.lua", "Interface\\FrameXML\\TextStatusBar.xml", "Interface\\FrameXML\\UnitFrame.xml", diff --git a/crates/benilla-app/src/ui_script/group_loot_tests.rs b/crates/benilla-app/src/ui_script/group_loot_tests.rs index f7e62cea..85f78504 100644 --- a/crates/benilla-app/src/ui_script/group_loot_tests.rs +++ b/crates/benilla-app/src/ui_script/group_loot_tests.rs @@ -78,6 +78,9 @@ fn setup() -> UiScript { load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); load_xml(&s, "MoneyFrame.xml"); load_xml(&s, "UiPanels.xml"); + 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, "Cooldown.xml"); load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml"); @@ -552,6 +555,10 @@ fn managed_positions_engage_for_the_bare_frame_name() { load_xml(&s, "UiPanels.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"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); // `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, "GameTooltip.xml"); @@ -618,6 +625,8 @@ fn ctrl_and_shift_on_the_roll_icon_preview_and_post_its_link() { load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua"); load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml"); load_xml(&s, "UiPanels.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); load_xml(&s, "Interface\\FrameXML\\FloatingChatFrame.xml"); s.set_loot_rolls(rolls()); diff --git a/crates/benilla-app/src/ui_script/guild_tests.rs b/crates/benilla-app/src/ui_script/guild_tests.rs index e9f4e52d..ed6c74e4 100644 --- a/crates/benilla-app/src/ui_script/guild_tests.rs +++ b/crates/benilla-app/src/ui_script/guild_tests.rs @@ -203,39 +203,9 @@ fn setup() -> UiScript { ..UnitState::default() }), ); - load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); - load_xml(&s, "MoneyFrame.xml"); - load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); - load_xml(&s, "UiPanels.xml"); - load_xml(&s, "GameTooltip.xml"); - load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); - load_xml(&s, "UIParent.xml"); - load_xml(&s, "UnitPopup.xml"); - load_xml(&s, "ScrollTemplates.xml"); - load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); - load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); - load_xml(&s, "FriendsFrame.xml"); - // The social window's fourth tab lives in its own file, and it is part of THIS window's - // manifest slice now: `BENILLA_FRIENDS_SUBFRAMES` names "RaidFrame", and both - // `FriendsFrame_ShowSubFrame` and `FriendsFrame_OnHide` resolve every name in that list - // through `getglobal` and call `:Hide()` on it. The reference's list names it too and never - // guards, because there RaidFrame.xml is FrameXML and always loaded — so the guard belongs in - // the harness's load order, not in shipped Lua defending against a state the client cannot be - // in (decision 1549). - // Stock `RaidFrame_OnLoad` reconciles the party frames the moment it loads - // (`RaidOptionsFrame_UpdatePartyFrames` -> `HidePartyFrame`/`ShowPartyFrame`), so this pane - // drags in the whole unit-frame cluster — and it must be the REAL frames: loading only - // `PartyFrame.lua` for the two names gets "attempt to index a nil value" the first time one - // iterates. The manifest already seats these far above RaidFrame (1874). - load_xml(&s, r"Interface\FrameXML\TextStatusBar.lua"); - load_xml(&s, r"Interface\FrameXML\TextStatusBar.xml"); - load_xml(&s, r"Interface\FrameXML\BuffFrame.xml"); - load_xml(&s, r"Interface\FrameXML\UnitFrame.xml"); - load_xml(&s, r"Interface\FrameXML\CombatFeedback.xml"); - load_xml(&s, r"Interface\FrameXML\PartyFrame.xml"); - load_xml(&s, r"Interface\FrameXML\RaidFrame.xml"); - load_xml(&s, r"Interface\AddOns\Blizzard_RaidUI\Blizzard_RaidUI.xml"); + for f in super::test_ui::SOCIAL_UI { + load_xml(&s, f); + } s } @@ -1436,11 +1406,13 @@ fn every_list_in_the_window_takes_the_mouse_wheel() { let _data = benilla_formats::wow_data_or_skip!(); let s = setup(); open(&s); + // The reference's wheel lives on each list's FauxScrollFrameTemplate scroll frame, not on + // the pane (1959). for frame in [ - "FriendsListFrame", - "IgnoreListFrame", - "WhoFrame", - "GuildFrame", + "FriendsFrameFriendsScrollFrame", + "FriendsFrameIgnoreScrollFrame", + "WhoListScrollFrame", + "GuildListScrollFrame", ] { assert!( s.eval::(&format!( diff --git a/crates/benilla-app/src/ui_script/help_frame_tests.rs b/crates/benilla-app/src/ui_script/help_frame_tests.rs index 9c591cc2..f44bacb8 100644 --- a/crates/benilla-app/src/ui_script/help_frame_tests.rs +++ b/crates/benilla-app/src/ui_script/help_frame_tests.rs @@ -32,6 +32,8 @@ fn setup() -> UiScript { "MoneyFrame.xml", "Interface\\FrameXML\\GlobalStrings.lua", "UiPanels.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", // The stock file's `HelpFrame_OnShow` calls `UpdateMicroButtons()` before // `GetGMStatus()`, so without the micro row the OnShow raises and the status ask never // happens. Ours never called it. MicroMenu needs the bar's button kit under it. diff --git a/crates/benilla-app/src/ui_script/honor_frame_tests.rs b/crates/benilla-app/src/ui_script/honor_frame_tests.rs index 914cb7ed..897e16f8 100644 --- a/crates/benilla-app/src/ui_script/honor_frame_tests.rs +++ b/crates/benilla-app/src/ui_script/honor_frame_tests.rs @@ -373,6 +373,9 @@ fn shown_inspect_honor_page() -> UiScript { "UiPanels.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", // `InspectUnit`'s home since 1832. "UIParent.xml", "GameTooltip.xml", diff --git a/crates/benilla-app/src/ui_script/inspect_tests.rs b/crates/benilla-app/src/ui_script/inspect_tests.rs index 67a3ecaa..b66d0e5d 100644 --- a/crates/benilla-app/src/ui_script/inspect_tests.rs +++ b/crates/benilla-app/src/ui_script/inspect_tests.rs @@ -121,6 +121,8 @@ fn armed() -> UiScript { load_xml(&s, "UiPanels.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"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); // `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"); @@ -168,6 +170,8 @@ fn shipped_inspect_frame_loads_clean() { load_xml(&s, "UiPanels.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"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); // `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"); diff --git a/crates/benilla-app/src/ui_script/instance_tests.rs b/crates/benilla-app/src/ui_script/instance_tests.rs index 249ca62c..232bcf41 100644 --- a/crates/benilla-app/src/ui_script/instance_tests.rs +++ b/crates/benilla-app/src/ui_script/instance_tests.rs @@ -93,9 +93,13 @@ fn the_self_menu_row_gates_on_the_binding_and_confirms_before_sending() { "UiPanels.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "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\\UIDropDownMenu.xml", - "UnitPopup.xml", + "Interface\\FrameXML\\UnitPopup.xml", "Interface\\FrameXML\\TextStatusBar.lua", "Interface\\FrameXML\\TextStatusBar.xml", "Interface\\FrameXML\\BuffFrame.xml", diff --git a/crates/benilla-app/src/ui_script/keybindings_tests.rs b/crates/benilla-app/src/ui_script/keybindings_tests.rs index fcf08487..b22a2915 100644 --- a/crates/benilla-app/src/ui_script/keybindings_tests.rs +++ b/crates/benilla-app/src/ui_script/keybindings_tests.rs @@ -16,10 +16,11 @@ //! Keybindings redirect head; and the action bar's abbreviation law (`GetBindingText` //! transcribed in UIParent.xml) reads `s-2`, the ref's own Lua. //! -//! Labels here are the RAW tokens (`BINDING_HEADER_MOVEMENT`, `BUTTON3`): the harness loads no -//! GlobalStrings, exercising the page's `getglobal(...) or raw` fallback — the app's VM -//! executes the real 1.12 GlobalStrings.lua at boot, which turns them into "Movement Keys" / -//! "Middle Mouse". One test seeds a handful of the real strings to pin that path too. +//! Labels are read the way the page reads them — `KeyBindings_String(token, raw)`, the +//! GlobalStrings global with the raw token as fallback ([`label`]). The harness carries the +//! real 1.12 GlobalStrings.lua since the dialog engine became the reference's file (1960; the +//! stock StaticPopup.lua reads the strings at load), so a label here is the app's own +//! "Movement Keys" / "Jump", never the raw token the strings-less harness used to show. use benilla_ui::script::keybind::{KeybindCommand, KeybindRequest}; use benilla_ui::script::{QuadContent, UiScript}; @@ -48,6 +49,10 @@ pub(crate) fn harness() -> UiScript { "UiPanels.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\StaticPopup.xml", "GameTooltip.xml", "Interface\\FrameXML\\UIDropDownMenu.xml", "ScrollTemplates.xml", @@ -74,6 +79,12 @@ pub(crate) fn harness() -> UiScript { s } +/// A label as the page resolves it: the GlobalStrings global named `token`, else `raw`. +pub(crate) fn label(s: &UiScript, token: &str, raw: &str) -> String { + s.eval::(&format!(r#"return KeyBindings_String("{token}", "{raw}")"#)) + .unwrap() +} + /// Open the options window on the Keybindings page. pub(crate) fn on_page(s: &mut UiScript) { s.run(r#"ShowUIPanel(OptionsFrame); OptionsFrame_SelectCategory("Keybindings")"#) @@ -115,7 +126,7 @@ fn the_page_is_an_options_category_with_the_collapsed_honest_tree() { let text = s .eval::(&format!("return {ROW}{}HeaderText:GetText()", i + 1)) .unwrap(); - assert_eq!(&text, token, "section {} (raw-token fallback)", i + 1); + assert_eq!(text, label(&s, token, token), "section {}", i + 1); } assert!( !s.eval::(&format!("return {ROW}{}:IsVisible()", expected.len() + 1)) @@ -129,14 +140,14 @@ fn the_page_is_an_options_category_with_the_collapsed_honest_tree() { assert_eq!( s.eval::(&format!("return {ROW}2Description:GetText()")) .unwrap(), - "MOVEANDSTEER" + label(&s, "BINDING_NAME_MOVEANDSTEER", "MOVEANDSTEER") ); assert_eq!( s.eval::(&format!("return {ROW}2Key1ButtonText:GetText()")) .unwrap(), - "BUTTON3" + label(&s, "KEY_BUTTON3", "BUTTON3") ); - // With the real strings present (the app's GlobalStrings), the same rows read 1.12 text. + // The same rows with the strings pinned by hand, so the expectation is literal 1.12 text. s.run( r#"BINDING_HEADER_MOVEMENT = "Movement Keys" BINDING_NAME_MOVEANDSTEER = "Move and Steer" @@ -164,7 +175,7 @@ fn the_page_is_an_options_category_with_the_collapsed_honest_tree() { assert_eq!( s.eval::(&format!("return {ROW}2HeaderText:GetText()")) .unwrap(), - "BINDING_HEADER_CHAT" + label(&s, "BINDING_HEADER_CHAT", "BINDING_HEADER_CHAT") ); // Leaving the page hides its body and the Unbind button. s.run(r#"OptionsFrame_SelectCategory("Controls")"#).unwrap(); @@ -228,10 +239,11 @@ fn the_capture_flow_binds_steals_and_refuses_like_112() { s.eval::(r#"return GetBindingAction("T")"#).unwrap(), "MOVEFORWARD" ); + let victim = label(&s, "BINDING_NAME_ATTACKTARGET", "ATTACKTARGET"); assert!( s.eval::("return OptionsFrameContainerBodyKeybindingsOutput:GetText()") .unwrap() - .contains("ATTACKTARGET"), + .contains(&victim), "the newly-bare victim is named" ); // **The wheel binds like any other key** (B265, decision 1295) — including onto MOVEFORWARD, @@ -250,7 +262,10 @@ fn the_capture_flow_binds_steals_and_refuses_like_112() { assert_eq!( s.eval::("return OptionsFrameContainerBodyKeybindingsOutput:GetText()") .unwrap(), - "|cffff0000CAMERAZOOMIN Function is Now Unbound!|r" + format!( + "|cffff0000{} Function is Now Unbound!|r", + label(&s, "BINDING_NAME_CAMERAZOOMIN", "CAMERAZOOMIN") + ) ); // The refusal that IS there is the key-string validator, and this is the page arm that // reports it: a chord the engine will not take restores the slot's old key and says why @@ -293,7 +308,7 @@ fn unbind_reset_and_the_live_commit_replace_okay_cancel() { assert_eq!( s.eval::(&format!("return {ROW}9Description:GetText()")) .unwrap(), - "JUMP" + label(&s, "BINDING_NAME_JUMP", "JUMP") ); s.run(&format!("{ROW}9Key1Button:Click()")).unwrap(); s.run("OptionsFrameContainerUnbind:Click()").unwrap(); @@ -593,11 +608,12 @@ fn the_wheel_bubbles_from_the_rows_and_the_bar_rides_the_gutter() { s.resolve(); // A spin over a row NAME — the dead zone when the handler lived on the sibling. + let steer = label(&s, "BINDING_NAME_MOVEANDSTEER", "MOVEANDSTEER"); let quads = s.extract(); let (wx, wy) = quads .iter() .find_map(|q| match &q.content { - QuadContent::Text { text: Some(t), .. } if t == "MOVEANDSTEER" => q + QuadContent::Text { text: Some(t), .. } if *t == steer => q .rect .map(|r| ((r.left + r.right) * 0.5, (r.bottom + r.top) * 0.5)), _ => None, @@ -706,7 +722,10 @@ fn the_pet_lane_is_registered_under_the_action_bar_header() { ); // Page side: search finds it and paints a live row with the capsule filled. - s.run(r#"OptionsFrameSearchBox:SetText("bonusactionbutton1")"#) + // The search tags are the DISPLAY names uppercased (era AddSearchTags), so the query is the + // label the app's strings give the row, not its token. + let query = label(&s, "BINDING_NAME_BONUSACTIONBUTTON1", "BONUSACTIONBUTTON1").to_lowercase(); + s.run(&format!(r#"OptionsFrameSearchBox:SetText("{query}")"#)) .unwrap(); s.tick(0.0); // the deferred OnTextChanged drains here (decision 1831) assert!(s @@ -715,8 +734,8 @@ fn the_pet_lane_is_registered_under_the_action_bar_header() { assert_eq!( s.eval::("return OptionsFrameContainerBodyKeybindSearch1Description:GetText()") .unwrap(), - "BONUSACTIONBUTTON1", - "the raw-token fallback — the app's GlobalStrings read 'Secondary Action Button 1'" + label(&s, "BINDING_NAME_BONUSACTIONBUTTON1", "BONUSACTIONBUTTON1"), + "the row wears the app's own string" ); assert_eq!( s.eval::("return OptionsFrameContainerBodyKeybindSearch1Key1ButtonText:GetText()") diff --git a/crates/benilla-app/src/ui_script/loot_tests.rs b/crates/benilla-app/src/ui_script/loot_tests.rs index 3ebc088a..ccffe73f 100644 --- a/crates/benilla-app/src/ui_script/loot_tests.rs +++ b/crates/benilla-app/src/ui_script/loot_tests.rs @@ -620,6 +620,8 @@ fn ctrl_and_shift_on_a_loot_row_preview_and_post_without_looting() { "Interface\\FrameXML\\UIPanelTemplates.lua", "Interface\\FrameXML\\UIPanelTemplates.xml", "UiPanels.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", "Interface\\FrameXML\\FloatingChatFrame.xml", ] { load_xml(&s, file); diff --git a/crates/benilla-app/src/ui_script/macro_tests.rs b/crates/benilla-app/src/ui_script/macro_tests.rs index 24513744..f03b0fbb 100644 --- a/crates/benilla-app/src/ui_script/macro_tests.rs +++ b/crates/benilla-app/src/ui_script/macro_tests.rs @@ -93,6 +93,8 @@ fn harness_with(player: &str) -> UiScript { "Interface\\FrameXML\\BasicControls.xml", // `TEXT` "MoneyFrame.xml", "UiPanels.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\StaticPopup.xml", // The chain's `PanelTemplates_SelectTab` reaches for `GameTooltip` unguarded. "GameTooltip.xml", "UIParent.xml", // `ShowMacroFrame` lives here now diff --git a/crates/benilla-app/src/ui_script/manifest.rs b/crates/benilla-app/src/ui_script/manifest.rs index 698cf121..768c0f9d 100644 --- a/crates/benilla-app/src/ui_script/manifest.rs +++ b/crates/benilla-app/src/ui_script/manifest.rs @@ -183,6 +183,26 @@ pub(crate) fn load_default_ui(script: &UiScript) -> Vec { /// chain document means `Interface\FrameXML\ContainerFrame.xml`'s `