From d99ad8bc7d1c2fcf0c8e7a8e3aff6e55e95ae5ed Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 7 Aug 2026 16:02:21 +0700 Subject: [PATCH] the dressing room opens, and the pet gets its paper doll - ctrl-click an item to try it on: the dressing room dresses your double, and shift-click posts the item link to chat - the pet tab lands in the character window: paper doll stats, family and diet, the right-click menu, and dropping food on the pet feeds it - water-only spells refuse on dry land: aquatic form gates to swimming, client-side like the real one - the macro icon picker's white squares get their art back, and option rows explain themselves on hover Co-Authored-By: Claude Fable 5 --- crates/benilla-app/assets/ui/BagFrame.xml | 32 +- .../benilla-app/assets/ui/CharacterFrame.xml | 145 +- crates/benilla-app/assets/ui/DressUpFrame.xml | 308 +++ .../benilla-app/assets/ui/GroupLootFrame.xml | 60 +- crates/benilla-app/assets/ui/InspectFrame.xml | 26 +- crates/benilla-app/assets/ui/ItemRef.xml | 8 +- crates/benilla-app/assets/ui/LootFrame.xml | 65 +- .../benilla-app/assets/ui/MerchantFrame.xml | 48 +- crates/benilla-app/assets/ui/OptionsFrame.xml | 139 +- crates/benilla-app/assets/ui/PetActionBar.xml | 83 +- .../assets/ui/PetPaperDollFrame.xml | 735 +++++++ crates/benilla-app/assets/ui/QuestFrame.xml | 81 +- .../benilla-app/assets/ui/QuestLogFrame.xml | 73 +- crates/benilla-app/assets/ui/UIParent.xml | 61 + crates/benilla-app/assets/ui/UiPanels.xml | 5 + crates/benilla-app/assets/ui/UnitFrames.xml | 100 +- crates/benilla-app/assets/ui/UnitPopup.xml | 54 +- crates/benilla-app/src/assets/mod.rs | 110 +- crates/benilla-app/src/bindings/commands.rs | 22 +- crates/benilla-app/src/capture/fixtures.rs | 8 + crates/benilla-app/src/cursor.rs | 325 ++- crates/benilla-app/src/entities.rs | 33 +- crates/benilla-app/src/entities/attach/mod.rs | 5 +- .../attach/{glue_preview.rs => preview.rs} | 384 +++- crates/benilla-app/src/lib.rs | 11 + crates/benilla-app/src/names.rs | 20 + crates/benilla-app/src/net.rs | 14 + crates/benilla-app/src/net/apply.rs | 2 + crates/benilla-app/src/net/apply/names.rs | 2 + crates/benilla-app/src/net/io.rs | 2 + crates/benilla-app/src/portrait/dressup.rs | 326 +++ crates/benilla-app/src/portrait/mod.rs | 115 +- crates/benilla-app/src/target/mod.rs | 9 +- crates/benilla-app/src/ui_action/cast_fail.rs | 11 + crates/benilla-app/src/ui_action/cast_send.rs | 12 + crates/benilla-app/src/ui_action/drop_item.rs | 173 ++ crates/benilla-app/src/ui_action/mod.rs | 24 +- crates/benilla-app/src/ui_action/state.rs | 234 +- .../src/ui_action/targeting/cursor.rs | 12 +- .../src/ui_action/targeting/mod.rs | 15 + crates/benilla-app/src/ui_cast.rs | 17 + crates/benilla-app/src/ui_char.rs | 292 +-- crates/benilla-app/src/ui_dressup.rs | 350 +++ crates/benilla-app/src/ui_loot.rs | 29 +- crates/benilla-app/src/ui_loot_roll.rs | 7 + crates/benilla-app/src/ui_macro/tests.rs | 67 + crates/benilla-app/src/ui_merchant.rs | 12 + crates/benilla-app/src/ui_pet.rs | 1876 ----------------- crates/benilla-app/src/ui_pet/bar.rs | 338 +++ crates/benilla-app/src/ui_pet/drain.rs | 435 ++++ crates/benilla-app/src/ui_pet/menu.rs | 171 ++ crates/benilla-app/src/ui_pet/mod.rs | 193 ++ crates/benilla-app/src/ui_pet/tests.rs | 885 ++++++++ crates/benilla-app/src/ui_pet/unit.rs | 242 +++ crates/benilla-app/src/ui_pet_doll.rs | 231 ++ crates/benilla-app/src/ui_pet_stats.rs | 340 ++- crates/benilla-app/src/ui_quest.rs | 7 + crates/benilla-app/src/ui_quest_log.rs | 6 + .../src/ui_script/character_tests.rs | 8 +- .../src/ui_script/dressup_tests.rs | 378 ++++ .../benilla-app/src/ui_script/escape_tests.rs | 1 + .../benilla-app/src/ui_script/extract/mod.rs | 31 +- .../src/ui_script/game_menu_tests.rs | 1 + .../src/ui_script/group_loot_tests.rs | 91 +- .../benilla-app/src/ui_script/loot_tests.rs | 144 +- crates/benilla-app/src/ui_script/manifest.rs | 16 +- .../src/ui_script/merchant_tests.rs | 124 +- crates/benilla-app/src/ui_script/mod.rs | 6 + .../src/ui_script/options_tests.rs | 264 +++ .../benilla-app/src/ui_script/panel_tests.rs | 6 + .../src/ui_script/pet_bar_tests.rs | 18 +- .../src/ui_script/pet_frame_tests.rs | 72 + .../src/ui_script/pet_paperdoll_tests.rs | 451 ++++ .../benilla-app/src/ui_script/quest_tests.rs | 136 +- .../src/ui_script/questlog_tests.rs | 146 ++ .../src/ui_script/shipped_xml_tests.rs | 9 +- .../src/ui_script/unit_popup_tests.rs | 296 +++ crates/benilla-app/src/ui_unit.rs | 1 + .../benilla-formats/src/creature_families.rs | 376 ++++ crates/benilla-formats/src/dbc.rs | 38 - crates/benilla-formats/src/lib.rs | 7 +- crates/benilla-formats/src/macro_icons.rs | 89 + crates/benilla-protocol/src/events.rs | 4 + crates/benilla-protocol/src/events/decode.rs | 48 +- crates/benilla-protocol/src/messages/mod.rs | 13 +- .../benilla-protocol/src/messages/opcode.rs | 23 + .../benilla-protocol/src/messages/packet.rs | 11 +- crates/benilla-protocol/src/messages/parse.rs | 13 +- crates/benilla-protocol/src/messages/pet.rs | 42 + .../src/messages/update_object/fields/mod.rs | 17 + .../messages/update_object/fields/player.rs | 10 +- .../src/messages/update_object/fields/unit.rs | 16 + .../benilla-protocol/src/world/writer/pet.rs | 36 +- crates/benilla-protocol/tests/client.rs | 25 +- crates/benilla-ui/src/script/char_stats.rs | 421 +++- crates/benilla-ui/src/script/container.rs | 78 +- crates/benilla-ui/src/script/cursor.rs | 6 + crates/benilla-ui/src/script/dressup.rs | 113 + crates/benilla-ui/src/script/loot.rs | 58 +- crates/benilla-ui/src/script/loot_roll.rs | 45 +- crates/benilla-ui/src/script/merchant.rs | 53 + crates/benilla-ui/src/script/mod.rs | 7 +- crates/benilla-ui/src/script/model.rs | 66 +- crates/benilla-ui/src/script/pet.rs | 460 +++- crates/benilla-ui/src/script/quest.rs | 53 +- crates/benilla-ui/src/script/quest_log.rs | 53 +- crates/benilla-ui/src/script/spellbook.rs | 22 + crates/benilla-ui/src/script/unit/bindings.rs | 38 + crates/benilla-ui/src/script/unit/mod.rs | 6 + 109 files changed, 11304 insertions(+), 2532 deletions(-) create mode 100644 crates/benilla-app/assets/ui/DressUpFrame.xml create mode 100644 crates/benilla-app/assets/ui/PetPaperDollFrame.xml rename crates/benilla-app/src/entities/attach/{glue_preview.rs => preview.rs} (66%) create mode 100644 crates/benilla-app/src/portrait/dressup.rs create mode 100644 crates/benilla-app/src/ui_action/drop_item.rs create mode 100644 crates/benilla-app/src/ui_dressup.rs delete mode 100644 crates/benilla-app/src/ui_pet.rs create mode 100644 crates/benilla-app/src/ui_pet/bar.rs create mode 100644 crates/benilla-app/src/ui_pet/drain.rs create mode 100644 crates/benilla-app/src/ui_pet/menu.rs create mode 100644 crates/benilla-app/src/ui_pet/mod.rs create mode 100644 crates/benilla-app/src/ui_pet/tests.rs create mode 100644 crates/benilla-app/src/ui_pet/unit.rs create mode 100644 crates/benilla-app/src/ui_pet_doll.rs create mode 100644 crates/benilla-app/src/ui_script/dressup_tests.rs create mode 100644 crates/benilla-app/src/ui_script/pet_paperdoll_tests.rs create mode 100644 crates/benilla-formats/src/creature_families.rs create mode 100644 crates/benilla-formats/src/macro_icons.rs create mode 100644 crates/benilla-ui/src/script/dressup.rs diff --git a/crates/benilla-app/assets/ui/BagFrame.xml b/crates/benilla-app/assets/ui/BagFrame.xml index 11c8a056..e72a442b 100644 --- a/crates/benilla-app/assets/ui/BagFrame.xml +++ b/crates/benilla-app/assets/ui/BagFrame.xml @@ -100,11 +100,28 @@ local bagId = button:GetParent():GetID() if mouseButton == "LeftButton" then local info = C_Container.GetContainerItemInfo(bagId, button.slot) - -- The stack-split trigger — SHIFT + left-click on an unlocked stack of ≥2, the - -- reference fork verbatim (ContainerFrame.lua:567-577; ctrl's dress-up and the - -- chat-link insert are named deferrals). `ignoreModifiers` (the drag-trio calls, - -- both true) suppresses the fork, the reference's own drag/receive contract. + -- The modifier fork, the reference's own order (ContainerFrame.lua:563-579; + -- BankFrame.lua:141-152 for a bank slot, which is this same handler at container + -- -1). `ignoreModifiers` (the drag-trio calls, both true) suppresses it whole — + -- the reference's drag/receive contract. + -- + -- CTRL: preview the item on your own character in the dressing room (decision 1060). + if IsControlKeyDown() and not ignoreModifiers then + DressUpItemLink(C_Container.GetContainerItemLink(bagId, button.slot)) + return + end + -- SHIFT: post the item's link into the chat edit box if it's open, ELSE split the + -- stack. The ref's own `else` (l.567-577) — and note it gates on `IsShown()` here + -- where its siblings use `IsVisible()`; ours are the same thing (the edit box's only + -- parent is UIParent), so the ref's own word is quoted at each site. if IsShiftKeyDown() and not ignoreModifiers then + -- The visibility test is the REF'S fork, not the helper's: with chat open this + -- arm never splits, even in the one benilla state where nothing can be posted + -- (an unresolved template — `BenillaChatEdit_InsertLink`'s own note). + if ChatFrameEditBox and ChatFrameEditBox:IsShown() then + BenillaChatEdit_InsertLink(C_Container.GetContainerItemLink(bagId, button.slot)) + return + end if info and not info.isLocked and (info.stackCount or 1) >= 2 then button.SplitStack = function(btn, n) SplitContainerItem(bagId, btn.slot, n) @@ -175,12 +192,13 @@ -- mode → the sell cursor over this slot; else a readable item (a mail letter copy — -- right-click opens the reader) → the Inspect magnifier; anything else restores the -- base cursor. The OnUpdate re-enter keeps it current while hovering (the ref's own - -- loop). The ref's `IsControlKeyDown() and hasItem` magnifier arm (the Ctrl+click - -- dress-up preview cursor) is omitted with the dress-up feature itself. + -- loop). The ref's `IsControlKeyDown() and hasItem` magnifier arm (l.638) is the + -- dress-up preview's own cursor tell — it landed with the dressing room (decision + -- 1060); `info` is non-nil above, which is the ref's `hasItem`. if BenillaMerchantFrame and BenillaMerchantFrame:IsShown() and BenillaMerchantFrame.selectedTab == 1 and not InRepairMode() then ShowContainerSellCursor(bagId, button.slot) - elseif info.isReadable then + elseif info.isReadable or IsControlKeyDown() then ShowInspectCursor() else ResetCursor() diff --git a/crates/benilla-app/assets/ui/CharacterFrame.xml b/crates/benilla-app/assets/ui/CharacterFrame.xml index ba820389..224853a4 100644 --- a/crates/benilla-app/assets/ui/CharacterFrame.xml +++ b/crates/benilla-app/assets/ui/CharacterFrame.xml @@ -33,18 +33,25 @@ `AutoEquipCursorItem`/`CursorCanGoInSlot`/`IsInventoryItemLocked`, joining the paper doll onto the ONE cursor-payload space via its sentinel `EQUIPMENT_BAG` bag id (decision 0216 §1, extended by decision 0208's own phasing, §"Phasing"/"1a"/"1b", and 0216/0218's - cursor-arc design). Tabs 2-5 (Pet/Reputation/Skills/Honor) are still OMITTED, the 0138 + cursor-arc design). Tabs 2-5 (Pet/Reputation/Skills/Honor) were all OMITTED at 1b, the 0138 merchant-tab precedent ("tabs arrive with their pages") — `BENILLA_CHARACTERFRAME_SUBFRAMES` - only lists the one page that exists. + only lists the pages that exist. Skills arrived with decision 0437 phase 4 + (`SkillFrame.xml`) and **Pet with decision 1057** (`PetPaperDollFrame.xml`), which takes the + real client's own slot 2; Reputation and Honor are still out. Three things here are shared + WITH the pet page rather than owned by this one, and each is flagged at its definition: the + `(unit, prefix)` stat setters (including `_SetDefense`, which exists for the pet row alone), + the `BenillaPaperDollModel_*` pane quartet (driven through each pane's own `frame.setFacing`), + and the tab row itself, whose tab 2 the pet page raises and hides. Named deferrals/OMITs (each re-cited at its own site below): - Guild line (`CharacterGuildText`) and the PVP rank name (`UnitPVPName` — plain `UnitName` ships instead): both decision 0208's own named deferrals (guild needs `CMSG_GUILD_QUERY`; rank needs the honor arc). `CharacterTitleText` (also PVP-rank-driven) is dropped with it. - - Shift-click chat-link insert and ctrl-click `DressUpItemLink` (the dressing room): both - named deferrals this phase too — the left-click fork recognizes the modifiers and does - nothing, rather than silently falling through to a pickup a real shift/ctrl-click - would never have triggered. + - Shift-click chat-link insert (decision 1059) and ctrl-click `DressUpItemLink` (the + dressing room, decision 1060) both LANDED — the left-click fork below is now the + reference's own three arms (ctrl → preview, shift → post the link, else pick up). + They were named deferrals when this file was written; the note stays because the fork + below reads as a transcription of that history. - The ammo slot (id 0) LOADS ammo by entry (`CMSG_SET_AMMO`) — it does NOT swap into an equip slot (decision 0526; wow-re `cursor-dragdrop-slots.md`: the client's auto-equip sender forks ammo-class → SET_AMMO). So it can't share the 19 named slots' @@ -134,9 +141,13 @@ -- if one ever loads, wins over this fallback) ────────────────────────────────────────────── CHARACTER = CHARACTER or "Character" -- :481 SKILLS = SKILLS or "Skills" -- :3477 (decision 0437 phase 4, the Skills tab label) + PET = PET or "Pet" -- :3010 (the Pet tab label, decision 1057) PLAYER_LEVEL = PLAYER_LEVEL or "Level %d %s %s" -- :3075 ARMOR = ARMOR or "Armor" -- :62 ARMOR_COLON = ARMOR_COLON or "Armor:" -- :63 + DEFENSE_COLON = DEFENSE_COLON or "Defense:" -- :917 (the shared + -- _SetDefense's label; only + -- the pet page has the row) ARMOR_TOOLTIP = ARMOR_TOOLTIP or "Decreases the amount of damage taken from physical attacks. The amount of reduction is influenced by the level of the attacker.\nDamage reduction against a level %d attacker: %.1f%%" -- :65 ATTACK_POWER_COLON = ATTACK_POWER_COLON or "Power:" -- :71 @@ -231,9 +242,13 @@ -- The pages that exist so far (this file's header comment; the 0138 merchant-tab -- precedent — tabs arrive with their pages). Benilla-prefixed, unlike the ref's bare -- "PaperDollFrame"/"SkillFrame". 1:1 with the tab row below (index N is tab N's page) — - -- decision 0437 phase 4 adds "BenillaSkillFrame" as tab 2; Pet/Reputation/Honor (the - -- real client's tabs 2/3/5) are still OMITTED. - BENILLA_CHARACTERFRAME_SUBFRAMES = { "BenillaPaperDollFrame", "BenillaSkillFrame" } + -- decision 0437 phase 4 added the Skills page, decision 1057 the Pet one, which takes the + -- real client's own slot 2 and pushes Skills to 3. Reputation/Honor (the real client's + -- tabs 3/5) are still OMITTED, which is the whole reason our Skills tab renders 3rd where + -- the reference's is 4th. + BENILLA_CHARACTERFRAME_SUBFRAMES = { + "BenillaPaperDollFrame", "BenillaPetPaperDollFrame", "BenillaSkillFrame", + } -- ── The window: toggle / tab dispatch (ref CharacterFrame.lua:1-48) ───────────────────── -- Kept BARE (not Benilla-prefixed) — the addon-facing entry point, matching this codebase's @@ -252,6 +267,14 @@ function ToggleCharacter(tab) local subFrame = getglobal(tab) if not subFrame then return end + -- ref CharacterFrame.lua:4-6, the pet guard: asking for the pet page with no pet does + -- NOTHING — not even close an open window. The `not IsVisible()` half is what still + -- lets the page CLOSE itself when the pet vanishes underneath it (the pet page's own + -- OnEvent calls straight back in here to do exactly that). Hoisted below the getglobal + -- (the ref tests it above) purely so the visibility read has a frame to ask. + if tab == "BenillaPetPaperDollFrame" and not HasPetUI() and not subFrame:IsVisible() then + return + end local frame = getglobal("BenillaCharacterFrame") if frame:IsVisible() then if subFrame:IsVisible() then @@ -285,10 +308,12 @@ function BenillaCharacterFrame_OnLoad(frame) -- GetID()/SetID() DO exist (landed with slice 2's BagFrame, which relies on them for -- its own per-window bag id) — the STALE claim this comment used to make is corrected - -- here (0208 phase 1b touched this function anyway). 2 tabs ship as of decision 0437 - -- phase 4 (Character + Skills); the window opens on tab 1 by default, matching the - -- real client. - PanelTemplates_SetNumTabs(frame, 2) + -- here (0208 phase 1b touched this function anyway). 3 tabs ship as of decision 1057 + -- (Character + Pet + Skills); the window opens on tab 1 by default, matching the real + -- client. Tab 2 raises and hides itself with the pet — BenillaPetTab_Update, which the + -- pet page runs at its own OnLoad and on every pet transition — so the COUNT here is + -- the full row, not the currently-visible one, exactly as the ref's SetNumTabs(5) is. + PanelTemplates_SetNumTabs(frame, 3) PanelTemplates_SetTab(frame, 1) end @@ -517,6 +542,27 @@ frame.tooltipSubtext = format(ARMOR_TOOLTIP, playerLevel, armorReduction) end + -- ref PaperDollFrame_SetDefense (l.252-272). Shared like every setter around it, but the + -- CHARACTER sheet has no defense row — the real client's doesn't either, and this exists + -- solely for the pet page (decision 1057), which is why it arrives now and not in 0208. + -- `UnitDefense`'s second return is a single signed modifier, not the pos/neg pair the other + -- setters get, so the split into posBuff/negBuff happens here. + function BenillaPaperDollFrame_SetDefense(unit, prefix) + unit = unit or "player" + prefix = prefix or "BenillaCharacter" + local base, modifier = UnitDefense(unit) + local frame = getglobal(prefix .. "DefenseFrame") + local text = getglobal(prefix .. "DefenseFrameStatText") + local posBuff = 0 + local negBuff = 0 + if modifier > 0 then + posBuff = modifier + elseif modifier < 0 then + negBuff = modifier + end + BenillaPaperDollFormatStat(DEFENSE_COLON, base, posBuff, negBuff, frame, text) + end + -- ref PaperDollFrame_SetDamage (l.274-372). function BenillaPaperDollFrame_SetDamage(unit, prefix) unit = unit or "player" @@ -787,11 +833,17 @@ -- Bag/QuestLog all thread `self` explicitly rather than lean on the engine's `this` -- global, which DOES exist — `benilla-ui/src/script/event.rs` — but isn't this codebase's -- idiom). - function BenillaPaperDollStatTooltip(button, stat) + -- `unit` joins the signature for the pet page (decision 1057): the ref's own + -- PaperDollStatTooltip(unit, stat) always took it — its pet rows pass "pet" (ref + -- PetPaperDollFrame.xml:345) — and phase 1a simply had no second unit to pass, so it was + -- inlined as "player". Restored, defaulted, and third rather than first because `button` + -- leads by house convention (above). + function BenillaPaperDollStatTooltip(button, stat, unit) -- classFileName nil until the first player snapshot lands (this file's SetLevel comment -- on the same engine timing gap) — "" just misses every class lookup, falling through to - -- DEFAULT_*_TOOLTIP below exactly like an unrecognized class would. - local _, classFileName = UnitClass("player") + -- DEFAULT_*_TOOLTIP below exactly like an unrecognized class would. A pet has no class + -- either, so the pet rows land on the same DEFAULT_* fallback the ref gives them. + local _, classFileName = UnitClass(unit or "player") classFileName = classFileName or "" local classStatText = getglobal(strupper(classFileName) .. "_" .. stat .. "_TOOLTIP") if not classStatText then @@ -882,17 +934,23 @@ BenillaPaperDollSlot_Update(button) end - -- Left: shift/ctrl are named deferrals (chat-link insert / DressUpItemLink, header - -- comment) — recognized and done, not fallen through to a pickup a real modified click - -- would never trigger. Plain click: PickupInventoryItem (pick up / place / swap / cancel, + -- Left: the reference's modifier fork verbatim (ref l.647-660) — CTRL previews the worn + -- item in the dressing room (decision 1060), SHIFT posts its link into an open chat edit + -- box (decision 1059), and neither falls through to a pickup a real modified click would + -- never trigger. Plain click: PickupInventoryItem (pick up / place / swap / cancel, -- ref l.656). Right: UseInventoryItem (ref l.659). `ignoreModifiers` is the drag-route -- calls' own suppression, the bag slots' precedent. function BenillaPaperDollSlot_OnClick(button, mouseButton, ignoreModifiers) if mouseButton == "LeftButton" then if IsControlKeyDown() and not ignoreModifiers then - return -- DressUpItemLink: named deferral, no dressing room yet + DressUpItemLink(GetInventoryItemLink("player", button.invSlotId)) + return elseif IsShiftKeyDown() and not ignoreModifiers then - return -- chat-link insert: named deferral + -- Both of the ref's guards (l.652: only when the edit box is up) plus our own + -- nil-link one live in the shared helper (UIParent.xml). A shift-click with chat + -- closed does nothing here — a worn item has no stack to split, unlike a bag slot. + BenillaChatEdit_InsertLink(GetInventoryItemLink("player", button.invSlotId)) + return end PickupInventoryItem(button.invSlotId) GameTooltip:Hide() -- the item just left the slot for the cursor @@ -1047,9 +1105,16 @@ -- ── The model pane (ref UIParent.lua:1420-1462) ───────────────────────────────────────── -- This file's header comment: a booth-texture stand-in, not a live widget. `frame` here is -- BenillaCharacterModelFrame; `frame.facing` stands in for the ref's `model.rotation`. + -- These four are the ref's SHARED Model_* helpers (UIParent.lua), and they are shared here + -- too: the pet page's pane (decision 1057) drives the very same code. The one thing that + -- differs per pane is WHERE the yaw goes — the engine has no `Model:SetFacing` widget + -- method, so each pane has its own booth-yaw binding — so the pane names its own in + -- `frame.setFacing` at OnLoad and these read it. (The inspect window still carries its own + -- copy of this quartet, predating the generalization; folding it in is a follow-up, not + -- this slice's to churn.) function BenillaPaperDollModel_OnLoad(frame) frame.facing = 0.61 -- ref UIParent.lua:1422, the literal default facing - BenillaPaperDollModel_SetFacing(frame.facing) + frame.setFacing(frame.facing) end -- ref Model_RotateLeft/Right (UIParent.lua:1426-1442) — the ref's OWN left/right sign @@ -1057,13 +1122,13 @@ -- re-derived, just quoted. function BenillaPaperDollModel_RotateLeft(frame) frame.facing = frame.facing - 0.03 - BenillaPaperDollModel_SetFacing(frame.facing) + frame.setFacing(frame.facing) PlaySound("igInventoryRotateCharacter") end function BenillaPaperDollModel_RotateRight(frame) frame.facing = frame.facing + 0.03 - BenillaPaperDollModel_SetFacing(frame.facing) + frame.setFacing(frame.facing) PlaySound("igInventoryRotateCharacter") end @@ -1082,14 +1147,14 @@ if frame.facing < 0 then frame.facing = frame.facing + (2 * PI) end - BenillaPaperDollModel_SetFacing(frame.facing) + frame.setFacing(frame.facing) end if getglobal(frame:GetName() .. "RotateRightButton"):GetButtonState() == "PUSHED" then frame.facing = frame.facing - step if frame.facing > (2 * PI) then frame.facing = frame.facing - (2 * PI) end - BenillaPaperDollModel_SetFacing(frame.facing) + frame.setFacing(frame.facing) end end @@ -1270,22 +1335,35 @@ HideUIPanel(getglobal("BenillaCharacterFrame")) - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BenillaDressUpModel_OnLoad(self) + BenillaSetBoothTexture(getglobal(self:GetName() .. "Texture"), "dressup") + + BenillaDressUpModel_OnUpdate(self, arg1) + + + + + SetDressUpBackground() + + SetDressUpBackground() + SetPortraitTexture(BenillaDressUpFramePortrait, "player") + PlaySound("igCharacterInfoOpen") + + + + BenillaDressUpModel_Close() + PlaySound("igCharacterInfoClose") + + + + diff --git a/crates/benilla-app/assets/ui/GroupLootFrame.xml b/crates/benilla-app/assets/ui/GroupLootFrame.xml index f366194e..a1e0f576 100644 --- a/crates/benilla-app/assets/ui/GroupLootFrame.xml +++ b/crates/benilla-app/assets/ui/GroupLootFrame.xml @@ -44,20 +44,23 @@ `BenillaGroupLootFrame`s byte-identical instances of the same template (each registers only `CANCEL_LOOT_ROLL` on itself, ref l.487-489/493-495, verbatim). - NO DRESSING-ROOM / CHAT-LINK INSERT (CraftFrame.xml's/TradeSkillFrame.xml's own identical - deviation). The ref icon button's Ctrl-click `DressUpItemLink`/Shift-click - `ChatFrameEditBox:Insert` (ref l.353-360) and its `this.hasItem = 1` OnLoad flag (ref l.339, - read only by UIParent.lua's own generic Ctrl-click dressing-room handler) are dropped — no - dressing room exists; a real, now-inert vestige, not a benilla omission. Likewise the ref's - `CursorUpdate()`/`ResetCursor()`/`CursorOnUpdate()` hover-cursor calls (ref l.344,348,351) have - no engine counterpart and are omitted — LootFrame.xml's own row template - (`BenillaLootButtonTemplate`) already established this same omission for item-row hover. + THE DRESSING-ROOM / CHAT-LINK CLICK LANDED (decisions 1059/1060 — this note used to read "no + dressing room exists", which was true until the room did). The ref icon button's Ctrl-click + `DressUpItemLink`/Shift-click `ChatFrameEditBox:Insert` (ref l.353-361) are transcribed in + `BenillaGroupLootFrame_Icon_OnClick` below, over the `GetLootRollItemLink(rollID)` binding the + same arc added to `loot_roll.rs`. Still dropped: the ref's `this.hasItem = 1` OnLoad flag + (ref l.338-340) and its `CursorUpdate()`/`ResetCursor()`/`CursorOnUpdate()` hover-cursor calls + (ref l.344,348,351) — the flag exists only to feed those, and they are the Ctrl-hover + MAGNIFIER, a separate affordance (`ShowInspectCursor`, which the bags and the vendor rows do + wire) that this button has never had. LootFrame.xml's own row template + (`BenillaLootButtonTemplate`) carries the same hover-cursor omission. THE ITEM TOOLTIP uses `GameTooltip:SetItemById(itemId, name, quality)` (LootFrame.xml's own - `BenillaLootItem_OnEnter` idiom), not the reference's `SetLootRollItem`/`GetLootRollItemLink` - (neither exists here) — `itemId` is the benilla-extension 6th return of `GetLootRollItemInfo` + `BenillaLootItem_OnEnter` idiom), not the reference's `SetLootRollItem` (no such tooltip + method here) — `itemId` is the benilla-extension 6th return of `GetLootRollItemInfo` (see `loot_roll.rs`), always populated once a roll is known (unlike name/texture/quality, which - may be `nil` while the item template is still in flight). + may be `nil` while the item template is still in flight). `GetLootRollItemLink` DOES exist now + (decision 1059) but is the click's, not the hover's: the tooltip wants the id, not the markup. `toplevel="true"` (ref l.243) IS OMITTED — `SetToplevel` isn't in the v1 object model yet (`loader/geometry.rs`'s `apply_attrs`: a warn-once "gap", same as `movable`/`resizable`); other @@ -78,6 +81,7 @@ The Era API this drives (`benilla-ui/src/script/loot_roll.rs`, decision 0591): `GetLootRollItemInfo(rollID)` -> texture,name,count,quality,bindOnPickUp,itemID; + `GetLootRollItemLink(rollID)` -> the item's link (the icon click's two arms, decision 1059); `GetLootRollTimeLeft(rollID)` -> ms remaining; `RollOnLoot(rollID, rollType)` (0 Pass / 1 Need / 2 Greed). Events: `START_LOOT_ROLL(rollID, rollTime)` claims a frame; `CANCEL_LOOT_ROLL(rollID)` hides the one that owns it (`crates/benilla/src/ui_loot_roll.rs` fires both). @@ -248,6 +252,34 @@ function BenillaGroupLootFrame_Icon_OnLeave() GameTooltip:Hide() end + + -- The item icon's click (ref $parentIconFrame OnClick, LootFrame.xml l.353-361, transcribed + -- whole): CTRL previews the rolled item in the dressing room (decision 1060), SHIFT posts + -- its link into an open chat edit box (decision 1059) — "link me that" being most of what a + -- roll popup gets asked. Both read `GetLootRollItemLink(rollID)`, the binding this arc added + -- (loot_roll.rs); it answers nil while the item template is still in flight, and both arms + -- drop that nil — the ctrl one inside DressUpItemLink (DressUpFrame.lua l.10-16), the shift + -- one inside BenillaChatEdit_InsertLink. + -- + -- Nothing else happens on this button, exactly as in the reference: the vote lives on the + -- Pass/Need/Greed buttons and the icon is a preview/link affordance only. The ref's + -- `this.hasItem = 1` OnLoad flag (l.338-340) stays dropped — it exists solely for the + -- CursorUpdate/CursorOnUpdate ctrl-hover magnifier (l.344/351), which this button still does + -- not wire (see the header). No RegisterForClicks either, also as in the reference: a Button + -- is registered for LeftButtonUp by default (widget/kinds/mod.rs), which is the whole set + -- this handler wants. + function BenillaGroupLootFrame_Icon_OnClick(button) + local rollId = button:GetParent().rollID + if IsControlKeyDown() then + DressUpItemLink(GetLootRollItemLink(rollId)) -- ref l.354-355 + elseif IsShiftKeyDown() then + -- ref l.356-359, through the shared helper (UIParent.xml): it carries the ref's own + -- edit-box-visible guard AND our nil-link one, which this popup needs routinely — + -- the frame is up the instant START_LOOT_ROLL fires, well before the item template + -- answers, and until it does there is no link to post. + BenillaChatEdit_InsertLink(GetLootRollItemLink(rollId)) + end + end ]]> + @@ -49,7 +50,10 @@ return end if IsControlKeyDown() then - -- ref: DressUpItemLink(text) - no dressing room exists. + -- ref l.50: ctrl-clicking an item link in chat previews it on your own character + -- (decision 1060). `text` is the FULL escaped link, not the bare payload — + -- DressUpItemLink pulls the id out of it itself. + DressUpItemLink(text) return end if IsShiftKeyDown() then diff --git a/crates/benilla-app/assets/ui/LootFrame.xml b/crates/benilla-app/assets/ui/LootFrame.xml index 40610a8d..7b35750e 100644 --- a/crates/benilla-app/assets/ui/LootFrame.xml +++ b/crates/benilla-app/assets/ui/LootFrame.xml @@ -33,9 +33,10 @@ The Era loot API this drives (all app-fed via the loot seam, benilla-ui `script/loot.rs`): GetNumLootItems(), GetLootSlotInfo(slot) -> texture,item,quantity,quality, LootSlotIsItem/ - LootSlotIsCoin(slot), LootSlot(slot) (the row click's action), CloseLoot() (from OnHide). The - synthesized coin pile is row 1 when the loot carries gold; a row click sends the money or the - item's wire slot (the app maps it). + LootSlotIsCoin(slot), GetLootSlotLink(slot) (the row's item link, read by the click's + ctrl/shift arms — decision 1059), LootSlot(slot) (the row click's action), CloseLoot() (from + OnHide). The synthesized coin pile is row 1 when the loot carries gold; a row click sends the + money or the item's wire slot (the app maps it). Paging (LootFrame.lua l.27-31,70-73,105-118): 4 rows/page; > 4 items spends one slot on the pager, showing 3 rows + Up/Down. We keep the current page across content-change repaints @@ -70,12 +71,66 @@ button:RegisterForClicks("LeftButtonUp", "RightButtonUp") end + -- ref LootFrameItem_OnClick (LootFrame.lua l.147-162): CTRL previews the row's item in the + -- dressing room (decision 1060), SHIFT posts its link into an open chat edit box (decision + -- 1059) — both through GetLootSlotLink, the row-link binding this arc added (loot.rs). + -- + -- WHERE THE TAKE LIVES, and why the gate below reads three modifiers where FrameXML names + -- two (VERIFIED at the 1.12.1 bytes — wow-re `ui/scratch/loot-click-modifier-gate.md`, + -- decision 1067). The reference's Lua handler suppresses NOTHING, because the take is the C + -- `CLootButton`'s own click behaviour (`LootFrame.lua:94`'s `button:SetSlot(slot)` hands the + -- widget the slot). `CLootButton::OnClick 0x4c1820` runs the Lua `` FIRST and + -- unconditionally, then gates itself on `0x41f8f0(0/1/2)` — byte-identically the predicates + -- behind `IsShiftKeyDown`/`IsControlKeyDown`/**`IsAltKeyDown`** — and only then sends + -- (`0x4c2790` → `0x5e1ad0` → `push 0x108`, CMSG_AUTOSTORE_LOOT_ITEM). + -- + -- Three consequences, all of them this handler's shape: + -- · **ALT suppresses the take too**, though no FrameXML file mentions alt. Its gate is C. + -- · The gate is on ANY modifier being HELD, not on "the arm that handled it": a shift-click + -- with the chat box closed does nothing in Lua and still must not loot. + -- · The button code is never read (`0x4c1820` doesn't look at it), so a RIGHT-click loots + -- exactly like a left one — which is why this handler takes no `mouseButton`. + -- + -- benilla folds that C action into this Lua handler (`LootSlot(button.slot)`, this file's + -- model since 0084), so the gate has to be an early return here. Named divergence, not a + -- transcription slip: 1.12 exposes no plain "take slot N" binding at all — the real + -- `LootSlot 0x4c2e70` is the LOOT_BIND *confirmation continuation* (flag 1, gated on the + -- stashed slot at `[0x847cec]`; its only FrameXML caller is `StaticPopup.lua:606`). Moving + -- the take host-side onto the widget, and shrinking `LootSlot()` to that continuation, is + -- 1067's named follow-up. + -- + -- The reference's tail (StaticPopup_Hide("CONFIRM_LOOT_DISTRIBUTION") and the four + -- LootFrame.selected* fields, l.155-161) is the MASTER-LOOT dropdown's bookkeeping — + -- GroupLootDropDown reads exactly those four (l.164+). benilla ships no master loot + -- (benilla-protocol's loot module: CMSG_LOOT_MASTER_GIVE/SMSG_LOOT_MASTER_LIST out of + -- scope), so returning early skips nothing that exists. + -- + -- The coin row has no link (it is synthesized, with no item behind it — ui_loot.rs), so a + -- modified click on it finds nil: DressUpItemLink returns on a nil link (DressUpFrame.lua + -- l.10-16) and the insert is skipped. It does not fall through to looting the money either, + -- which is the same law the item rows follow. function BenillaLootItem_OnClick(button) -- The row remembers its resolved loot slot from the last Update; act on it (LootFrame.lua -- l.94-95 stores button.slot, the click calls LootSlot on it). - if button.slot then - LootSlot(button.slot) + if not button.slot then return end + if IsControlKeyDown() then + DressUpItemLink(GetLootSlotLink(button.slot)) -- ref l.148-149 + return + elseif IsShiftKeyDown() then + -- ref l.150-153, through the shared helper (UIParent.xml): it carries the ref's own + -- "only when the edit box is up" guard AND our nil-link one, which this row needs + -- more than most — the coin row never has a link, and an item row has none until + -- its template lands. A shift-click with chat closed does nothing here: a loot row + -- has no stack to split, unlike a bag slot. + BenillaChatEdit_InsertLink(GetLootSlotLink(button.slot)) + return + elseif IsAltKeyDown() then + -- The C gate's third modifier (`0x41f8f0(2)` @ `0x4c1841`). Nothing is bound to + -- alt in FrameXML, so this arm does nothing at all — its entire job is to NOT + -- loot, which is what the real client does and what we did not. + return end + LootSlot(button.slot) end -- Hover: the item name coloured by quality (loot rows carry it — GetLootSlotInfo's 4th diff --git a/crates/benilla-app/assets/ui/MerchantFrame.xml b/crates/benilla-app/assets/ui/MerchantFrame.xml index 455aa167..294b0fd5 100644 --- a/crates/benilla-app/assets/ui/MerchantFrame.xml +++ b/crates/benilla-app/assets/ui/MerchantFrame.xml @@ -20,7 +20,11 @@ repair pair, the page turners. We keep the real geometry of everything we DO ship. Clicks (decision 0081 v1): RIGHT-click a row → BuyMerchantItem. LEFT-click is PickupMerchantItem - in the real client (a drag-buy) — a no-op here until the cursor-item arc. + in the real client (a drag-buy) — a no-op here until the cursor-item arc. CTRL-left-click + previews the item in the dressing room and SHIFT-left-click posts its link to an open chat + edit box (the ref's own fork, MerchantFrame.lua l.301-306 — decisions 1059/1060, over the + GetMerchantItemLink feed); the shift-with-chat-closed stack-split half of that arm waits on a + GetMerchantItemMaxStack binding (named at the handler). UIPanel slot system (decision 0084 §2): carries NO hardcoded — ShowUIPanel/SetLeftFrame SetPoints it to the left slot (TOPLEFT UIParent, 0, -104) on open; every show goes through @@ -200,13 +204,47 @@ function BenillaMerchantItem_OnClick(button, mouseButton) -- Buyback tab: either click buys the slot back (ref MerchantItemButton_OnClick's else - -- arm, l.358-361). Merchant tab: RIGHT-click = BuyMerchantItem (the real right-click - -- branch); LEFT-click is PickupMerchantItem (a drag-buy onto the cursor) in the real - -- client — a no-op here until the cursor-item arc lands. + -- arm, l.358-361) — and, as there, with NO modifier branch of any kind: 1.12 ships no + -- GetBuybackItemLink, so a buyback row carries no link at all (ui_merchant.rs). if BenillaMerchantFrame.selectedTab == 2 then BuybackItem(button.merchantIndex) + return + end + local index = BenillaMerchantFrame_ItemIndex(button) + if mouseButton == "LeftButton" then + -- The reference's LEFT-button modifier fork (ref l.301-306): CTRL previews the + -- vendor's item in the dressing room (decision 1060) — which is what the Ctrl-hover + -- magnifier this window already arms has been promising — and SHIFT posts its link + -- into an open chat edit box (decision 1059). Both return: a modified click must + -- never also buy. + if IsControlKeyDown() then + DressUpItemLink(GetMerchantItemLink(index)) + return + elseif IsShiftKeyDown() then + -- ref l.304-306, through the shared helper (UIParent.xml): it carries the ref's + -- own edit-box-visible guard AND our nil-link one (a row whose item template is + -- still in flight has no link yet). + BenillaChatEdit_InsertLink(GetMerchantItemLink(index)) + -- DEFERRED, in the ref's own words: the `else` half of that shift arm is + -- `local maxStack = GetMerchantItemMaxStack(this:GetID())` → + -- `OpenStackSplitFrame(maxStack, ...)`, buying up to what you can afford + -- (ref l.307-327). `GetMerchantItemMaxStack` has no binding here, so a + -- shift-click with chat CLOSED does nothing rather than guess a max stack. + return + end + -- Plain LEFT-click is PickupMerchantItem, a drag-buy onto the cursor (ref l.329) — + -- still a no-op here until the cursor-item arc lands. elseif mouseButton == "RightButton" then - BuyMerchantItem(BenillaMerchantFrame_ItemIndex(button)) + -- The ref's right-button leg opens with `if IsControlKeyDown() ... then return` + -- (l.332-333): a Ctrl-held right-click does nothing at all, and in particular must + -- not buy while the player is Ctrl-clicking around to preview. Its shift arm is the + -- same GetMerchantItemMaxStack stack-split deferred above (l.334-353) — and the + -- ref's own `maxStack <= 1` case re-enters to buy a single unit (l.341-343), which + -- is exactly what falling through to BuyMerchantItem does here. + if IsControlKeyDown() then + return + end + BuyMerchantItem(index) end end diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml index b1a4a83b..c7a6bcdd 100644 --- a/crates/benilla-app/assets/ui/OptionsFrame.xml +++ b/crates/benilla-app/assets/ui/OptionsFrame.xml @@ -131,8 +131,15 @@ whose click jumps to the page (the era redirect law), capped at the static pool's size. Both bars wear the shared kit's trough (the recessed channel — 1.12 authors that art per window, so it is opt-in there and declared beside each bar here). - - **Setting-page cuts (0957 Audio, 0959 Graphics)**: setting-row tooltips (the era - OPTION_TOOLTIP_* hovers) are not wired; Defaults resets the VISIBLE page only (the + - **Row tooltips are 1.12's own strings on the era's seat (B223, decision 1054)**: every + row whose setting exists in a 1.12 panel raises that panel's `OPTION_TOOLTIP_*` text — + the description ALONE, wrapped, in the engine's default gold (the 1.12/1.14 option-row + plate, and this window's own since 1008's Character Specific row), seated the era way + (ANCHOR_RIGHT off the label half, x -10). The era's name-line-then-description pair + (`Settings.InitTooltip` → `GameTooltip_AddHighlightLine(name)`) is cut: its title would + repeat the label the cursor is already on. **Auto Loot** is the one row with no 1.12 + string — no 1.12 row exists for it (its label is era's too) — and raises no plate. + - **Setting-page cuts (0957 Audio, 0959 Graphics)**: Defaults resets the VISIBLE page only (the era confirm dialog is not modeled); era rows with no 1.12 CVar + benilla knob behind them (output device, Dialog volume, loop music, channels, cache; the whole era Graphics dropdown zoo) don't ship. A slider shows the 1.12 panel's @@ -609,8 +616,13 @@ Nameplates = { "RowPlayerNames", "RowNpcNames", "RowOwnName" }, } - function OptionsRow_OnLoad(row, cvar, label, child) + -- `tip` is the GlobalStrings KEY of the row's description (B223, decision 1054) — the key, + -- not the text: it resolves at HOVER, the way the 1.12 Interface panel resolves its own + -- (UIOptionsFrame.lua l.265, `getglobal("OPTION_TOOLTIP_"..key)`), so the row is wired + -- before GlobalStrings has to exist. nil = no 1.12 string for this setting (Auto Loot). + function OptionsRow_OnLoad(row, cvar, label, tip, child) row.cvar = cvar + row.tip = tip -- The search tag: the era's default is exactly the setting's display name -- (SettingControls.lua l.773 AddSearchTags(name)), uppercased at registration -- (Blizzard_Settings.lua l.60). @@ -725,9 +737,47 @@ -- The era row-wide hover wash (HoverBackgroundTemplate, white a=0.1): the row and every -- control on it show the SAME texture, so crossing from label to slider doesn't blink. + -- ONE seam carries the wash AND the plate (B223): every surface that lights the row also + -- raises its tooltip, so the two can never disagree — and the crossing is still seamless, + -- since the engine fires OnLeave before OnEnter within a move (pointer.rs), leaving the + -- hide/re-show inside a single frame. function OptionsRow_Hover(row, on) local hover = getglobal(row:GetName() .. "Hover") - if on then hover:Show() else hover:Hide() end + if on then + hover:Show() + OptionsRow_ShowTooltip(row) + else + hover:Hide() + GameTooltip:Hide() + end + end + + -- The row's descriptive plate (B223, decision 1054). + -- STRING: 1.12's own per-option text, by GlobalStrings key (see OptionsRow_OnLoad). A row + -- with no 1.12 string raises NOTHING — a plate whose only line echoes the label the cursor + -- is on is noise, and inventing a description would be inventing fidelity. + -- SHAPE: the 1.12/1.14 option-row plate — the description alone, wrapped, in the tooltip's + -- default gold (OptionsFrameTemplates.xml l.52-64: SetOwner + SetText(text, …, 1)); this + -- window already wears it on the Keybindings page's Character Specific row (1008). The era + -- pair (a white name line over the description) is cut — see the SCOPE note. + -- SEAT: the era's (DefaultTooltipMixin.OnEnter — ANCHOR_RIGHT, x -10, off the row's LABEL + -- half), carried by the $parentTip region, so the plate opens at the same column on every + -- row whichever surface the cursor crossed. + function OptionsRow_ShowTooltip(row) + local text = row.tip and getglobal(row.tip) + if text then + GameTooltip:SetOwner(getglobal(row:GetName() .. "Tip"), "ANCHOR_RIGHT", -10, 0) + GameTooltip:SetText(text, nil, nil, nil, nil, 1) + else + -- A mute row PUTS AWAY whatever the last row raised. Entering a row asserts "this + -- row's plate is what shows", so an early return isn't enough: the ordinary cursor + -- path is saved by the outgoing row's own OnLeave, but any caller that enters + -- without one (a driven hover, a re-layout under the cursor) would leave the + -- neighbour's description standing over a row it doesn't describe — a plate saying + -- the wrong thing is worse than a row saying nothing. 1.12's own handler has the + -- bare early-out; this is deliberate, and it is why the mute case has a test. + GameTooltip:Hide() + end end function OptionsCheckbox_OnClick(box) @@ -1076,6 +1126,18 @@ + + + + + + + @@ -1135,6 +1197,16 @@ + + + + + + + @@ -1218,6 +1290,14 @@ + + + + + + + @@ -1599,7 +1679,10 @@ deselectOnClick=="0"; era wraps the CVar in NegateBoolean); labels are 1.12 GlobalStrings where the row exists there (GAMEFIELD_DESELECT_TEXT, INVERT_MOUSE), era's for Auto Loot - (no 1.12 row exists). --> + (no 1.12 row exists) — and the tooltip keys are those same + 1.12 keys through the panel's own `_TEXT`-stripping lookup + (UIOptionsFrame.lua l.265), which is why Sticky Targeting's + description is GAMEFIELD_DESELECT's. --> @@ -1612,7 +1695,8 @@ - OptionsRow_OnLoad(self, "deselectOnClick", "Sticky Targeting") + OptionsRow_OnLoad(self, "deselectOnClick", "Sticky Targeting", + "OPTION_TOOLTIP_GAMEFIELD_DESELECT") self.invert = 1 @@ -1621,6 +1705,10 @@ + OptionsRow_OnLoad(self, "autoLootDefault", "Auto Loot") @@ -1628,7 +1716,8 @@ - OptionsRow_OnLoad(self, "mouseInvertPitch", "Invert Mouse") + OptionsRow_OnLoad(self, "mouseInvertPitch", "Invert Mouse", + "OPTION_TOOLTIP_INVERT_MOUSE") @@ -1653,7 +1742,8 @@ - OptionsRow_OnLoad(self, "MasterSoundEffects", "Enable All Sound") + OptionsRow_OnLoad(self, "MasterSoundEffects", "Enable All Sound", + "OPTION_TOOLTIP_ENABLE_ALL_SOUND") self.soundQuirk = 1 @@ -1662,7 +1752,8 @@ - OptionsRow_OnLoad(self, "MasterVolume", "Master Volume") + OptionsRow_OnLoad(self, "MasterVolume", "Master Volume", + "OPTION_TOOLTIP_MASTER_VOLUME") @@ -1670,7 +1761,8 @@ - OptionsRow_OnLoad(self, "SoundVolume", "Sound Volume", 1) + OptionsRow_OnLoad(self, "SoundVolume", "Sound Volume", + "OPTION_TOOLTIP_SOUND_VOLUME", 1) -- era parentInitializer (search pulls Master in -- above a matched child, SettingsPanel.lua l.665) self.parentRow = "RowMaster" @@ -1682,7 +1774,8 @@ - OptionsRow_OnLoad(self, "MusicVolume", "Music Volume", 1) + OptionsRow_OnLoad(self, "MusicVolume", "Music Volume", + "OPTION_TOOLTIP_MUSIC_VOLUME", 1) self.parentRow = "RowMaster" @@ -1692,7 +1785,8 @@ - OptionsRow_OnLoad(self, "AmbienceVolume", "Ambience Volume", 1) + OptionsRow_OnLoad(self, "AmbienceVolume", "Ambience Volume", + "OPTION_TOOLTIP_AMBIENCE_VOLUME", 1) self.parentRow = "RowMaster" @@ -1702,7 +1796,8 @@ - OptionsRow_OnLoad(self, "EnableMusic", "Enable Music") + OptionsRow_OnLoad(self, "EnableMusic", "Enable Music", + "OPTION_TOOLTIP_ENABLE_MUSIC") self.soundQuirk = 1 @@ -1712,7 +1807,8 @@ - OptionsRow_OnLoad(self, "EnableAmbience", "Enable Ambience") + OptionsRow_OnLoad(self, "EnableAmbience", "Enable Ambience", + "OPTION_TOOLTIP_ENABLE_AMBIENCE") self.soundQuirk = 1 @@ -1742,7 +1838,8 @@ - OptionsRow_OnLoad(self, "uiScale", "UI Scale") + OptionsRow_OnLoad(self, "uiScale", "UI Scale", + "OPTION_TOOLTIP_UI_SCALE") OptionsSliderRow_Setup(self, 0.64, 1.0, 0.01) -- era registers its uiscale row CommitFlag.Apply -- (Graphics.lua l.684): the one row whose live @@ -1757,7 +1854,8 @@ - OptionsRow_OnLoad(self, "WorldDetail", "Environment Detail") + OptionsRow_OnLoad(self, "WorldDetail", "Environment Detail", + "OPTION_TOOLTIP_ENVIRONMENT_DETAIL") -- The stop names are the era preset ladder's (its -- quality dropdowns say Low/Medium/High); 1.12's -- slider had no stop labels to quote. @@ -1787,21 +1885,24 @@ - OptionsRow_OnLoad(self, "UnitNamePlayer", "Player Names") + OptionsRow_OnLoad(self, "UnitNamePlayer", "Player Names", + "OPTION_TOOLTIP_SHOW_PLAYER_NAMES") - OptionsRow_OnLoad(self, "UnitNameNPC", "NPC Names") + OptionsRow_OnLoad(self, "UnitNameNPC", "NPC Names", + "OPTION_TOOLTIP_SHOW_NPC_NAMES") - OptionsRow_OnLoad(self, "UnitNameOwn", "Show Own Name") + OptionsRow_OnLoad(self, "UnitNameOwn", "Show Own Name", + "OPTION_TOOLTIP_SHOW_OWN_NAME") diff --git a/crates/benilla-app/assets/ui/PetActionBar.xml b/crates/benilla-app/assets/ui/PetActionBar.xml index 4800bc58..c7d86e38 100644 --- a/crates/benilla-app/assets/ui/PetActionBar.xml +++ b/crates/benilla-app/assets/ui/PetActionBar.xml @@ -245,8 +245,15 @@ -- Note what the key path does NOT do — it is a bare CastPetAction, with none of the click -- path's forks: no shift pick-up, no right-button autocast, and no IsPetAttackActive -- call-off. That is the reference's split, not a simplification of ours: the fork lives in - -- PetActionButton_OnClick, which a key press never reaches. So CTRL-1 on a pet already - -- attacking re-issues Attack where a left click would call it off. + -- PetActionButton_OnClick, which a key press never reaches. + -- + -- On a PET bar none of that is visible, because the call-off fork is DEAD CODE there: + -- IsPetAttackActive needs the [0xb714b0] latch, and `0x4bd420` raises it only when the + -- bar's unit is the one you are POSSESSING (ui_pet.rs's `possessing` carries the carve — + -- UNIT_FLAG_POSSESSED, never a hunter's or warlock's pet). A pet's Attack button can never + -- latch lit, so click and key both simply order the attack. The split is visible on a + -- POSSESS bar (Mind Control, Eye of Kilrogg, Eyes of the Beast): there a click sends + -- PetStopAttack and CTRL-1 re-issues the order instead. function BenillaPetActionButtonDown(id) local button = getglobal("BenillaPetActionButton" .. id) if ( button:GetButtonState() == "NORMAL" ) then @@ -390,6 +397,78 @@ BenillaPetAutocast_Update(getglobal("BenillaPetActionButton" .. i), frame.clock) end end + + -- ── The pet menu's two dialogs (ref StaticPopup.lua l.738-748 + l.1069-1102 + l.365-374, + -- decision 1066) ──────────────────────────────────────────────────────────────────── + -- They live here rather than with the rows that raise them (UnitPopup.xml) for the same + -- reason every other dialog in this codebase lives with its arc's frame file: registering + -- into StaticPopupDialogs is a LOAD-TIME dependency on UiPanels.xml's engine, and + -- UnitPopup.xml is a shared engine that eight isolation prefixes load without it. Raising + -- them is not a dependency — StaticPopup_Show resolves at click time. + -- + -- 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 — because a rename is one-shot, so a + -- typo is permanent in exactly 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. + 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, + } ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BenillaPetExpBar_Update() + + + + + + + + + + + + + + + + + + + + + + + + self.setFacing = BenillaPetPaperDollModel_SetFacing + BenillaPaperDollModel_OnLoad(self) + BenillaSetBoothTexture(getglobal(self:GetName() .. "Texture"), "petdoll") + + BenillaPaperDollModel_OnUpdate(self, arg1) + + + + + + + + + + + + + + + + + + + + self:EnableMouse(true) + + -- ref l.267-270, unguarded now that both halves exist (decision 1062): + -- `GetPetFoodTypes` is a real binding and `BuildListString` is the ref's + -- own UIParent.lua joiner, transcribed in UIParent.xml. + -- + -- `BuildListString()` returns NIL on an empty list and `format`'s "%s" + -- would error on it, so the pairing matters. Two things keep it safe, and + -- both are the reference's: this frame is shown for HUNTER pets only (the + -- `canGainXP` block above), and in the shipped `CreatureFamily.dbc` every + -- family with a ZERO food mask is a warlock minion or "Remote Control" — + -- every hunter-tameable family has a real diet. `GetPetFoodTypes` is + -- itself hunter-gated in the binary too (`0x6116e0`), so all three agree. + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + GameTooltip:SetText(format(PET_DIET_TEMPLATE, BuildListString(GetPetFoodTypes()))) + GameTooltip:Show() + + GameTooltip:Hide() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + self:EnableMouse(true) + BenillaPaperDollStatTooltip(self, "STRENGTH", "pet") + GameTooltip:Hide() + + + + + + self:EnableMouse(true) + BenillaPaperDollStatTooltip(self, "AGILITY", "pet") + GameTooltip:Hide() + + + + + + self:EnableMouse(true) + BenillaPaperDollStatTooltip(self, "STAMINA", "pet") + GameTooltip:Hide() + + + + + + self:EnableMouse(true) + BenillaPaperDollStatTooltip(self, "INTELLECT", "pet") + GameTooltip:Hide() + + + + + + self:EnableMouse(true) + BenillaPaperDollStatTooltip(self, "SPIRIT", "pet") + GameTooltip:Hide() + + + + + + + self:EnableMouse(true) + + + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + GameTooltip:SetText(ATTACK_TOOLTIP, 1.0, 1.0, 1.0) + GameTooltip:Show() + + GameTooltip:Hide() + + + + + + self:EnableMouse(true) + + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + GameTooltip:SetText(self.tooltip) + GameTooltip:AddLine(self.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) + GameTooltip:Show() + + GameTooltip:Hide() + + + + + + self:EnableMouse(true) + + BenillaCharacterDamageFrame_OnEnter(self) + GameTooltip:Hide() + + + + + + self:EnableMouse(true) + + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + GameTooltip:SetText(DEFENSE_TOOLTIP, 1.0, 1.0, 1.0) + GameTooltip:Show() + + GameTooltip:Hide() + + + + + + self:EnableMouse(true) + + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + GameTooltip:SetText(self.tooltip) + GameTooltip:AddLine(self.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) + GameTooltip:Show() + + GameTooltip:Hide() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BenillaPetPaperDollFrame_OnLoad(self) + BenillaPetPaperDollFrame_OnEvent(self) + BenillaPetPaperDollFrame_OnShow() + BenillaPetPaperDollFrame_OnHide() + + + diff --git a/crates/benilla-app/assets/ui/QuestFrame.xml b/crates/benilla-app/assets/ui/QuestFrame.xml index 908676b7..089fa724 100644 --- a/crates/benilla-app/assets/ui/QuestFrame.xml +++ b/crates/benilla-app/assets/ui/QuestFrame.xml @@ -104,6 +104,13 @@ button.kind = kind button.index = index button.itemId = itemId + -- ref l.388/490 ("-- For the tooltip"): every laid-out reward/choice row is stamped + -- rewardType = "item"; the ONE row that ever reads "spell" is the reward-spell row + -- (l.445), which v1 doesn't build (GetRewardSpell() is nil, decision 0088). The click + -- arms below guard on it exactly as the ref does, so the guard is live the day that + -- row lands. (Deviation, harmless: the ref leaves required rows' rewardType unset — + -- l.197-200 stamps only `.type` — which reads the same `~= "spell"` either way.) + button.rewardType = "item" button.quality = quality button.icon:SetTexture(texture or BENILLA_FALLBACK_ICON) button.name:SetText(name or "...") @@ -559,9 +566,65 @@ end end - -- ref QuestRewardItem_OnClick (l.127-141): only a REWARD-panel choice row is selectable (the - -- detail panel's rows, and both panels' FIXED reward rows, are informational only — no - -- OnClick wired to them, matching the ref's separate QuestRewardItemTemplate vs QuestItemTemplate). + -- ── The item-row modifier fork, shared by every quest-item row in this window ───────────── + -- ref QuestItem_OnClick (QuestFrame.lua:115-125), the OnClick QuestItemTemplate itself + -- carries (QuestFrameTemplates.xml:162-164) — so it covers the DETAIL panel's choice/reward + -- rows, the PROGRESS panel's required rows, and the reward panel's FIXED reward rows. + -- CTRL previews the item in the dressing room (decision 1060), SHIFT posts its link into an + -- open chat edit box (decision 1059); a plain click does nothing here, which is why these + -- rows carried no OnClick before this arc. + -- + -- Two guards the ref's own code has, transcribed: the ctrl arm is gated on + -- `this.rewardType ~= "spell"` (l.117 — a spell reward has no item to wear), and the shift + -- arm additionally requires the edit box to be VISIBLE (l.121; the `and rewardType ~= + -- "spell"` rides the same line there). Ours adds `ChatFrameEditBox and` — ChatFrame.xml is + -- a separate file a window test need not load, exactly as BenillaPaperDollSlot_OnClick does. + -- + -- One benilla-only guard: `link` is nil until the ask-once item template lands (a state the + -- reference's synchronous item cache never has), and EditBox:Insert takes a string. The ref + -- already carries the twin of this guard inside DressUpItemLink (ref DressUpFrame.lua:11-13). + function BenillaQuestItem_OnClick(button) + if IsControlKeyDown() then + if button.rewardType ~= "spell" then + DressUpItemLink(GetQuestItemLink(button.kind, button.index)) + end + elseif IsShiftKeyDown() then + -- The ref's spell guard rides this arm (l.121); the "edit box up" and "link not + -- nil" pair is the shared helper's (UIParent.xml — one law, one place). + if button.rewardType ~= "spell" then + BenillaChatEdit_InsertLink(GetQuestItemLink(button.kind, button.index)) + end + end + end + + -- ref QuestRewardItem_OnClick (l.127-141), the OnClick the REWARD panel's choice rows carry + -- through QuestRewardItemTemplate (QuestFrameTemplates.xml:169-171). The first two arms are + -- QuestItem_OnClick's verbatim (only the shift arm's spell guard differs — the ref drops it + -- here, l.133); the choice SELECT is the THIRD arm, so a ctrl- or shift-click must NOT also + -- pick the reward. That ordering is the whole point of the fork. + function BenillaQuestRewardItem_OnClick(button) + if IsControlKeyDown() then + if button.rewardType ~= "spell" then + DressUpItemLink(GetQuestItemLink(button.kind, button.index)) + end + elseif IsShiftKeyDown() then + -- No spell guard on this arm — the ref drops it here (l.133). The visible/non-nil + -- pair is the shared helper's (UIParent.xml). + BenillaChatEdit_InsertLink(GetQuestItemLink(button.kind, button.index)) + elseif button.kind == "choice" then + -- ref l.136's own `this.type == "choice"` test. The ref needs it because its reward + -- panel packs choices AND fixed rewards into ONE QuestRewardItem1..10 pool; ours + -- keeps two pools and hangs this template only on BenillaQuestRewardChoice*, so the + -- test is belt-and-braces — kept because it is the ref's, and because it is what + -- makes the template safe to reuse if the pools are ever merged. + BenillaQuestRewardChoice_OnClick(button) + end + end + + -- The third arm alone (ref l.136-139): only a REWARD-panel choice row is selectable — the + -- detail panel's rows and both panels' FIXED reward rows ride the plain + -- `BenillaQuestItemTemplate`, whose OnClick has no select arm at all, matching the ref's + -- separate QuestRewardItemTemplate vs QuestItemTemplate. function BenillaQuestRewardChoice_OnClick(button) getglobal("BenillaQuestFrame").itemChoice = button.index -- Ref QuestRewardItem_OnClick l.137-138 verbatim: the glow frame seats at (-8,7) on @@ -702,14 +765,20 @@ GameTooltip:Show() GameTooltip:Hide() + + BenillaQuestItem_OnClick(self) + (QuestFrameTemplates.xml:167-173) — the ctrl/shift arms shared with the base template, then + the choose highlight as the THIRD arm. The detail panel's rows and both panels' FIXED reward + rows stay on plain `BenillaQuestItemTemplate`, whose OnClick has no select arm (ref: only + reward-panel CHOICE rows select). --> + BenillaQuestLogRewardItem_OnClick(self) diff --git a/crates/benilla-app/assets/ui/UIParent.xml b/crates/benilla-app/assets/ui/UIParent.xml index a72b1937..c7f5bc22 100644 --- a/crates/benilla-app/assets/ui/UIParent.xml +++ b/crates/benilla-app/assets/ui/UIParent.xml @@ -341,5 +341,66 @@ end return modKeys..localizedName; end + + -- ref UIParent.lua:1051-1057, transcribed 1:1 (decision 1062). The reference's own + -- list joiner, and it is exactly as plain as it looks: a bare ", " between every item, + -- with NO "and" before the last one and NO localization string of any kind — 1.12 has + -- no LIST_DELIMITER/AND global for it to reach for, and it does not build one. (The + -- richer `BuildColoredListString` right below it in the reference — the per-item + -- met/unmet red — already lives in TradeSkillFrame.xml as a guarded global, 0437 phase + -- 3; folding that one up here beside this is a follow-up, not this slice's churn.) + -- + -- Callers: the pet paper doll's diet tooltip and PetStable.lua's, both as + -- `format(PET_DIET_TEMPLATE, BuildListString(GetPetFoodTypes()))`. + -- + -- ZERO arguments returns NIL, not "" — the ref reads `arg[1]` off an empty vararg and + -- returns it unchanged. That is load-bearing: `format("%s", nil)` errors, so a caller + -- that can pass nothing (a warlock minion's empty diet) must be gated by its own UI, and + -- it is — the diet icon is hunter-only. Quietly returning "" here would hide that. + -- + -- Lua-5.0 `arg`/`arg.n` has no equivalent (LUA_COMPAT_VARARG isn't shipped) — + -- `select("#", ...)`/`select(i, ...)`, TradeSkillFrame.xml's own substitution. + function BuildListString(...) + local n = select("#", ...); + if ( n == 0 ) then + return nil; + end + local list = select(1, ...); + for i = 2, n do + local item = select(i, ...); + list = list..", "..item; + end + return list; + end + + -- BenillaChatEdit_InsertLink(link) → true if it went in, else nil (decision 1059). + -- + -- Every shift-click site in this UI does the same two things before inserting: check the + -- chat edit box is up, and — ours alone — check the link is not nil. That nil case is + -- **benilla's, not the reference's**: our item templates resolve asynchronously + -- (`CMSG_ITEM_QUERY_SINGLE`, answered a frame or many later), so a `Get…Link` getter + -- legitimately answers nil for a slot whose name and quality have not landed, where the real + -- client's synchronous item cache always has one. `EditBox:Insert` takes a string (the + -- byte-modelled `0x77bee0`), so an unguarded nil is a script error — which is exactly the + -- bug this function exists to make impossible to reintroduce at the next site. A dozen call + -- sites were growing a dozen copies of the same two-line guard, and three of them shipped + -- without it. + -- + -- The RETURN is what lets a caller fork on "did chat take it?" — a bag slot's shift-click + -- posts the link when chat is open and opens the stack splitter when it is not (ref + -- ContainerFrame.lua:567-577). Blizzard themselves eventually factored this exact helper out + -- for this exact reason: 2.0's `ChatEdit_InsertLink(text)`, returning true/false, called from + -- every one of these sites. 1.12 has no such function, so ours carries a benilla name rather + -- than pretending to be reference API. + function BenillaChatEdit_InsertLink(link) + if not link then + return nil + end + if not ChatFrameEditBox or not ChatFrameEditBox:IsVisible() then + return nil + end + ChatFrameEditBox:Insert(link) + return true + end ]]> diff --git a/crates/benilla-app/assets/ui/UiPanels.xml b/crates/benilla-app/assets/ui/UiPanels.xml index 725df673..7668a464 100644 --- a/crates/benilla-app/assets/ui/UiPanels.xml +++ b/crates/benilla-app/assets/ui/UiPanels.xml @@ -45,6 +45,11 @@ -- registration line (`UIPanelWindows["TaxiFrame"] = { area = "left", pushable = 0 }`), -- quoted verbatim — same left-slot competition as Gossip/Merchant/Trainer above. UIPanelWindows["BenillaTaxiFrame"] = { area = "left", pushable = 0 } + -- The dressing room (decision 1060, DressUpFrame.xml): the ref's own UIParent.lua l.41 + -- registration (`UIPanelWindows["DressUpFrame"] = { area = "left", pushable = 2 }`), quoted + -- verbatim — pushable=2 lets the NPC-session windows (pushable=0) shove it aside, which is + -- what makes "ctrl-click a merchant's item" show both. + UIPanelWindows["BenillaDressUpFrame"] = { area = "left", pushable = 2 } UIPanelWindows["BenillaQuestFrame"] = { area = "left", pushable = 0 } UIPanelWindows["BenillaQuestLogFrame"] = { area = "left", pushable = 0 } UIPanelWindows["BenillaLootFrame"] = { area = "left", pushable = 7 } diff --git a/crates/benilla-app/assets/ui/UnitFrames.xml b/crates/benilla-app/assets/ui/UnitFrames.xml index ff57edb2..a2e09d6e 100644 --- a/crates/benilla-app/assets/ui/UnitFrames.xml +++ b/crates/benilla-app/assets/ui/UnitFrames.xml @@ -223,8 +223,19 @@ -- flag to build one from — UnitIsPlayer is the nearest honest read, so a charmed mob or -- another player's pet falls through to the unit tooltip where the reference would explain -- the menu. The player case, which is the one anybody hovers, is exact. + -- ref UnitFrame_OnEnter l.55-57, ahead of everything else: the ONLY lit/grey cursor split + -- over a UI element in 1.12, and it is authored here in Lua rather than in the engine + -- (wow-re item-target-cursor-and-dropitemonunit.md 4.3). Bag and paper-doll slots have no + -- such branch, which is why they show the ordinary pointer. function BenillaUnitFrame_OnEnter(frame) local unit = frame.unit + if SpellIsTargeting() then + if SpellCanTargetUnit(unit) then + SetCursor("CAST_CURSOR") + else + SetCursor("CAST_ERROR_CURSOR") + end + end if not UnitExists(unit) then return end GameTooltip_SetDefaultAnchor(GameTooltip, frame) if SHOW_NEWBIE_TIPS == "1" then @@ -249,6 +260,10 @@ -- lingers on a ramp reads as a stuck tooltip, where a unit readout fading is the client's -- signature. (Items Hide, units fade — that split still holds for the world mouseover.) function BenillaUnitFrame_OnLeave(frame) + -- ref UnitFrame_OnLeave l.86: leaving a unit frame while still targeting goes to the + -- GREY cast cursor, not to the base one — you have left the only thing the armed spell + -- could have bound. + if SpellIsTargeting() then SetCursor("CAST_ERROR_CURSOR") end if SHOW_NEWBIE_TIPS == "1" then GameTooltip:Hide() else @@ -708,6 +723,11 @@ local name if UnitIsUnit("target", "player") then menu = "SELF" + elseif UnitIsUnit("target", "pet") then + -- Selecting your own pet and right-clicking the TARGET frame gives you the pet + -- menu — the ref's own second branch (TargetFrame.lua l.440-441), which sat as a + -- comment here until the "pet" token (0990) and then the menu itself (1066). + menu = "PET" elseif UnitIsPlayer("target") then if UnitInParty("target") then menu = "PARTY" @@ -715,8 +735,6 @@ menu = "PLAYER" end else - -- The ref checks UnitIsUnit("target", "pet") → PET before this branch; no "pet" - -- token exists until the pet slice (phase 4). menu = "RAID_TARGET_ICON" name = RAID_TARGET_ICON end @@ -1169,16 +1187,12 @@ 0988's warning was answered rather than dodged. Its three siblings (`GetPetLoyalty`, `GetPetTrainingPoints`, `GetPetExperience`) landed with it. - STILL DEFERRED, each with its reason: - - The PET right-click menu. The reference opens it from PetFrame_OnClick's else arm - (`ToggleDropDownMenu(1, nil, PetFrameDropDown)` — PetFrame.lua:127-129), and its four - entries (UnitPopup.xml's PET_PAPERDOLL / PET_RENAME / PET_ABANDON / PET_DISMISS) each - need a binding that is its own build — the pet paper doll most of all. Two more are - needed just to decide what the menu SHOWS: `PetCanBeAbandoned` forks it, hiding - paperdoll/rename/abandon for a warlock pet and hiding dismiss for a hunter's, so the - same menu reads "Abandon" (permanent, behind the ABANDON_PET confirm) on a hunter pet - and "Dismiss" on a demon. They stay suppressed in UnitPopup.xml; the frame takes no - right click rather than opening a menu of dead rows. + The PET RIGHT-CLICK MENU is no longer deferred either — decision 1066 built the last of + the five things it was waiting on (the pet paper doll, the fifth, landed as 1057). The + frame's else arm opens PetFrameDropDown below; the rows, their fork and their dialogs are + UnitPopup.xml's. + + STILL DEFERRED, with its reason: - PartyMemberBuffTooltip on hover (the ref's OnEnter panel): it belongs to the party frames' own deferred buff panel, not here. --> @@ -1348,20 +1362,52 @@ BenillaPetFrameAttackMode:SetVertexColor(1.0, 1.0, 1.0, alpha) end - -- ref PetFrame_OnClick's left branch, all three legs in the ref's own order. The right - -- branch (the PET menu) is the named deferral in the header. + -- ref PetFrame_OnClick, now whole (decision 1066 built the right branch). Its FIRST leg is + -- the one that is easy to drop: with a spell armed, a RIGHT click disarms rather than + -- opening the menu, and it precedes both branches. function BenillaPetFrame_OnClick(button) - if button ~= "LeftButton" then return end - if SpellIsTargeting() then - SpellTargetUnit("pet") - elseif CursorHasItem() then - DropItemOnUnit("pet") + if SpellIsTargeting() and button == "RightButton" then + SpellStopTargeting() + return + end + if button == "LeftButton" then + if SpellIsTargeting() then + SpellTargetUnit("pet") + elseif CursorHasItem() then + DropItemOnUnit("pet") + else + TargetUnit("pet") + end else - TargetUnit("pet") + -- No anchor arguments, unlike the player and target frames: the ref opens this one + -- at the CURSOR (PetFrame.lua l.128). + ToggleDropDownMenu(1, nil, PetFrameDropDown) + end + end + + -- The PET menu's owner (ref PetFrame.lua l.158-166). The UnitExists gate is the ref's own, + -- and it is not redundant with the frame being hidden: this dropdown is also what the + -- TARGET frame's menu falls through to when you have your own pet selected. + function PetFrameDropDown_OnLoad() + UIDropDownMenu_Initialize(this, PetFrameDropDown_Initialize, "MENU") + end + + function PetFrameDropDown_Initialize() + if UnitExists("pet") then + UnitPopup_ShowMenu(PetFrameDropDown, "PET", "pet") end end function BenillaPetFrame_OnEnter(frame) + -- The pet frame is a unit frame in the reference, and takes the same cursor split: + -- hovering it with a spell armed lights or greys exactly as the target frame does. + if SpellIsTargeting() then + if SpellCanTargetUnit("pet") then + SetCursor("CAST_CURSOR") + else + SetCursor("CAST_ERROR_CURSOR") + end + end if not UnitExists("pet") then return end GameTooltip_SetDefaultAnchor(GameTooltip, frame) if GameTooltip:SetUnit("pet") then @@ -1410,6 +1456,18 @@ + + + + + + PetFrameDropDown_OnLoad() + + @@ -1512,7 +1570,7 @@ BenillaPetFrame_OnEvent(this) BenillaPetFrame_OnUpdate(this, arg1) BenillaPetFrame_OnEnter(this) - GameTooltip:FadeOut() + if SpellIsTargeting() then SetCursor("CAST_ERROR_CURSOR") end GameTooltip:FadeOut() BenillaPetFrame_OnClick(arg1) diff --git a/crates/benilla-app/assets/ui/UnitPopup.xml b/crates/benilla-app/assets/ui/UnitPopup.xml index ef633897..2ec8327c 100644 --- a/crates/benilla-app/assets/ui/UnitPopup.xml +++ b/crates/benilla-app/assets/ui/UnitPopup.xml @@ -22,9 +22,12 @@ that last entry's stated reason ("its CMSG family is its own slice") was simply WRONG: 0890 verified auto-follow sends nothing at all, it is pure client-side movement, so the row was waiting on a wire slice that does not exist. It was the motion law it really needed. - Everything a menu SHOWS works. The PET menu and the RAID_* rank rows stay - in the tables but are unreachable (no "pet" token, no raid roster) — their OnClick branches - are trimmed with the rest. + Everything a menu SHOWS works. The PET menu opened with decision 1066: its four rows and the + one predicate that forks them are below, its opener is UnitFrames.xml's PetFrameDropDown, and + the two dialogs behind Abandon and Rename are registered in PetActionBar.xml (with the pet + arc, like every other dialog in this codebase — this file must stay loadable without the + popup engine). The RAID_* rank rows stay in the tables but are unreachable (no raid roster) — + their OnClick branches are trimmed with the rest. - Lua 5.1: the ref's 5.0 `for i, v in t do` iteration is ipairs/pairs here; strsub → the TEXT stub below keeps the table lines verbatim (the ref's TEXT() is the localization identity). @@ -126,6 +129,7 @@ UnitPopupFrames = { "PlayerFrameDropDown", "TargetFrameDropDown", + "PetFrameDropDown", "PartyMemberFrame1DropDown", "PartyMemberFrame2DropDown", "PartyMemberFrame3DropDown", @@ -404,13 +408,27 @@ elseif ( value == "RAID_LEADER" or value == "RAID_PROMOTE" or value == "RAID_DEMOTE" or value == "RAID_REMOVE" ) then -- The raid roster (GetRaidRosterInfo) is a later arc — no raid menus open yet. UnitPopupShown[index] = 0; - elseif ( value == "PET_PAPERDOLL" or value == "PET_RENAME" or value == "PET_ABANDON" or value == "PET_DISMISS" ) then - -- The "pet" token exists now (0990), but none of these four rows does: each - -- wants its own binding (paperdoll a whole panel), and `PetCanBeAbandoned` - -- besides, which is what forks abandon-vs-dismiss. So the PET menu stays shut - -- at its opener (UnitFrames.xml's pet frame takes no right click) and these - -- stay suppressed here — the two halves of one deferral, not two. - UnitPopupShown[index] = 0; + -- The four PET rows, ref UnitPopup.lua l.402-417 verbatim (decision 1066). ONE + -- predicate decides all four, and Dismiss reads it INVERTED: a hunter's pet is + -- abandonable, so it offers paperdoll/rename/abandon and hides dismiss; a + -- warlock's demon is not, so it offers only dismiss. Rename wants both bits, + -- because its own is one-shot — the server clears it after the first rename. + elseif ( value == "PET_PAPERDOLL" ) then + if ( not PetCanBeAbandoned() ) then + UnitPopupShown[index] = 0; + end + elseif ( value == "PET_RENAME" ) then + if ( not PetCanBeAbandoned() or not PetCanBeRenamed() ) then + UnitPopupShown[index] = 0; + end + elseif ( value == "PET_ABANDON" ) then + if ( not PetCanBeAbandoned() ) then + UnitPopupShown[index] = 0; + end + elseif ( value == "PET_DISMISS" ) then + if ( PetCanBeAbandoned() ) then + UnitPopupShown[index] = 0; + end elseif ( string.sub(value, 1, 12) == "RAID_TARGET_" ) then if ( (inParty == 0) or ((isLeader == 0) and (isAssistant == 0)) ) then UnitPopupShown[index] = 0; @@ -596,11 +614,25 @@ -- already knows the unit's name to the letter, so unlike `/follow rag` it must not -- prefix-match its way onto a bystander. Client-side movement only, no wire. FollowByName(name, 1); + + -- The four PET arms, ref UnitPopup_OnClick l.590-597 verbatim (decision 1066). The + -- asymmetry is the reference's and it is the right one: DISMISS runs immediately, + -- ABANDON goes behind a confirm. Sending a summon away costs nothing; giving up a + -- tamed pet is permanent, and the server deletes it (`PET_SAVE_AS_DELETED`). + elseif ( button == "PET_DISMISS" ) then + PetDismiss(); + elseif ( button == "PET_ABANDON" ) then + StaticPopup_Show("ABANDON_PET"); + elseif ( button == "PET_PAPERDOLL" ) then + ToggleCharacter("BenillaPetPaperDollFrame"); + elseif ( button == "PET_RENAME" ) then + StaticPopup_Show("RENAME_PET"); end -- DEFERRED branches trimmed with their rows (header): TARGET, - -- GUILD_*, RESET_INSTANCES, PET_*, RAID_LEADER/PROMOTE/DEMOTE/REMOVE. + -- GUILD_*, RESET_INSTANCES, RAID_LEADER/PROMOTE/DEMOTE/REMOVE. PlaySound("UChatScrollButton"); end + ]]>