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"))
-
-
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
+
]]>