From 3d10e7415b275edab7bdfa875cc93bf0544aecf5 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 25 Aug 2026 18:40:16 +0700 Subject: [PATCH] the target-of-target frame, and talents can be unlearned again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - the target-of-target frame and its option: 1.12's frame with its five display modes, fed one hop off the selected unit's own target field through the same read `/assist` runs, in four feeds (snapshot and transitions, the debuff buttons, the portrait booth, TargetUnit's click arm); ships off, as 1.12 does, on its own pair of saved globals - talents can be unlearned again: the trainer's `MSG_TALENT_WIPE_CONFIRM` had no handler since the wipe flow was deferred, so the gossip line closed the menu and did nothing — the question is an NPC session (walking away retracts it), the popup shows the cost on the StaticPopup money variant, Accept answers it behind the client's own purse check - Sprint's sparkles hang in the air behind you along the trail instead of collapsing into your torso: ride-vs-trail is the emitter's own file flag 0x10, not the host class — the finding wow-re returned eighteen days ago and nobody folded; the particle dump names the two flag bits it had been mis-reporting - `/sleep` takes both movement masks (the strictest posture), correcting the day's sit gate which let you lie down on a lake bottom while `/sit` was refused; a Lua-driven selection (TargetUnit on an attackable token) starts the swing exactly as a click does, through the one select path Co-Authored-By: Claude Fable 5 --- crates/benilla-app/assets/ui/OptionsFrame.xml | 75 ++- .../assets/ui/TalentWipeConfirm.xml | 82 +++ crates/benilla-app/assets/ui/UiPanels.xml | 48 +- crates/benilla-app/assets/ui/UnitFrames.xml | 513 ++++++++++++++++-- crates/benilla-app/assets/ui/benilla.toc | 24 +- crates/benilla-app/src/bindings.rs | 1 + crates/benilla-app/src/entities/attach/mod.rs | 3 - .../src/entities/equipment/spawn.rs | 3 - .../benilla-app/src/entities/spell_fx/mod.rs | 12 +- crates/benilla-app/src/entities/wmo_props.rs | 3 - crates/benilla-app/src/lib.rs | 7 + crates/benilla-app/src/net.rs | 6 + crates/benilla-app/src/net/apply.rs | 17 + crates/benilla-app/src/net/io.rs | 1 + crates/benilla-app/src/player/state.rs | 110 ++-- crates/benilla-app/src/portrait/booth.rs | 5 - crates/benilla-app/src/portrait/mod.rs | 54 +- crates/benilla-app/src/target/by_name.rs | 20 +- crates/benilla-app/src/target/click.rs | 43 +- crates/benilla-app/src/ui_aura.rs | 51 ++ crates/benilla-app/src/ui_chat/input/mod.rs | 32 +- crates/benilla-app/src/ui_script/bag_tests.rs | 20 + .../benilla-app/src/ui_script/bank_tests.rs | 1 + .../benilla-app/src/ui_script/binder_tests.rs | 1 + .../src/ui_script/character_tests.rs | 12 + .../benilla-app/src/ui_script/chat_tests.rs | 1 + .../src/ui_script/color_picker_tests.rs | 1 + .../src/ui_script/combo_frame_tests.rs | 1 + .../benilla-app/src/ui_script/death_tests.rs | 1 + .../src/ui_script/delete_item_tests.rs | 1 + .../src/ui_script/dressup_tests.rs | 3 + .../benilla-app/src/ui_script/duel_tests.rs | 1 + .../src/ui_script/durability_tests.rs | 2 + .../src/ui_script/enchant_confirm_tests.rs | 1 + .../benilla-app/src/ui_script/errors_tests.rs | 1 + .../benilla-app/src/ui_script/escape_tests.rs | 7 + .../benilla-app/src/ui_script/extract/mod.rs | 22 +- .../benilla-app/src/ui_script/fade_tests.rs | 2 + .../src/ui_script/friends_tests.rs | 1 + .../src/ui_script/game_menu_tests.rs | 15 +- .../src/ui_script/group_loot_tests.rs | 2 + .../benilla-app/src/ui_script/guild_tests.rs | 1 + .../src/ui_script/honor_frame_tests.rs | 2 + .../src/ui_script/inspect_tests.rs | 2 + .../src/ui_script/keybindings_tests.rs | 1 + .../benilla-app/src/ui_script/loot_tests.rs | 7 + .../benilla-app/src/ui_script/macro_tests.rs | 1 + .../src/ui_script/merchant_tests.rs | 13 + crates/benilla-app/src/ui_script/mod.rs | 6 + .../src/ui_script/options_tests.rs | 134 ++++- .../src/ui_script/panel_template_tests.rs | 1 + .../benilla-app/src/ui_script/panel_tests.rs | 13 + .../src/ui_script/pet_bar_tests.rs | 3 + .../src/ui_script/pet_paperdoll_tests.rs | 1 + .../benilla-app/src/ui_script/quest_tests.rs | 10 + .../src/ui_script/questlog_tests.rs | 15 + .../benilla-app/src/ui_script/raid_tests.rs | 1 + .../src/ui_script/reputation_frame_tests.rs | 1 + .../src/ui_script/screenshot_tests.rs | 1 + .../src/ui_script/skills_frame_tests.rs | 1 + .../src/ui_script/spellbook_tests.rs | 6 + .../src/ui_script/static_popup_tests.rs | 1 + .../src/ui_script/talent_wipe_tests.rs | 149 +++++ .../benilla-app/src/ui_script/taxi_tests.rs | 1 + .../src/ui_script/tooltip_compare_tests.rs | 1 + .../src/ui_script/tot_frame_tests.rs | 487 +++++++++++++++++ .../src/ui_script/trainer_tests.rs | 1 + .../src/ui_script/unit_frame_tests.rs | 2 + .../src/ui_script/unit_popup_tests.rs | 1 + .../src/ui_script/world_map_tests.rs | 1 + .../src/ui_script/zone_text_tests.rs | 1 + crates/benilla-app/src/ui_session.rs | 6 +- crates/benilla-app/src/ui_talent_wipe.rs | 249 +++++++++ crates/benilla-app/src/ui_unit.rs | 84 ++- crates/benilla-app/tests/auction_frame.rs | 2 +- crates/benilla-app/tests/character_stats.rs | 3 +- .../tests/create_frame_template.rs | 3 +- crates/benilla-app/tests/item_text_frame.rs | 3 +- crates/benilla-app/tests/mail_frame.rs | 2 +- crates/benilla-app/tests/talent_frame.rs | 3 +- crates/benilla-app/tests/trade_frame.rs | 3 +- crates/benilla-app/tests/tradeskill_frame.rs | 3 +- .../src/bin/benilla-extract/m2dump.rs | 14 +- crates/benilla-formats/src/particles.rs | 17 +- crates/benilla-protocol/src/events.rs | 5 + crates/benilla-protocol/src/events/decode.rs | 3 + crates/benilla-protocol/src/messages/mod.rs | 4 +- .../benilla-protocol/src/messages/opcode.rs | 14 + .../benilla-protocol/src/messages/packet.rs | 9 + crates/benilla-protocol/src/messages/parse.rs | 9 + .../src/messages/progression.rs | 68 +++ .../src/world/writer/progression.rs | 18 +- crates/benilla-ui/src/script/model.rs | 12 + crates/benilla-ui/src/script/talent.rs | 48 ++ crates/benilla-world/src/particles.rs | 31 +- crates/benilla-world/src/particles/sim.rs | 232 ++++++-- 96 files changed, 2639 insertions(+), 277 deletions(-) create mode 100644 crates/benilla-app/assets/ui/TalentWipeConfirm.xml create mode 100644 crates/benilla-app/src/ui_script/talent_wipe_tests.rs create mode 100644 crates/benilla-app/src/ui_script/tot_frame_tests.rs create mode 100644 crates/benilla-app/src/ui_talent_wipe.rs diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml index b3a98397..b5466d06 100644 --- a/crates/benilla-app/assets/ui/OptionsFrame.xml +++ b/crates/benilla-app/assets/ui/OptionsFrame.xml @@ -629,8 +629,13 @@ -- left to right: A gives Status Text (CheckButton2), B gives Show Cloak → Show Helm -- (31 → 20, sitting under the name toggles that live on our Nameplates page), C gives -- the 42 → 39 → 66 chain. Help (29) closes the page, as before. + -- The target-of-target pair (decision 1576) breaks that left-to-right reading, and + -- it has to: 1.12 keeps it in a box this window has no page for — Advanced Options' + -- "Raid & Party" — so there is no column order to preserve, only a neighbour to pick. + -- It sits under Status Text because that is the other unit-frame readout on the page. Interface = { - "RowStatusText", "RowShowCloak", "RowShowHelm", + "RowStatusText", "RowTargetOfTarget", "RowTargetOfTargetMode", + "RowShowCloak", "RowShowHelm", "RowInstantQuestText", "RowBuffDurations", "RowAutoQuestWatch", "RowNewbieTips", }, @@ -876,7 +881,11 @@ -- indexed 0/1/2, so a config or addon carrying that value means Never — and the -- nearest-stop fallback below, which assumes the values are ordinal, would show -- "Always" for it. A categorical dropdown says which strays are its own. - if e.value == v or e.alt == v then match = e end + -- `e.alt ~= nil` guards the nil-eats-everything case: a row whose store is not + -- there yet reads nil, and every entry without an `alt` would otherwise ANSWER to + -- it (nil == nil), leaving the capsule showing the last entry and the row wearing + -- its description. An entry answers to a second value only if it has one. + if e.value == v or (e.alt ~= nil and e.alt == v) then match = e end end if not match and tonumber(v) then local best @@ -1098,6 +1107,14 @@ end return end + if key == "Interface" then + -- 1.12's own rule for this pair (UIOptionsFrame_UpdateDependencies l.694-700): + -- the mode picker is dead while the switch is unticked. It is the only + -- cross-row dependency on this page. + OptionsRow_SetEnabled(OptionsFrameContainerBodyInterfaceRowTargetOfTargetMode, + SHOW_TARGET_OF_TARGET ~= "0") + return + end if key == "ActionBars" then -- 1.12's own rule for this pair, verbatim in effect (UIOptionsFrame.lua l.722-726): -- Show Right ActionBar 2 is dead while Show Right ActionBar is unticked, because @@ -2624,6 +2641,56 @@ self.cvarEvent = "STATUS_BAR_TEXT" + + + + + + + + OptionsGlobalRow_OnLoad(self, "SHOW_TARGET_OF_TARGET", + "Show Target of Target", "OPTION_TOOLTIP_SHOW_TARGET_OF_TARGET") + self.applyFunc = TargetofTarget_Update + + + + + + + + + + OptionsGlobalRow_OnLoad(self, "SHOW_TARGET_OF_TARGET_STATE", + "Show When", "OPTION_TOOLTIP_TARGETOFTARGET5", 1) + OptionsDropdownRow_Setup(self, { + { text = "Raid", value = "1", tip = "OPTION_TOOLTIP_TARGETOFTARGET1" }, + { text = "Party", value = "2", tip = "OPTION_TOOLTIP_TARGETOFTARGET2" }, + { text = "Solo", value = "3", tip = "OPTION_TOOLTIP_TARGETOFTARGET3" }, + { text = "Party or Raid", value = "4", tip = "OPTION_TOOLTIP_TARGETOFTARGET4" }, + { text = "Always", value = "5", tip = "OPTION_TOOLTIP_TARGETOFTARGET5" }, + }) + self.applyFunc = TargetofTarget_Update + + - - + + OptionsFuncRow_OnLoad(self, ShowingCloak, ShowCloak, "1", diff --git a/crates/benilla-app/assets/ui/TalentWipeConfirm.xml b/crates/benilla-app/assets/ui/TalentWipeConfirm.xml new file mode 100644 index 00000000..c906f822 --- /dev/null +++ b/crates/benilla-app/assets/ui/TalentWipeConfirm.xml @@ -0,0 +1,82 @@ + + + + + + + + + + self:RegisterEvent("CONFIRM_TALENT_WIPE") + + BenillaTalentWipeConfirmDriver_OnEvent() + + + diff --git a/crates/benilla-app/assets/ui/UiPanels.xml b/crates/benilla-app/assets/ui/UiPanels.xml index 536e2be1..f921154f 100644 --- a/crates/benilla-app/assets/ui/UiPanels.xml +++ b/crates/benilla-app/assets/ui/UiPanels.xml @@ -906,8 +906,13 @@ -- 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. -- · Accepted but v1-inert: exclusive, interruptCinematic (cinematics are acked - -- instantly — 0196), notClosableByLogout, preferredIndex, and the money variant. + -- instantly — 0196), 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. @@ -956,18 +961,25 @@ end end - -- Ref l.1428-1438, the no-money leg only (v1 has no money variant). + -- 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") - if StaticPopupDialogs[which] and StaticPopupDialogs[which].hasEditBox then + 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 @@ -1100,18 +1112,30 @@ 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. + -- 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, -8) + button1:SetPoint("TOPRIGHT", buttonAnchor, "BOTTOM", -6, buttonDrop) button2:SetPoint("LEFT", dialog:GetName() .. "Button1", "RIGHT", 13, 0) button2:SetText(info.button2) button2:Show() @@ -1932,6 +1956,20 @@ + + + + + + + SmallMoneyFrame_OnLoad(); MoneyFrame_SetType("STATIC") + + + @@ -1694,6 +1733,406 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TargetofTargetHealthCheck() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +