Files
benilla-octo/crates/benilla-app/assets/ui/UnitPopup.xml
T
SamandClaude Fable 5 b5bc569156 the addon arc: the AddOns folder opens
- benilla loads real 1.12 addons from Interface/AddOns — TOC, XML and Lua through one loader, with per-addon saved variables, Bindings.xml and the lifecycle events addons expect
- both AddOns screens are in: the character-select list with per-addon enable state, and the ESC menu's in-game list
- the API surface is measured, not guessed — a corpus harness runs hundreds of real 1.12 addons and ranks exactly which globals, templates and verbs they demand
- the Lua dialect is the client's own, read off the binary — 5.0 semantics with the 1.12 additions, and UI source files are bytes, not UTF-8

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:18:31 +07:00

648 lines
37 KiB
XML

<!--
The unit right-click popups (decision 0434 phase 5) — transcribed from the 1.12 FrameXML
UnitPopup.lua (the whole file: the button/menu tables, ShowMenu, HideButtons, the OnUpdate
enable pass, OnClick) + the UIParent.xml OnUpdate hook (a driver frame here — the DeathFrame
driver pattern; benilla's UIParent carries no OnUpdate).
The menus ride the dropdown kit's MENU displayMode + level-2 submenus (UIDropDownMenu.xml).
The owner frames (TargetFrameDropDown, PlayerFrameDropDown, PartyMemberFrameNDropDown) live
with their frames in UnitFrames.xml / PartyFrame.xml — this file is the shared engine, like
the ref's load split.
DEVIATIONS, deliberate (each waits for its arc):
- NO ADDED ROWS. A "PVP" row briefly sat at the head of SELF (decision 0646 §3); decision 0652
took it back out. 1.12.1 has no PvP toggle in any menu — `TogglePVP()`'s single caller in the
whole shipped UI is `SlashCmdList["PVP"]`, and `UnitPopup.lua` contains the string "PVP"
exactly zero times — so the row was a deviation that made this menu read wrong against the
reference. `/pvp` is the toggle the client has.
- DEFERRED ENTRIES: rows whose backing system doesn't exist yet are HIDDEN outright (the
BENILLA_UNITPOPUP_DEFERRED set in HideButtons) rather than shown-dead: TARGET (the friends
list), GUILD_PROMOTE/GUILD_LEAVE (the guild arc), RESET_INSTANCES (instances). TRADE came out
with decision 0592 P1, DUEL with 0633, INSPECT with 0631, FOLLOW with 0890's follow-up — and
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 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).
- The newbie tooltips on rows are OUT with the kit's (default-off cvar look).
- CheckInteractDistance now really gates (decision 0631): the binding landed with the inspect
row, off wow-re's §5-VERIFIED `check_interact_dist2` table {10², 11.1111², 10², 30²}. So every
SHOWN row with a non-zero `dist` grays out of range now — INSPECT (dist 1, 10 yd), TRADE
(dist 2, 11.11 yd) and DUEL (dist 3, 10 yd) — where TRADE and DUEL shipped ungated only
because the binding was nil when they landed. The nil-guard in the OnUpdate pass below is
kept as-is.
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Script><![CDATA[
-- The ref's localization identity (UIParent.lua) — keeps the table lines verbatim.
if ( not TEXT ) then
TEXT = function(s) return s end
end
UNITPOPUP_TIMEOUT = 5;
UnitPopupButtons = { };
UnitPopupButtons["CANCEL"] = { text = TEXT(CANCEL), dist = 0, space = 1 };
UnitPopupButtons["TRADE"] = { text = TEXT(TRADE), dist = 2 };
UnitPopupButtons["INSPECT"] = { text = TEXT(INSPECT), dist = 1 };
UnitPopupButtons["TARGET"] = { text = TEXT(TARGET), dist = 0 };
UnitPopupButtons["DUEL"] = { text = TEXT(DUEL), dist = 3, space = 1 };
UnitPopupButtons["WHISPER"] = { text = TEXT(WHISPER), dist = 0 };
UnitPopupButtons["INVITE"] = { text = TEXT(PARTY_INVITE), dist = 0 };
UnitPopupButtons["UNINVITE"] = { text = TEXT(PARTY_UNINVITE), dist = 0 };
UnitPopupButtons["PROMOTE"] = { text = TEXT(PARTY_PROMOTE), dist = 0 };
UnitPopupButtons["GUILD_PROMOTE"] = { text = TEXT(GUILD_PROMOTE), dist = 0 };
UnitPopupButtons["GUILD_LEAVE"] = { text = TEXT(GUILD_LEAVE), dist = 0 };
UnitPopupButtons["LEAVE"] = { text = TEXT(PARTY_LEAVE), dist = 0 };
UnitPopupButtons["FOLLOW"] = { text = TEXT(FOLLOW), dist = 4 };
UnitPopupButtons["PET_PASSIVE"] = { text = TEXT(PET_PASSIVE), dist = 0 };
UnitPopupButtons["PET_DEFENSIVE"] = { text = TEXT(PET_DEFENSIVE), dist = 0 };
UnitPopupButtons["PET_AGGRESSIVE"] = { text = TEXT(PET_AGGRESSIVE), dist = 0 };
UnitPopupButtons["PET_WAIT"] = { text = TEXT(PET_WAIT), dist = 0 };
UnitPopupButtons["PET_FOLLOW"] = { text = TEXT(PET_FOLLOW), dist = 0 };
UnitPopupButtons["PET_ATTACK"] = { text = TEXT(PET_ATTACK), dist = 0 };
UnitPopupButtons["PET_DISMISS"] = { text = TEXT(PET_DISMISS), dist = 0 };
UnitPopupButtons["PET_ABANDON"] = { text = TEXT(PET_ABANDON), dist = 0 };
UnitPopupButtons["PET_PAPERDOLL"] = { text = TEXT(PET_PAPERDOLL), dist = 0 };
UnitPopupButtons["PET_RENAME"] = { text = TEXT(PET_RENAME), dist = 0 };
UnitPopupButtons["LOOT_METHOD"] = { text = TEXT(LOOT_METHOD), dist = 0, nested = 1 };
UnitPopupButtons["FREE_FOR_ALL"] = { text = TEXT(LOOT_FREE_FOR_ALL), dist = 0 };
UnitPopupButtons["ROUND_ROBIN"] = { text = TEXT(LOOT_ROUND_ROBIN), dist = 0 };
UnitPopupButtons["MASTER_LOOTER"] = { text = TEXT(LOOT_MASTER_LOOTER), dist = 0 };
UnitPopupButtons["GROUP_LOOT"] = { text = TEXT(LOOT_GROUP_LOOT), dist = 0 };
UnitPopupButtons["NEED_BEFORE_GREED"] = { text = TEXT(LOOT_NEED_BEFORE_GREED), dist = 0 };
UnitPopupButtons["RESET_INSTANCES"] = { text = TEXT(RESET_INSTANCES), dist = 0 };
UnitPopupButtons["LOOT_THRESHOLD"] = { text = TEXT(LOOT_THRESHOLD), dist = 0, nested = 1 };
UnitPopupButtons["LOOT_PROMOTE"] = { text = TEXT(LOOT_PROMOTE), dist = 0 };
UnitPopupButtons["ITEM_QUALITY2_DESC"] = { text = TEXT(ITEM_QUALITY2_DESC), dist = 0, color = ITEM_QUALITY_COLORS[2] };
UnitPopupButtons["ITEM_QUALITY3_DESC"] = { text = TEXT(ITEM_QUALITY3_DESC), dist = 0, color = ITEM_QUALITY_COLORS[3] };
UnitPopupButtons["ITEM_QUALITY4_DESC"] = { text = TEXT(ITEM_QUALITY4_DESC), dist = 0, color = ITEM_QUALITY_COLORS[4] };
UnitPopupButtons["RAID_LEADER"] = { text = TEXT(NEW_LEADER), dist = 0 };
UnitPopupButtons["RAID_PROMOTE"] = { text = TEXT(PROMOTE), dist = 0 };
UnitPopupButtons["RAID_DEMOTE"] = { text = TEXT(DEMOTE), dist = 0 };
UnitPopupButtons["RAID_REMOVE"] = { text = TEXT(REMOVE), dist = 0 };
UnitPopupButtons["RAID_TARGET_ICON"] = { text = TEXT(RAID_TARGET_ICON), dist = 0, nested = 1 };
UnitPopupButtons["RAID_TARGET_1"] = { text = TEXT(RAID_TARGET_1), dist = 0, checkable = 1, color = {r = 1.0, g = 0.92, b = 0}, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0, tCoordRight = 0.25, tCoordTop = 0, tCoordBottom = 0.25 };
UnitPopupButtons["RAID_TARGET_2"] = { text = TEXT(RAID_TARGET_2), dist = 0, checkable = 1, color = {r = 0.98, g = 0.57, b = 0}, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.25, tCoordRight = 0.5, tCoordTop = 0, tCoordBottom = 0.25 };
UnitPopupButtons["RAID_TARGET_3"] = { text = TEXT(RAID_TARGET_3), dist = 0, checkable = 1, color = {r = 0.83, g = 0.22, b = 0.9}, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.5, tCoordRight = 0.75, tCoordTop = 0, tCoordBottom = 0.25 };
UnitPopupButtons["RAID_TARGET_4"] = { text = TEXT(RAID_TARGET_4), dist = 0, checkable = 1, color = {r = 0.04, g = 0.95, b = 0}, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.75, tCoordRight = 1, tCoordTop = 0, tCoordBottom = 0.25 };
UnitPopupButtons["RAID_TARGET_5"] = { text = TEXT(RAID_TARGET_5), dist = 0, checkable = 1, color = {r = 0.7, g = 0.82, b = 0.875}, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0, tCoordRight = 0.25, tCoordTop = 0.25, tCoordBottom = 0.5 };
UnitPopupButtons["RAID_TARGET_6"] = { text = TEXT(RAID_TARGET_6), dist = 0, checkable = 1, color = {r = 0, g = 0.71, b = 1}, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.25, tCoordRight = 0.5, tCoordTop = 0.25, tCoordBottom = 0.5 };
UnitPopupButtons["RAID_TARGET_7"] = { text = TEXT(RAID_TARGET_7), dist = 0, checkable = 1, color = {r = 1.0, g = 0.24, b = 0.168}, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.5, tCoordRight = 0.75, tCoordTop = 0.25, tCoordBottom = 0.5 };
UnitPopupButtons["RAID_TARGET_8"] = { text = TEXT(RAID_TARGET_8), dist = 0, checkable = 1, color = {r = 0.98, g = 0.98, b = 0.98}, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.75, tCoordRight = 1, tCoordTop = 0.25, tCoordBottom = 0.5 };
UnitPopupButtons["RAID_TARGET_NONE"] = { text = TEXT(NONE), dist = 0, checkable = 1, };
-- First level menus
UnitPopupMenus = { };
UnitPopupMenus["SELF"] = { "LOOT_METHOD", "LOOT_THRESHOLD", "LOOT_PROMOTE", "LEAVE", "RESET_INSTANCES", "RAID_TARGET_ICON", "CANCEL" };
UnitPopupMenus["PET"] = { "PET_PAPERDOLL", "PET_RENAME", "PET_ABANDON", "PET_DISMISS", "CANCEL" };
UnitPopupMenus["PARTY"] = { "WHISPER", "PROMOTE", "LOOT_PROMOTE", "UNINVITE", "INSPECT", "TRADE", "FOLLOW", "DUEL", "RAID_TARGET_ICON", "CANCEL" };
UnitPopupMenus["PLAYER"] = { "WHISPER", "INSPECT", "INVITE", "TRADE", "FOLLOW", "DUEL", "RAID_TARGET_ICON", "CANCEL" };
UnitPopupMenus["RAID"] = { "RAID_LEADER", "RAID_PROMOTE", "RAID_DEMOTE", "RAID_REMOVE", "CANCEL" };
UnitPopupMenus["FRIEND"] = { "WHISPER", "INVITE", "TARGET", "GUILD_PROMOTE", "GUILD_LEAVE", "CANCEL" };
UnitPopupMenus["RAID_TARGET_ICON"] = { "RAID_TARGET_1", "RAID_TARGET_2", "RAID_TARGET_3", "RAID_TARGET_4", "RAID_TARGET_5", "RAID_TARGET_6", "RAID_TARGET_7", "RAID_TARGET_8", "RAID_TARGET_NONE" };
-- Second level menus
UnitPopupMenus["LOOT_METHOD"] = { "FREE_FOR_ALL", "ROUND_ROBIN", "MASTER_LOOTER", "GROUP_LOOT", "NEED_BEFORE_GREED", "CANCEL" };
UnitPopupMenus["LOOT_THRESHOLD"] = { "ITEM_QUALITY2_DESC", "ITEM_QUALITY3_DESC", "ITEM_QUALITY4_DESC", "CANCEL" };
UnitPopupShown = { 1, 1, 1, 1, 1, 1, 1, 1, 1 };
UnitLootMethod = {};
UnitLootMethod["freeforall"] = { text = LOOT_FREE_FOR_ALL };
UnitLootMethod["roundrobin"] = { text = LOOT_ROUND_ROBIN };
UnitLootMethod["master"] = { text = LOOT_MASTER_LOOTER };
UnitLootMethod["group"] = { text = LOOT_GROUP_LOOT };
UnitLootMethod["needbeforegreed"] = { text = LOOT_NEED_BEFORE_GREED };
UnitPopupFrames = {
"PlayerFrameDropDown",
"TargetFrameDropDown",
"PetFrameDropDown",
"PartyMemberFrame1DropDown",
"PartyMemberFrame2DropDown",
"PartyMemberFrame3DropDown",
"PartyMemberFrame4DropDown",
};
-- The DEFERRED set (header): entries hidden until their backing arc lands.
BENILLA_UNITPOPUP_DEFERRED = {
TARGET = 1,
GUILD_PROMOTE = 1, GUILD_LEAVE = 1, RESET_INSTANCES = 1,
};
function UnitPopup_ShowMenu(dropdownMenu, which, unit, name, userData)
-- Init variables
dropdownMenu.which = which;
dropdownMenu.unit = unit;
local server = nil;
if ( unit and not name ) then
name = UnitName(unit);
end
dropdownMenu.name = name;
dropdownMenu.userData = userData;
dropdownMenu.server = server;
-- Determine which buttons should be shown or hidden
UnitPopup_HideButtons();
-- If only one menu item (the cancel button) then don't show the menu
local count = 0;
for index, value in ipairs(UnitPopupMenus[which]) do
if ( UnitPopupShown[index] == 1 and value ~= "CANCEL" ) then
count = count + 1;
end
end
if ( count < 1 ) then
return;
end
-- Reflect the live loot method/threshold into the nested rows' labels (ref).
local lootMethod = GetLootMethod();
dropdownMenu.selectedLootMethod = UnitLootMethod[lootMethod].text;
UnitPopupButtons["LOOT_METHOD"].text = dropdownMenu.selectedLootMethod;
dropdownMenu.selectedLootThreshold = getglobal("ITEM_QUALITY"..GetLootThreshold().."_DESC");
UnitPopupButtons["LOOT_THRESHOLD"].text = dropdownMenu.selectedLootThreshold;
-- A non-leader can VIEW the loot settings but not open the submenus (ref).
if ( ((GetNumPartyMembers() > 0) or (GetNumRaidMembers() > 0)) and IsPartyLeader() ) then
UnitPopupButtons["LOOT_METHOD"].nested = 1;
UnitPopupButtons["LOOT_THRESHOLD"].nested = 1;
else
UnitPopupButtons["LOOT_METHOD"].nested = nil;
UnitPopupButtons["LOOT_THRESHOLD"].nested = nil;
end
-- If level 2 dropdown
local info;
local color;
if ( UIDROPDOWNMENU_MENU_LEVEL == 2 ) then
dropdownMenu.which = UIDROPDOWNMENU_MENU_VALUE;
OPEN_DROPDOWNMENUS[UIDROPDOWNMENU_MENU_LEVEL] = {which = dropdownMenu.which, unit = dropdownMenu.unit};
for index, value in ipairs(UnitPopupMenus[UIDROPDOWNMENU_MENU_VALUE]) do
info = {};
info.text = UnitPopupButtons[value].text;
info.owner = UIDROPDOWNMENU_MENU_VALUE;
color = UnitPopupButtons[value].color;
if ( color ) then
info.textR = color.r;
info.textG = color.g;
info.textB = color.b;
end
info.icon = UnitPopupButtons[value].icon;
info.tCoordLeft = UnitPopupButtons[value].tCoordLeft;
info.tCoordRight = UnitPopupButtons[value].tCoordRight;
info.tCoordTop = UnitPopupButtons[value].tCoordTop;
info.tCoordBottom = UnitPopupButtons[value].tCoordBottom;
-- Checked conditions
if ( info.text == dropdownMenu.selectedLootMethod ) then
info.checked = 1;
elseif ( info.text == dropdownMenu.selectedLootThreshold ) then
info.checked = 1;
elseif ( string.sub(value, 1, 12) == "RAID_TARGET_" ) then
local raidTargetIndex = GetRaidTargetIndex(unit);
if ( raidTargetIndex == index ) then
info.checked = 1;
end
end
info.value = value;
info.func = UnitPopup_OnClick;
UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
end
return;
end
-- Add dropdown title
if ( unit or name ) then
info = {};
if ( name ) then
info.text = name;
else
info.text = TEXT(UNKNOWN);
end
info.isTitle = 1;
info.notCheckable = 1;
UIDropDownMenu_AddButton(info);
end
-- Set which menu is being opened
OPEN_DROPDOWNMENUS[UIDROPDOWNMENU_MENU_LEVEL] = {which = dropdownMenu.which, unit = dropdownMenu.unit};
-- Show the buttons which are used by this menu
for index, value in ipairs(UnitPopupMenus[which]) do
if ( UnitPopupShown[index] == 1 ) then
info = {};
info.text = UnitPopupButtons[value].text;
info.value = value;
info.owner = which;
info.func = UnitPopup_OnClick;
if ( not UnitPopupButtons[value].checkable ) then
info.notCheckable = 1;
end
-- Text color
if ( value == "LOOT_THRESHOLD" ) then
color = ITEM_QUALITY_COLORS[GetLootThreshold()];
if ( color ) then
info.textR = color.r;
info.textG = color.g;
info.textB = color.b;
end
else
color = UnitPopupButtons[value].color;
if ( color ) then
info.textR = color.r;
info.textG = color.g;
info.textB = color.b;
end
end
-- Icons
info.icon = UnitPopupButtons[value].icon;
info.tCoordLeft = UnitPopupButtons[value].tCoordLeft;
info.tCoordRight = UnitPopupButtons[value].tCoordRight;
info.tCoordTop = UnitPopupButtons[value].tCoordTop;
info.tCoordBottom = UnitPopupButtons[value].tCoordBottom;
-- Checked conditions
if ( string.sub(value, 1, 12) == "RAID_TARGET_" ) then
local raidTargetIndex = GetRaidTargetIndex("target");
if ( raidTargetIndex == index ) then
info.checked = 1;
end
end
if ( UnitPopupButtons[value].nested ) then
info.hasArrow = 1;
end
UIDropDownMenu_AddButton(info);
end
end
PlaySound("igMainMenuOpen");
end
function UnitPopup_HideButtons()
local dropdownMenu = getglobal(UIDROPDOWNMENU_INIT_MENU);
local inParty = 0;
if ( (GetNumPartyMembers() > 0) or (GetNumRaidMembers() > 0) ) then
inParty = 1;
end
local isLeader = 0;
if ( inParty == 1 and IsPartyLeader() ) then
isLeader = 1;
end
local isAssistant = 0;
if ( IsRaidOfficer() ) then
isAssistant = 1;
end
local canCoop = 0;
if ( dropdownMenu.unit and UnitCanCooperate("player", dropdownMenu.unit) ) then
canCoop = 1;
end
for index, value in ipairs(UnitPopupMenus[dropdownMenu.which]) do
UnitPopupShown[index] = 1;
if ( BENILLA_UNITPOPUP_DEFERRED[value] ) then
-- Hidden until its arc lands (header) — checked FIRST so the ref chain
-- below never resurrects one.
UnitPopupShown[index] = 0;
elseif ( value == "DUEL" ) then
-- Ref UnitPopup.lua l.311: shown only for a unit we can cooperate with — an
-- enemy-faction player is already attackable, so there is nothing to arrange.
if ( canCoop == 0 ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "TRADE" ) then
-- Only shown for a unit we can cooperate with (ref UnitPopup.lua l.285) — you
-- cannot trade an enemy-faction player, so the row is hidden, not just doomed.
if ( canCoop == 0 ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "FOLLOW" ) then
-- Ref UnitPopup.lua l.304-307, verbatim: you cannot follow a unit you cannot
-- cooperate with.
if ( canCoop == 0 ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "INSPECT" ) then
-- Ref UnitPopup.lua l.316-319, verbatim. These two arms were MISSING
-- (decision 1033): without them FOLLOW and INSPECT stayed shown for *every*
-- unit — including one that does not exist — so PARTY's count never fell to
-- zero, `UnitPopup_ShowMenu`'s "nothing but CANCEL, don't show the menu"
-- early-out never fired at load, and each party dropdown's OnLoad-time
-- initialize ran on to its closing `PlaySound("igMainMenuOpen")`.
if ( canCoop == 0 ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "INVITE" ) then
if ( dropdownMenu.unit ) then
if ( canCoop == 0 ) then
UnitPopupShown[index] = 0;
end
else
if ( dropdownMenu.name == UnitName("party1") or
dropdownMenu.name == UnitName("party2") or
dropdownMenu.name == UnitName("party3") or
dropdownMenu.name == UnitName("party4") or
dropdownMenu.name == UnitName("player")) then
UnitPopupShown[index] = 0;
end
end
elseif ( value == "WHISPER" ) then
if ( (dropdownMenu.unit and canCoop == 0) or (dropdownMenu.name == UnitName("player")) ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "PROMOTE" ) then
if ( (inParty == 0) or (isLeader == 0) ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "UNINVITE" ) then
if ( (inParty == 0) or (isLeader == 0) ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "LEAVE" ) then
if ( inParty == 0 ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "FREE_FOR_ALL" ) then
if ( (inParty == 0) or ((isLeader == 0) and (GetLootMethod() ~= "freeforall")) ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "ROUND_ROBIN" ) then
if ( (inParty == 0) or ((isLeader == 0) and (GetLootMethod() ~= "roundrobin")) ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "MASTER_LOOTER" ) then
if ( (inParty == 0) or ((isLeader == 0) and (GetLootMethod() ~= "master")) ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "GROUP_LOOT" ) then
if ( (inParty == 0) or ((isLeader == 0) and (GetLootMethod() ~= "group")) ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "NEED_BEFORE_GREED" ) then
if ( (inParty == 0) or ((isLeader == 0) and (GetLootMethod() ~= "needbeforegreed")) ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "LOOT_THRESHOLD" ) then
if ( inParty == 0 ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "LOOT_PROMOTE" ) then
local lootMethod, lootMaster = GetLootMethod();
if ( (inParty == 0) or (isLeader == 0) or (lootMethod ~= "master") or (lootMaster and (dropdownMenu.unit == "party"..lootMaster)) or ((dropdownMenu.unit == "player") and lootMaster and (lootMaster == 0)) ) then
UnitPopupShown[index] = 0;
end
elseif ( value == "LOOT_METHOD" ) then
if ( inParty == 0 ) then
UnitPopupShown[index] = 0;
end
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;
-- 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;
end
if ( not (dropdownMenu.which == "SELF") ) then
if ( UnitExists("target") and not UnitInParty("target") ) then
if ( UnitIsPlayer("target") and (not UnitCanCooperate("player", "target") and not UnitIsUnit("target", "player")) ) then
UnitPopupShown[index] = 0;
end
end
end
end
end
end
-- The per-frame enable/disable pass over every open unit-popup menu (ref; driven by the
-- BenillaUnitPopupDriver below — the ref hooks it off UIParent's OnUpdate).
function UnitPopup_OnUpdate(elapsed)
if ( not DropDownList1:IsVisible() ) then
return;
else
-- If none of the unitpopup frames own the open menu then return
local found = nil;
for index, value in ipairs(UnitPopupFrames) do
if ( UIDROPDOWNMENU_OPEN_MENU == value ) then
found = 1;
break;
end
end
if ( not found ) then
return;
end
end
local inParty = 0;
if ( (GetNumPartyMembers() > 0) or (GetNumRaidMembers() > 0) ) then
inParty = 1;
end
local isLeader = 0;
if ( inParty == 1 and IsPartyLeader() ) then
isLeader = 1;
end
local isAssistant = 0;
if ( IsRaidOfficer() ) then
isAssistant = 1;
end
-- Loop through all menus and enable/disable their buttons appropriately
local count;
for level, dropdownFrame in pairs(OPEN_DROPDOWNMENUS) do
if ( dropdownFrame ) then
count = 0;
for index, value in ipairs(UnitPopupMenus[dropdownFrame.which]) do
if ( UnitPopupShown[index] == 1 ) then
count = count + 1;
local enable = 1;
if ( UnitPopupButtons[value].dist > 0 and CheckInteractDistance ) then
if ( not CheckInteractDistance(dropdownFrame.unit, UnitPopupButtons[value].dist) ) then
enable = 0;
end
end
if ( value == "LEAVE" ) then
if ( inParty == 0 ) then
enable = 0;
end
elseif ( value == "INVITE" ) then
if ( inParty == 1 and (isLeader == 0 and isAssistant == 0) ) then
enable = 0;
end
elseif ( value == "UNINVITE" or value == "PROMOTE" ) then
if ( inParty == 0 or isLeader == 0 ) then
enable = 0;
end
elseif ( value == "DUEL" ) then
-- Ref UnitPopup.lua l.502-505, verbatim: greyed while either
-- party is dead or a ghost, or while we lack full control
-- (feared/charmed) — `HasFullControl` is nil-guarded like
-- CheckInteractDistance above: benilla models no loss of control
-- yet, so the clause is inert rather than an error. The row also
-- carries dist = 3, so the shared range leg gates it too.
if ( UnitIsDeadOrGhost("player") or (HasFullControl and not HasFullControl()) or UnitIsDeadOrGhost(dropdownFrame.unit) ) then
enable = 0;
end
elseif ( value == "WHISPER" ) then
if ( dropdownFrame.unit and not UnitIsConnected(dropdownFrame.unit) ) then
enable = 0;
end
elseif ( value == "LOOT_PROMOTE" ) then
local lootMethod, lootMaster = GetLootMethod();
if ( (inParty == 0) or (isLeader == 0) or (lootMethod ~= "master") ) then
enable = 0;
else
local masterName = 0;
if ( not lootMaster or (lootMaster == 0) ) then
masterName = "player";
else
masterName = "party"..lootMaster;
end
if ( dropdownFrame.unit and UnitIsUnit(dropdownFrame.unit, masterName) ) then
enable = 0;
end
end
end
if ( enable == 1 ) then
UIDropDownMenu_EnableButton(level, count+1);
else
UIDropDownMenu_DisableButton(level, count+1);
end
end
end
end
end
end
function UnitPopup_OnClick()
local dropdownFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
local button = this.value;
local unit = dropdownFrame.unit;
local name = dropdownFrame.name;
if ( button == "WHISPER" ) then
ChatFrame_SendTell(name);
elseif ( button == "INVITE" ) then
if ( unit ) then
InviteToParty(unit);
else
InviteByName(name);
end
elseif ( button == "UNINVITE" ) then
UninviteFromParty(unit);
elseif ( button == "PROMOTE" ) then
PromoteToPartyLeader(unit);
elseif ( button == "LEAVE" ) then
LeaveParty();
elseif ( button == "FREE_FOR_ALL" ) then
SetLootMethod("freeforall");
UIDropDownMenu_SetButtonText(1, 2, UnitPopupButtons[button].text);
UIDropDownMenu_Refresh(dropdownFrame, nil, 1);
elseif ( button == "ROUND_ROBIN" ) then
SetLootMethod("roundrobin");
UIDropDownMenu_SetButtonText(1, 2, UnitPopupButtons[button].text);
UIDropDownMenu_Refresh(dropdownFrame, nil, 1);
elseif ( button == "MASTER_LOOTER" ) then
SetLootMethod("master", name);
UIDropDownMenu_SetButtonText(1, 2, UnitPopupButtons[button].text);
UIDropDownMenu_Refresh(dropdownFrame, nil, 1);
elseif ( button == "GROUP_LOOT" ) then
SetLootMethod("group");
UIDropDownMenu_SetButtonText(1, 2, UnitPopupButtons[button].text);
UIDropDownMenu_Refresh(dropdownFrame, nil, 1);
elseif ( button == "NEED_BEFORE_GREED" ) then
SetLootMethod("needbeforegreed");
UIDropDownMenu_SetButtonText(1, 2, UnitPopupButtons[button].text);
UIDropDownMenu_Refresh(dropdownFrame, nil, 1);
elseif ( button == "LOOT_PROMOTE" ) then
SetLootMethod("master", name);
elseif ( button == "ITEM_QUALITY2_DESC" or button == "ITEM_QUALITY3_DESC" or button == "ITEM_QUALITY4_DESC" ) then
SetLootThreshold(this:GetID()+1);
local color = ITEM_QUALITY_COLORS[this:GetID()+1];
UIDropDownMenu_SetButtonText(1, 3, UnitPopupButtons[button].text, color.r, color.g, color.b);
elseif ( string.sub(button, 1, 12) == "RAID_TARGET_" and button ~= "RAID_TARGET_ICON" ) then
local raidTargetIndex = string.sub(button, 13);
if ( raidTargetIndex == "NONE" ) then
raidTargetIndex = 0;
end
SetRaidTargetIcon(unit, tonumber(raidTargetIndex));
elseif ( button == "DUEL" ) then
-- Challenge to a duel (decision 0633): the app resolves the unit token → player
-- guid → a cast of the duel spell (ref UnitPopup_OnClick l.559, StartDuelUnit).
StartDuelUnit(unit);
elseif ( button == "TRADE" ) then
-- Offer to trade (decision 0592 P1): the app resolves the unit token → player guid
-- → CMSG_INITIATE_TRADE (ref UnitPopup_OnClick's TRADE arm, InitiateTrade(unit)).
InitiateTrade(unit);
elseif ( button == "INSPECT" ) then
-- Open the inspect window (decision 0631) — the ref UnitPopup_OnClick's INSPECT arm
-- verbatim: InspectUnit(unit), which gates on CanInspect and sends CMSG_INSPECT.
InspectUnit(unit);
elseif ( button == "FOLLOW" ) then
-- Begin auto-following — the ref UnitPopup_OnClick's FOLLOW arm verbatim, by NAME
-- and EXACT. That `1` is the resolver's exact-only flag, and it matters: this menu
-- 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("PetPaperDollFrame");
elseif ( button == "PET_RENAME" ) then
StaticPopup_Show("RENAME_PET");
end
-- DEFERRED branches trimmed with their rows (header): TARGET,
-- GUILD_*, RESET_INSTANCES, RAID_LEADER/PROMOTE/DEMOTE/REMOVE.
PlaySound("UChatScrollButton");
end
]]></Script>
<!-- The OnUpdate driver (ref UIParent.xml l.21 runs UnitPopup_OnUpdate from UIParent's own
OnUpdate; ours has none — the DeathFrame hidden-driver pattern instead). -->
<Frame name="BenillaUnitPopupDriver" parent="UIParent" hidden="false">
<Size><AbsDimension x="1" y="1"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<Scripts>
<OnUpdate>UnitPopup_OnUpdate(arg1)</OnUpdate>
</Scripts>
</Frame>
</Ui>