Files
benilla-octo/crates/benilla-app/assets/ui/RaidFrame.xml
T
SamandClaude Fable 5 03fedf1375 the paper doll steps its feet round, and the Raid Info button behaves
- rotating the paper doll plays the reference's turn-in-place shuffle instead of spinning it like a turntable — ShuffleLeft/Right picked by direction off the same scalar `SetRotation` is handed, a 100 ms expiry, a frequency-weighted idle variation on the way back; wow-re's re-derivation caught the pair inverted in the prose (the doll was stepping into its turn), and a display with no shuffle stays standing rather than freezing mid-turn
- the doll's item effects run at full speed: the half-rate booth render (our pacing, not a cull) was skipping the emitter tick with the frame; measured off the reporter's side-by-side clip the skeletal clock was never slow, only the 30 fps cadence, so `boothHalfRate` now defaults off
- the Raid Info button follows the server's answer: `UPDATE_INSTANCE_INFO` fires per packet as the client does (the reference discards the first), so a character bound to nothing ends with a grey button over an empty panel instead of a live button over nothing; the panel's scroll bar sits centred on the trough drawn behind it
- an inspected player's gear keeps the template's bind line, stated as deliberate — there is no item object to read a bound flag from, which is what the reference prints for the same reason

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

1089 lines
66 KiB
XML

<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!--
The social window's fourth tab — the RAID pane (decision 1549). `FriendsFrame.xml`'s
header said "Tab 4 RAID is NOT built … the tab is absent until that arc lands"; this is
that arc, and that note is now corrected there.
THE SPECIFICATION is TWO reference files, because the reference splits this pane in two:
· `RaidFrame.lua` / `RaidFrame.xml` (FrameXML, 115 + 357 lines) — the pane itself: the
RAID_DESCRIPTION blurb, Convert To Raid, Raid Info and its saved-instance panel.
· `Blizzard_RaidUI` (`Blizzard_RaidUI.lua` / `.xml`, 672 + 751 lines) — a **load-on-demand
addon** carrying the 8x5 group grid, the 40 member rows, the drag-to-subgroup, Add
Member, Ready Check and the ready-check popup. `RaidFrame_LoadUI()` pulls it in on the
first `RAID_ROSTER_UPDATE`.
We ship them as ONE always-loaded file, and that is a deliberate simplification rather than
an oversight: benilla has no load-on-demand Blizzard addons, so `RaidFrame_LoadUI` would be
a function whose only job is to be a no-op. The globals, geometry, art paths, row counts
and strings below are the two files' contract — what addons bind to — and the code
answering to it is ours. Anything an addon reaches for still resolves; only the *moment* it
starts existing differs, and nothing in 1.12 can observe that (the ref's own guard,
`if ( RaidGroupFrame_Update ) then`, is kept below for exactly the addons that test it).
SCOPE (stated, not hidden):
· The **raid PULLOUTS** are not built. In the reference, dragging a group's label or a
row's class column tears off a floating `RaidPullout` window with health/mana bars and
buff/debuff tracking, created on demand and stackable. That is a window family of its
own (its own templates, its own dropdown, its own aura refresh, `CreateFrame` pooling)
and it is *not part of this tab* — it lives on UIParent once torn off. The two drag
handles it hangs from stay declared as the reference declares them (Group N's label and
the class column are Buttons), minus the `OnDragStart`/`OnDragStop` pair; the day the
pullouts land, they are three lines each.
· `RaidOptionsFrame_UpdatePartyFrames` is not built: it exists to honour the
`HIDE_PARTY_INTERFACE` uvar, and benilla has no such option yet. Its two callers in the
reference (this file's OnLoad and UIOptionsFrame) would both be calling it to say
"show the party frames", which is what already happens.
· The `SHOW_DISPELLABLE_DEBUFFS` / `SHOW_CASTABLE_BUFFS` uvars go with the pullouts —
they are read nowhere else.
WHAT THE ENGINE OWES THIS FILE (decision 1549's Rust half): `ConvertToRaid`,
`SetRaidSubgroup`, `SwapRaidSubgroup`, `UninviteFromRaid`, `PromoteByName`,
`PromoteToAssistant`, `DemoteAssistant`, `DoReadyCheck`, `ConfirmReadyCheck`,
`RequestRaidInfo`, `GetNumSavedInstances`, `GetSavedInstanceInfo`,
`SetRaidRosterSelection`/`GetRaidRosterSelection` — all in `benilla-ui/src/script/party.rs`
beside `GetRaidRosterInfo`, which already existed. The events are `RAID_ROSTER_UPDATE`,
`READY_CHECK` and `UPDATE_INSTANCE_INFO`, fired by `ui_party::feed`; the `raid1`..`raid40`
unit tokens the rows target and hover are fed there too.
-->
<Script><![CDATA[
-- ── Strings (GlobalStrings.lua, verbatim; line numbers cited) ───────────────────────────
-- `X = X or "…"` throughout, FriendsFrame.xml's rule: several of these are names the real
-- string table already owns, and in a live session it has already run.
RAID = RAID or "Raid" -- l.3256
RAID_DESCRIPTION = RAID_DESCRIPTION or "Raids are groups of more than 5 people and are typically used to defeat unique challenges at high levels.\n\n|cffffffff- Raid members cannot earn credit toward most non-raid quests. Specifically, they will not receive non-raid quest credit for killing creatures or collecting items.\n\n- Raids grant substantially less experience for defeating monsters than normal groups.\n\n- Raids allow you to overcome challenges that might otherwise be nearly impossible.|r" -- l.3262
CONVERT_TO_RAID = CONVERT_TO_RAID or "Convert To Raid" -- l.863
RAID_INFO = RAID_INFO or "Raid Info" -- l.3264
RAID_INFORMATION = RAID_INFORMATION or "Raid Information" -- l.3265
RAID_INFO_DESC = RAID_INFO_DESC or "Your saved raid instance status." -- l.3266
RESETS_IN = RESETS_IN or "Resets in" -- l.3341
INSTANCE = INSTANCE or "INSTANCE" -- l.2331
ID = ID or "ID" -- l.2303
ADD_RAID_MEMBER = ADD_RAID_MEMBER or "Add Member" -- l.40
ADD_RAIDMEMBER_LABEL = ADD_RAIDMEMBER_LABEL or "Add Raid Member:" -- l.39
READY_CHECK = READY_CHECK or "Ready Check" -- l.3301
READY_CHECK_MESSAGE = READY_CHECK_MESSAGE or "%s has initiated a ready check.\n|cffffffffAre you ready?|r" -- l.3302
READY_CHECK_YOU_WERE_AFK = READY_CHECK_YOU_WERE_AFK or "You were AFK for a ready check" -- l.3305
RAID_LEADER_TOKEN = RAID_LEADER_TOKEN or "(L)" -- l.3276
RAID_ASSISTANT_TOKEN = RAID_ASSISTANT_TOKEN or "(A)" -- l.3259
GROUP = GROUP or "Group" -- l.2029
EMPTY = EMPTY or "Empty" -- l.1433
UNKNOWN = UNKNOWN or "Unknown" -- l.4443
-- ── The counts (ref RaidFrame.lua l.2-5) ────────────────────────────────────────────────
-- Published globals, not local constants: `MAX_RAID_MEMBERS` alone is named by a large
-- slice of the raid-addon corpus, which sweeps `1, MAX_RAID_MEMBERS` rather than
-- `1, GetNumRaidMembers()` precisely so a shrinking roster still clears its stale rows.
MAX_RAID_MEMBERS = 40
NUM_RAID_GROUPS = 8
MEMBERS_PER_RAID_GROUP = 5
MAX_RAID_INFOS = 10
-- The drag's two pieces of state, ref-named (l.2-3): the row currently under the cursor,
-- and the slot it is hovering. Globals because the ref publishes them and because the
-- pane's OnUpdate, its OnHide and the row's own handlers all three read them.
MOVING_RAID_MEMBER = nil
TARGET_RAID_SLOT = nil
-- `RAID_SUBGROUP_LISTS[subgroupNumber]` and `[classFileName]` — the row indices in each
-- bucket, rebuilt on every repaint (ref l.4, filled in `RaidGroupFrame_Update`). Nothing
-- in this file reads them: they are the PULLOUTS' input, and the pullouts are the stated
-- deferral above. They are filled anyway because they are also the published surface a
-- raid addon reads to answer "who is in group 3 / who are the priests" without walking the
-- roster itself, and filling them is four lines inside a loop that already has the answer.
RAID_SUBGROUP_LISTS = {}
local function set_shown(widget, on)
if on then widget:Show() else widget:Hide() end
end
local function set_enabled(widget, on)
if on then widget:Enable() else widget:Disable() end
end
-- ── The pane (ref RaidFrame_OnLoad l.7-27 / RaidFrame_OnEvent l.29-58) ──────────────────
function RaidFrame_OnLoad()
this:RegisterEvent("PLAYER_LOGIN")
this:RegisterEvent("RAID_ROSTER_UPDATE")
this:RegisterEvent("UPDATE_INSTANCE_INFO")
this:RegisterEvent("PARTY_MEMBERS_CHANGED")
this:RegisterEvent("PARTY_LEADER_CHANGED")
this:RegisterEvent("PLAYER_ENTERING_WORLD")
-- The grid's two per-unit repaints (ref `RaidGroupFrame_OnLoad` l.9-10, which adds
-- them to this same frame once the addon loads). They are what keeps a death or a
-- ding off `RAID_ROSTER_UPDATE`.
this:RegisterEvent("UNIT_LEVEL")
this:RegisterEvent("UNIT_HEALTH")
-- Ref l.26: the button is dead until the server has answered ONCE. `hasRaidInfo` is
-- the ref's "the first UPDATE_INSTANCE_INFO is the seeding one" latch; see OnEvent.
RaidFrame.hasRaidInfo = nil
RaidFrame_Update()
end
function RaidFrame_OnEvent(event)
if event == "PLAYER_ENTERING_WORLD" then
RequestRaidInfo()
end
if event == "PLAYER_LOGIN" then
if GetNumRaidMembers() > 0 then
RaidFrame_Update()
end
end
if event == "RAID_ROSTER_UPDATE" then
RaidFrame_Update()
end
if event == "UPDATE_INSTANCE_INFO" then
-- Ref l.42-48, transcribed including its shape: the FIRST answer only arms the
-- latch and returns, so the button's state is decided from the second onward. The
-- reference's reason is that its first `UPDATE_INSTANCE_INFO` fires before the
-- data has landed; ours fires with the data, so the latch costs one extra
-- `RequestRaidInfo` round trip and nothing else. Kept because an addon that
-- watches `RaidFrame.hasRaidInfo` — several raid-lockout addons do — is watching a
-- documented two-state field, and collapsing it to one state is a behaviour
-- change we would be making silently.
if not RaidFrame.hasRaidInfo then
RaidFrame.hasRaidInfo = 1
return
end
set_enabled(RaidFrameRaidInfoButton, GetNumSavedInstances() > 0)
RaidInfoFrame_Update()
end
if event == "PARTY_MEMBERS_CHANGED" or event == "PARTY_LEADER_CHANGED" then
RaidFrame_Update()
end
end
-- Ref RaidFrame_Update l.60-78. Out of a raid the pane is the blurb and one button; in one
-- it is the grid. The two are mutually exclusive, which is why nothing here reads both.
function RaidFrame_Update()
if GetNumRaidMembers() == 0 then
RaidFrameConvertToRaidButton:Show()
-- Ref l.64-68: convertible only with a party to convert AND the lead of it.
-- `GetPartyMember(1)` rather than `GetNumPartyMembers() > 0` is the ref's own
-- spelling, and they answer the same thing.
set_enabled(RaidFrameConvertToRaidButton, GetPartyMember(1) and IsPartyLeader())
RaidFrameRaidDescription:Show()
else
RaidFrameConvertToRaidButton:Hide()
RaidFrameRaidDescription:Hide()
end
-- Ref l.75-77 keeps this behind an existence test because the grid lives in a
-- load-on-demand addon there. Ours is in this file, so the test always passes — kept
-- verbatim anyway: an addon that replaces `RaidGroupFrame_Update` with nil to suppress
-- the grid is relying on this guard, and the guard costs nothing.
if RaidGroupFrame_Update then
RaidGroupFrame_Update()
end
end
-- ── The grid (ref Blizzard_RaidUI.lua RaidGroupFrame_Update l.45-176) ───────────────────
function RaidGroupFrame_Update()
local numRaidMembers = GetNumRaidMembers()
for i = 1, NUM_RAID_GROUPS do
local group = getglobal("RaidGroup" .. i)
set_shown(group, numRaidMembers > 0)
-- The fill cursor: which of this group's five slots the next member lands in.
group.nextIndex = 1
end
RaidFrameReadyCheckButton_Update()
RaidFrameAddMemberButton_Update()
-- Ref l.60-62: Raid Info shares its row with Ready Check, so it steps aside when Ready
-- Check is on screen. Only ever re-anchored one way — the ref never puts it back,
-- because Ready Check showing and the raid existing are the same condition for the
-- leader, and a non-leader never sees either.
if RaidFrameReadyCheckButton:IsShown() then
RaidFrameRaidInfoButton:SetPoint("LEFT", "RaidFrameReadyCheckButton", "RIGHT", 2, 0)
end
RaidGroup_ResetSlotButtons()
-- Rebuild the published buckets (see RAID_SUBGROUP_LISTS above). One per subgroup,
-- and one per CLASS token — the ref keys both into the same table, which is safe
-- because subgroup keys are numbers and class keys are strings.
RAID_SUBGROUP_LISTS = {}
for i = 1, NUM_RAID_GROUPS do
RAID_SUBGROUP_LISTS[i] = {}
end
for token in pairs(RAID_CLASS_COLORS) do
RAID_SUBGROUP_LISTS[token] = {}
end
for i = 1, MAX_RAID_MEMBERS do
local button = getglobal("RaidGroupButton" .. i)
if i <= numRaidMembers then
local name, rank, subgroup, level, class, fileName, zone, online, isDead =
GetRaidRosterInfo(i)
local group = getglobal("RaidGroup" .. subgroup)
-- Ref l.90's own comment: "To prevent errors when the server hiccups". A
-- sixth member in a subgroup is not representable, so the row simply does not
-- seat — better a missing row than an overlapping one.
if group and group.nextIndex <= MEMBERS_PER_RAID_GROUP then
button.id = i
button.name = name
button.unit = "raid" .. i
button.class = fileName
button.subgroup = subgroup
RaidGroupButton_SetRow(button, name, rank, level, class, fileName,
online, isDead)
table.insert(RAID_SUBGROUP_LISTS[subgroup], i)
if fileName and RAID_SUBGROUP_LISTS[fileName] then
table.insert(RAID_SUBGROUP_LISTS[fileName], i)
end
local slotName = "RaidGroup" .. subgroup .. "Slot" .. group.nextIndex
-- Ref l.160: the row being DRAGGED keeps the cursor, not its slot — the
-- repaint must not yank it out from under the hand holding it.
if MOVING_RAID_MEMBER ~= button then
button:SetPoint("TOPLEFT", slotName, "TOPLEFT", 0, 0)
end
-- Both directions of the row<->slot link: the row remembers where to
-- spring back to on a cancelled drag, the slot remembers who is in it so
-- a drop onto an occupied slot knows to SWAP rather than to move.
button.slot = slotName
getglobal(slotName).button = button:GetName()
group.nextIndex = group.nextIndex + 1
button:SetID(i)
button:Show()
else
button:Hide()
end
else
button:Hide()
end
end
end
-- One row's text and colour (ref l.96-152, lifted out of the sweep above so the two
-- per-unit event handlers below can reuse the colour half without repainting everything).
function RaidGroupButton_SetRow(button, name, rank, level, class, fileName, online, isDead)
local prefix = button:GetName()
if level == 0 then
level = ""
end
if not name then
name = UNKNOWN
end
getglobal(prefix .. "Name"):SetText(name)
getglobal(prefix .. "Class"):SetText(class or "")
getglobal(prefix .. "Level"):SetText(level or "")
-- The rank tokens (ref l.146-152): "(L)" for the leader, "(A)" for an assistant.
local token = ""
if rank == 2 then
token = RAID_LEADER_TOKEN
elseif rank == 1 then
token = RAID_ASSISTANT_TOKEN
end
getglobal(prefix .. "Rank"):SetText(token)
RaidGroupButton_SetRowColor(button, fileName, online, isDead)
end
-- The colour law, ONE function because the reference repeats it twice verbatim
-- (`RaidGroupFrame_Update` l.130-145 and `RaidGroupFrame_UpdateHealth` l.184-199) and the
-- two must never disagree: offline is grey and wins over everything, then dead is red,
-- then the class colour. All three columns take the same colour together.
function RaidGroupButton_SetRowColor(button, fileName, online, isDead)
local prefix = button:GetName()
local color
if not online then
color = GRAY_FONT_COLOR
elseif isDead then
color = RED_FONT_COLOR
else
color = RAID_CLASS_COLORS[fileName]
end
-- A class with no colour (an unresolved row) leaves the columns as they were, which is
-- the reference's own behaviour: its `if ( color )` guard skips the write rather than
-- falling back to white.
if not color then
return
end
getglobal(prefix .. "Name"):SetTextColor(color.r, color.g, color.b)
getglobal(prefix .. "Class"):SetTextColor(color.r, color.g, color.b)
getglobal(prefix .. "Level"):SetTextColor(color.r, color.g, color.b)
end
-- Ref l.178-181 / l.183-215: the two per-unit repaints. These exist so a level ding or a
-- death does NOT go through `RAID_ROSTER_UPDATE` — a 40-row rebuild per point of damage
-- taken is what firing the roster event on health would cost.
function RaidGroupFrame_UpdateLevel(id)
getglobal("RaidGroupButton" .. id .. "Level"):SetText(UnitLevel("raid" .. id))
end
function RaidGroupFrame_UpdateHealth(id)
local button = getglobal("RaidGroupButton" .. id)
local name, rank, subgroup, level, class, fileName, zone, online, isDead =
GetRaidRosterInfo(id)
RaidGroupButton_SetRowColor(button, fileName, online, isDead)
end
-- The `raidN` token → its row index, or nil for anything else. `UNIT_LEVEL`/`UNIT_HEALTH`
-- arrive for every unit in the game, so this is also the filter.
--
-- `string.find` with a capture rather than `string.match`: this VM is deliberately 5.0
-- (decision 1194), and `string.match` is one of the 5.1-only members that layer REMOVES —
-- calling it here would be the same mistake the addons that branch on `gmatch or gfind`
-- catch us in. The reference spells the same test as `gsub("raid([0-9]+)", "%1")` and
-- reads its substitution count; a capture says it once.
local function raid_index_of(unit)
if not unit then
return nil
end
local _, _, digits = string.find(unit, "^raid(%d+)$")
return tonumber(digits)
end
function RaidGroupFrame_OnEvent(event, unit)
RaidFrame_OnEvent(event)
if event == "UNIT_LEVEL" or event == "UNIT_HEALTH" then
local id = raid_index_of(unit)
-- Only a row that is actually on screen: an index past the roster names a hidden
-- button whose FontStrings still exist, and writing to it would be invisible work.
if id and id <= GetNumRaidMembers() then
if event == "UNIT_LEVEL" then
RaidGroupFrame_UpdateLevel(id)
else
RaidGroupFrame_UpdateHealth(id)
end
end
end
if event == "PLAYER_ENTERING_WORLD" then
RaidFrameReadyCheckButton_Update()
RaidFrameAddMemberButton_Update()
end
end
-- Ref l.17-22: a drag in flight when the pane closes is a drag that has to be finished,
-- or the row stays parked under a cursor that is no longer over anything.
function RaidGroupFrame_OnHide()
if MOVING_RAID_MEMBER then
RaidGroupButton_OnDragStop(MOVING_RAID_MEMBER)
end
end
-- ── The drag (ref l.217-272) ────────────────────────────────────────────────────────────
--
-- Hover detection is a per-frame sweep of all 40 slots rather than the slots' own
-- OnEnter/OnLeave, and that is the reference's design for a reason worth keeping: the row
-- being dragged sits UNDER the cursor, so it — not the slot — is what the pointer is over,
-- and no slot would ever get an OnEnter.
function RaidGroupFrame_OnUpdate()
if not MOVING_RAID_MEMBER then
return
end
TARGET_RAID_SLOT = nil
for i = 1, NUM_RAID_GROUPS do
for j = 1, MEMBERS_PER_RAID_GROUP do
local slot = getglobal("RaidGroup" .. i .. "Slot" .. j)
if MouseIsOver(slot) then
slot:LockHighlight()
TARGET_RAID_SLOT = slot
else
slot:UnlockHighlight()
end
end
end
end
function RaidGroupButton_OnDragStart()
-- Leader-only, ref l.234. A non-leader's drag never starts, so there is nothing to
-- cancel and nothing to send.
if not IsRaidLeader() then
return
end
local cursorX, cursorY = GetCursorPosition()
this:ClearAllPoints()
-- The ref multiplies the cursor by `GetScreenWidthScale()`/`GetScreenHeightScale()`
-- here because 1.12 hands Lua screen pixels against a 1024x768 reference UI. benilla's
-- UI space is 1:1 logical px and `GetCursorPosition` already reports in the space
-- `SetPoint` offsets are read in, so the multiply would place the row at ~1.5x the
-- cursor on a 1600-wide window. Same divergence UIParent.xml's `MouseIsOver` records
-- for `GetEffectiveScale`; the two scale getters exist there for the addons that call
-- them.
this:SetPoint("CENTER", "UIParent", "BOTTOMLEFT", cursorX, cursorY)
this:StartMoving()
MOVING_RAID_MEMBER = this
SetRaidRosterSelection(this.id)
end
function RaidGroupButton_OnDragStop(raidButton)
if not IsRaidLeader() then
return
end
raidButton = raidButton or this
raidButton:StopMovingOrSizing()
MOVING_RAID_MEMBER = nil
-- Three landings, ref l.256-272. Dropped on another group's slot: SWAP if somebody is
-- in it, MOVE if it is empty. Dropped anywhere else — including this row's own group —
-- the row springs back to the slot it came from and nothing goes on the wire.
local slot = TARGET_RAID_SLOT
if slot then
slot:UnlockHighlight()
end
if slot and slot:GetParent():GetID() ~= raidButton.subgroup then
if slot.button then
SwapRaidSubgroup(raidButton:GetID(), getglobal(slot.button):GetID())
else
-- Seat it optimistically in the target group's next free slot so the row does
-- not visibly snap home and back while the server's echo is in flight.
local parent = slot:GetParent()
raidButton:SetPoint("TOPLEFT",
parent:GetName() .. "Slot" .. parent.nextIndex, "TOPLEFT", 0, 0)
SetRaidSubgroup(raidButton:GetID(), parent:GetID())
end
else
raidButton:SetPoint("TOPLEFT", raidButton.slot, "TOPLEFT", 0, 0)
end
TARGET_RAID_SLOT = nil
end
-- ── The row's click and hover (ref l.274-311) ───────────────────────────────────────────
function RaidGroupButton_OnClick(button)
if button == "LeftButton" then
local unit = "raid" .. this.id
if SpellIsTargeting() then
SpellTargetUnit(unit)
elseif CursorHasItem() then
DropItemOnUnit(unit)
else
TargetUnit(unit)
end
else
HideDropDownMenu(1)
if this.id and this.name then
BenillaRaidDropDown.initialize = RaidFrameDropDown_Initialize
BenillaRaidDropDown.displayMode = "MENU"
BenillaRaidDropDown.unit = this.unit
BenillaRaidDropDown.rowName = this.name
BenillaRaidDropDown.rowId = this.id
ToggleDropDownMenu(1, nil, BenillaRaidDropDown, "cursor")
end
end
end
-- The RAID menu's fourth argument is the ROW INDEX, and it is load-bearing rather than
-- decorative: every one of the menu's four rows re-reads `GetRaidRosterInfo(userData)` to
-- decide whether to show itself, and `UninviteFromRaid` acts on it directly.
function RaidFrameDropDown_Initialize()
UnitPopup_ShowMenu(getglobal(UIDROPDOWNMENU_OPEN_MENU), "RAID",
BenillaRaidDropDown.unit, BenillaRaidDropDown.rowName, BenillaRaidDropDown.rowId)
end
function RaidGroupButton_OnEnter()
if SpellIsTargeting() then
if SpellCanTargetUnit(this.unit) then
SetCursor("CAST_CURSOR")
else
SetCursor("CAST_ERROR_CURSOR")
end
end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
if GameTooltip:SetUnit(this.unit) then
getglobal("GameTooltipTextLeft1"):SetTextColor(GameTooltip_UnitColor(this.unit))
end
end
-- Ref l.393-399: every slot forgets its occupant before a repaint re-seats them, so a
-- member who left does not leave a stale name behind for the drop code to swap with.
function RaidGroup_ResetSlotButtons()
for i = 1, NUM_RAID_GROUPS do
for j = 1, MEMBERS_PER_RAID_GROUP do
getglobal("RaidGroup" .. i .. "Slot" .. j).button = nil
end
end
end
-- ── The two managing buttons (ref l.646-668) ────────────────────────────────────────────
function RaidFrameReadyCheckButton_Update()
set_shown(RaidFrameReadyCheckButton, GetNumRaidMembers() > 0 and IsRaidLeader())
end
function RaidFrameAddMemberButton_Update()
local inRaid = GetNumRaidMembers() > 0
set_shown(RaidFrameAddMemberButton, inRaid)
if inRaid then
-- Ref l.662-666. `IsRaidOfficer()` answers nil in benilla today — the binding's
-- own body has not been carved (`0x4bb910`, and `script/party.rs` carries why
-- guessing it was refused), so an ASSISTANT sees this button disabled where the
-- reference enables it. Stated rather than papered over: the leader half is exact,
-- and the day that binding lands this line is already right.
set_enabled(RaidFrameAddMemberButton, IsRaidLeader() or IsRaidOfficer())
end
end
-- ── The saved-instance panel (ref RaidFrame.lua RaidInfoFrame_Update l.90-115) ──────────
function RaidInfoFrame_Update()
local savedInstances = GetNumSavedInstances()
for i = 1, MAX_RAID_INFOS do
local row = getglobal("RaidInfoInstance" .. i)
if i <= savedInstances then
local instanceName, instanceID, instanceReset = GetSavedInstanceInfo(i)
getglobal("RaidInfoInstance" .. i .. "Name"):SetText(instanceName)
getglobal("RaidInfoInstance" .. i .. "ID"):SetText(instanceID)
getglobal("RaidInfoInstance" .. i .. "Reset"):SetText(
RESETS_IN .. " " .. SecondsToTime(instanceReset))
row:Show()
else
row:Hide()
end
end
-- Ref l.108-113: four rows fit, so the bar appears at five. Unlike the reference this
-- runs on the empty case too — its early `if ( savedInstances > 0 )` leaves a stale
-- scroll bar standing when the last lockout expires, which is a bug rather than a
-- behaviour (nothing can observe the bar's absence except by seeing it wrongly there).
local scrollable = savedInstances > 4
set_shown(RaidInfoScrollFrameScrollBar, scrollable)
if scrollable then
-- **The bar has to move to the trough.** `UIPanelScrollFrameTemplate` seats it for
-- a bare ScrollFrame: 6 px out and 16 px down, so its up-arrow fits inside the
-- frame's own span. This panel draws an ornate trough behind it instead
-- (`RaidInfoScrollFrameTop`/`Bottom`, 30 px wide from +1), and the template's seat
-- leaves the 16-wide bar 2 px left of that trough's centre with its arrow floating
-- 13 px below the cap. The ref re-points it here for the same reason and never
-- puts it back — the value never varies, so setting it on each show is idempotent.
RaidInfoScrollFrameScrollBar:SetPoint(
"TOPLEFT", "RaidInfoScrollFrame", "TOPRIGHT", 8, -3)
end
RaidInfoScrollFrame:UpdateScrollChildRect()
end
-- ── The ready check (ref Blizzard_RaidUI.lua l.615-645) ─────────────────────────────────
--
-- Opened by UIParent's `READY_CHECK` arm — the engine event (`0x218`) the
-- `MSG_RAID_READY_CHECK` handler fires. The 30 s deadline is the reference's own
-- (`0xb713f4 = clock + 0x7530`, wow-re `system/ui/ui.md`); it lives in the frame's
-- OnUpdate here because that is where the reference keeps it too.
function ShowReadyCheck()
local name, leader
for i = 1, MAX_RAID_MEMBERS do
local rowName, rank = GetRaidRosterInfo(i)
if rowName then
name = rowName
if rank == 2 then
leader = "raid" .. i
break
end
end
end
-- The ref passes `leader` to SetPortraitTexture unguarded and would take a nil there
-- if no rank-2 row existed; ours does not, because a raid whose leader is out of the
-- roster (the moment after a promote, before the echo) is reachable and a raise in an
-- event handler is a script error the player sees.
if leader then
SetPortraitTexture(ReadyCheckPortrait, leader)
end
ReadyCheckFrameText:SetText(format(READY_CHECK_MESSAGE, name or UNKNOWN))
ReadyCheckFrame:Show()
ReadyCheckFrame.timer = 30
PlaySound("ReadyCheck")
end
function ReadyCheck_OnUpdate(elapsed)
if not ReadyCheckFrame.timer then
return
end
ReadyCheckFrame.timer = ReadyCheckFrame.timer - elapsed
if ReadyCheckFrame.timer < 0 then
-- Timing out is an answer too: you were away, and the raid is told so.
local info = ChatTypeInfo["SYSTEM"]
DEFAULT_CHAT_FRAME:AddMessage(READY_CHECK_YOU_WERE_AFK, info.r, info.g, info.b, info.id)
ReadyCheckFrame.timer = nil
ReadyCheckFrame:Hide()
end
end
-- ── Add Member's dialog (ref StaticPopup.lua l.871-895) ─────────────────────────────────
StaticPopupDialogs["ADD_RAIDMEMBER"] = {
text = ADD_RAIDMEMBER_LABEL,
button1 = "Accept",
button2 = "Cancel",
hasEditBox = 1,
maxLetters = 12,
timeout = 0,
hideOnEscape = 1,
OnAccept = function()
InviteByName(getglobal("StaticPopup1EditBox"):GetText())
end,
OnShow = function()
getglobal("StaticPopup1EditBox"):SetFocus()
end,
EditBoxOnEnterPressed = function()
InviteByName(getglobal("StaticPopup1EditBox"):GetText())
StaticPopup_Hide("ADD_RAIDMEMBER")
end,
}
]]></Script>
<!-- ── One member row (ref Blizzard_RaidUI.xml l.4-116) ───────────────────────────────────
156x14 over the GroupButton art. `movable` + `clampedToScreen` are what let it be torn
out of its slot and dragged; the drop code puts it back. -->
<Button name="RaidGroupButtonTemplate" movable="true" clampedToScreen="true" virtual="true">
<Size><AbsDimension x="156" y="14"/></Size>
<Layers>
<Layer level="ARTWORK">
<FontString name="$parentRank" inherits="GameFontNormalSmall" justifyH="LEFT">
<Size><AbsDimension x="18" y="8"/></Size>
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="1" y="1"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="$parentName" inherits="GameFontNormalSmall" justifyH="LEFT">
<Size><AbsDimension x="60" y="8"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="$parentRank" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="$parentLevel" inherits="GameFontNormalSmall" justifyH="CENTER">
<Size><AbsDimension x="22" y="8"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="$parentName" relativePoint="RIGHT"><Offset><AbsDimension x="5" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The class column is a Button in the reference because it is the CLASS PULLOUT's
drag handle (see the header's scope note). Kept as a Button so the shape and the
global are the reference's; its `RegisterForDrag` pair is what is missing. -->
<Button name="$parentClass">
<Size><AbsDimension x="50" y="8"/></Size>
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="110" y="1"/></Offset></Anchor></Anchors>
<ButtonText>
<Size><AbsDimension x="50" y="8"/></Size>
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</ButtonText>
<NormalFont inherits="GameFontNormalSmall" justifyH="LEFT"/>
<HighlightFont inherits="GameFontHighlightSmall" justifyH="LEFT"/>
</Button>
</Frames>
<Scripts>
<!-- The +2 frame levels are the reference's and they are load-bearing: the row is a
child of the pane while its slot is a child of a group frame one level deeper, so
without the raise the row would draw UNDER the slot it sits in. -->
<OnLoad>
this:SetFrameLevel(this:GetFrameLevel() + 2)
getglobal(this:GetName() .. "Class"):SetFrameLevel(this:GetFrameLevel() + 2)
this:RegisterForDrag("LeftButton")
this:RegisterForClicks("LeftButtonUp", "RightButtonUp")
</OnLoad>
<OnClick>RaidGroupButton_OnClick(arg1)</OnClick>
<OnDragStart>RaidGroupButton_OnDragStart()</OnDragStart>
<OnDragStop>RaidGroupButton_OnDragStop()</OnDragStop>
<OnEnter>RaidGroupButton_OnEnter()</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
<NormalTexture file="Interface\RaidFrame\UI-RaidFrame-GroupButton">
<TexCoords left="0" right="0.640625" top="0" bottom="0.4375"/>
</NormalTexture>
<HighlightTexture file="Interface\RaidFrame\UI-RaidFrame-GroupButton" alphaMode="ADD">
<TexCoords left="0" right="0.640625" top="0.46875" bottom="0.90625"/>
</HighlightTexture>
</Button>
<!-- One seat in a group (ref l.117-137). It has no click handler at all: it is a drop target
and an "Empty" label, and its highlight is driven by the drag sweep, never by hovering. -->
<Button name="RaidGroupSlotTemplate" virtual="true">
<Size><AbsDimension x="156" y="14"/></Size>
<Layers>
<Layer level="BACKGROUND">
<FontString inherits="GameFontDarkGraySmall" text="EMPTY">
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<HighlightTexture file="Interface\RaidFrame\UI-RaidFrame-GroupButton" alphaMode="ADD">
<TexCoords left="0" right="0.640625" top="0.46875" bottom="0.90625"/>
</HighlightTexture>
</Button>
<!-- One of the eight subgroups (ref l.138-235): a labelled outline over five seats. -->
<Frame name="RaidGroupTemplate" virtual="true">
<Size><AbsDimension x="162" y="80"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\RaidFrame\UI-RaidFrame-GroupOutline">
<Size><AbsDimension x="162" y="80"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<TexCoords left="0" right="0.6640625" top="0" bottom="0.625"/>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- "Group N". A Button, and the GROUP PULLOUT's drag handle in the reference — the
same deferral the class column carries. -->
<Button name="$parentLabel">
<Size><AbsDimension x="156" y="14"/></Size>
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="14"/></Offset></Anchor></Anchors>
<NormalFont inherits="GameFontNormalSmall"/>
<HighlightFont inherits="GameFontHighlightSmall"/>
</Button>
<Button name="$parentSlot1" inherits="RaidGroupSlotTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="3" y="-3"/></Offset></Anchor></Anchors>
</Button>
<Button name="$parentSlot2" inherits="RaidGroupSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentSlot1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
</Button>
<Button name="$parentSlot3" inherits="RaidGroupSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentSlot2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
</Button>
<Button name="$parentSlot4" inherits="RaidGroupSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentSlot3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
</Button>
<Button name="$parentSlot5" inherits="RaidGroupSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentSlot4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
</Button>
</Frames>
<Scripts>
<OnLoad>getglobal(this:GetName() .. "Label"):SetText(GROUP .. " " .. this:GetID())</OnLoad>
</Scripts>
</Frame>
<!-- One saved-lockout row (ref RaidFrame.xml l.4-42). -->
<Frame name="RaidInfoInstanceTemplate" virtual="true">
<Size><AbsDimension x="220" y="40"/></Size>
<Layers>
<Layer level="BACKGROUND">
<FontString name="$parentName" inherits="GameFontNormal">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="-2" y="-10"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="$parentID" inherits="GameFontDisableSmall">
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="-28" y="-12"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="$parentReset" inherits="GameFontHighlightSmall">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentName" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<!-- ── The pane itself (ref RaidFrame.xml l.44-356 + Blizzard_RaidUI.xml's RaidFrame children)
Parented to FriendsFrame and filling it, exactly as the reference does — which is what
makes `FriendsFrame_ShowSubFrame("RaidFrame")` the only thing tab 4 has to do. -->
<Frame name="RaidFrame" parent="FriendsFrame" setAllPoints="true" hidden="true">
<Layers>
<Layer level="ARTWORK">
<FontString name="RaidFrameRaidDescription" inherits="GameFontNormal" text="RAID_DESCRIPTION" justifyH="LEFT">
<Size><AbsDimension x="300" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="29" y="-85"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The row menu's host, the FriendsFrame's own pattern (`BenillaFriendsDropDown`). The
reference reuses `FriendsDropDown` for this; ours is its own frame so the raid
menu's userData cannot collide with a friends-row menu left half-open. -->
<Frame name="BenillaRaidDropDown" inherits="UIDropDownMenuTemplate"/>
<Button name="RaidFrameConvertToRaidButton" inherits="UIPanelButtonTemplate" text="CONVERT_TO_RAID">
<Size><AbsDimension x="115" y="21"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="72" y="-37"/></Offset></Anchor></Anchors>
<Scripts><OnClick>ConvertToRaid()</OnClick></Scripts>
<NormalFont inherits="GameFontNormalSmall"/>
<HighlightFont inherits="GameFontHighlightSmall"/>
<DisabledFont inherits="GameFontDisableSmall"/>
</Button>
<!-- Add Member sits where Convert To Raid does, because the two are never on screen
together: one is the not-in-a-raid state and the other is the in-a-raid one. -->
<Button name="RaidFrameAddMemberButton" inherits="UIPanelButtonTemplate" text="ADD_RAID_MEMBER" hidden="true">
<Size><AbsDimension x="90" y="21"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="71" y="-37"/></Offset></Anchor></Anchors>
<Scripts>
<!-- Ref: a friendly player target is invited straight away, otherwise the
name-entry dialog opens — the Add Friend button's own shape. -->
<OnClick>
if UnitCanCooperate("player", "target") then
InviteByName(UnitName("target"))
else
StaticPopup_Show("ADD_RAIDMEMBER")
end
</OnClick>
</Scripts>
<NormalFont inherits="GameFontNormalSmall"/>
<HighlightFont inherits="GameFontHighlightSmall"/>
<DisabledFont inherits="GameFontDisableSmall"/>
</Button>
<Button name="RaidFrameReadyCheckButton" inherits="UIPanelButtonTemplate" text="READY_CHECK" hidden="true">
<Size><AbsDimension x="90" y="21"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="RaidFrameAddMemberButton" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnClick>DoReadyCheck()</OnClick></Scripts>
<NormalFont inherits="GameFontNormalSmall"/>
<HighlightFont inherits="GameFontHighlightSmall"/>
<DisabledFont inherits="GameFontDisableSmall"/>
</Button>
<Button name="RaidFrameRaidInfoButton" inherits="UIPanelButtonTemplate" text="RAID_INFO">
<Size><AbsDimension x="90" y="21"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="RaidFrameConvertToRaidButton" relativePoint="RIGHT"><Offset><AbsDimension x="65" y="0"/></Offset></Anchor></Anchors>
<Scripts>
<OnClick>
if RaidInfoFrame:IsShown() then
RaidInfoFrame:Hide()
else
RaidInfoFrame:Show()
end
</OnClick>
</Scripts>
<NormalFont inherits="GameFontNormalSmall"/>
<HighlightFont inherits="GameFontHighlightSmall"/>
<DisabledFont inherits="GameFontDisableSmall"/>
</Button>
<!-- The lockout panel — a dialog-bordered flyout off the window's right edge. -->
<Frame name="RaidInfoFrame" enableMouse="true" hidden="true">
<Size><AbsDimension x="270" y="250"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-33" y="-28"/></Offset></Anchor></Anchors>
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
<BackgroundInsets><AbsInset left="11" right="12" top="12" bottom="11"/></BackgroundInsets>
<TileSize><AbsValue val="32"/></TileSize>
<EdgeSize><AbsValue val="32"/></EdgeSize>
</Backdrop>
<Layers>
<Layer level="ARTWORK">
<Texture name="RaidInfoDetailHeader" file="Interface\RaidFrame\UI-RaidInfo-Header">
<Size><AbsDimension x="250" y="64"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="11" y="-10"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
<Layer level="OVERLAY">
<Texture name="RaidInfoDetailCorner" file="Interface\DialogFrame\UI-DialogBox-Corner">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="-6" y="-7"/></Offset></Anchor></Anchors>
</Texture>
<FontString name="RaidInfoHeader" inherits="GameFontNormal" text="RAID_INFORMATION">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="20" y="-15"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="RaidInfoSubheader" inherits="GameFontHighlightSmall" text="RAID_INFO_DESC">
<Anchors><Anchor point="TOPLEFT" relativeTo="RaidInfoHeader" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="RaidInfoInstanceLabel" inherits="GameFontDisableSmall" text="INSTANCE">
<Anchors><Anchor point="TOPLEFT" relativeTo="RaidInfoSubheader" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-13"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="RaidInfoIDLabel" inherits="GameFontDisableSmall" text="ID">
<Anchors><Anchor point="LEFT" relativeTo="RaidInfoInstanceLabel" relativePoint="RIGHT"><Offset><AbsDimension x="110" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- No UIPanelCloseButton template ships in this house (FriendsFrame's own
close button says the same); the minimize-button art is the house form. -->
<Button name="RaidInfoCloseButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="-3" y="-3"/></Offset></Anchor></Anchors>
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
<Scripts><OnClick>RaidInfoFrame:Hide()</OnClick></Scripts>
</Button>
<ScrollFrame name="RaidInfoScrollFrame" inherits="UIPanelScrollFrameTemplate">
<Size><AbsDimension x="210" y="173"/></Size>
<Anchors><Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-39" y="12"/></Offset></Anchor></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentTop" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
<Size><AbsDimension x="30" y="125"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="1" y="16"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="0.484375" top="0" bottom="0.5"/>
</Texture>
<Texture name="$parentBottom" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
<Size><AbsDimension x="30" y="105"/></Size>
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="1" y="-2"/></Offset></Anchor></Anchors>
<TexCoords left="0.515625" right="1.0" top="0" bottom="0.4140625"/>
</Texture>
</Layer>
</Layers>
<ScrollChild>
<Frame name="RaidInfoScrollChildFrame" enableMouse="true">
<Size><AbsDimension x="223" y="40"/></Size>
<Frames>
<Frame name="RaidInfoInstance1" inherits="RaidInfoInstanceTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidInfoInstance2" inherits="RaidInfoInstanceTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="RaidInfoInstance1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidInfoInstance3" inherits="RaidInfoInstanceTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="RaidInfoInstance2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidInfoInstance4" inherits="RaidInfoInstanceTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="RaidInfoInstance3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidInfoInstance5" inherits="RaidInfoInstanceTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="RaidInfoInstance4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidInfoInstance6" inherits="RaidInfoInstanceTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="RaidInfoInstance5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidInfoInstance7" inherits="RaidInfoInstanceTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="RaidInfoInstance6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidInfoInstance8" inherits="RaidInfoInstanceTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="RaidInfoInstance7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidInfoInstance9" inherits="RaidInfoInstanceTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="RaidInfoInstance8" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
</Frame>
<!-- The reference anchors this one to Instance1, which stacks
row 10 on top of row 2. That is a typo in a chain of nine
identical links, not a behaviour: reproducing it would
hide the tenth lockout behind the second. Anchored to 9,
and said out loud rather than silently "fixed". -->
<Frame name="RaidInfoInstance10" inherits="RaidInfoInstanceTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="RaidInfoInstance9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
</Frame>
</Frames>
</Frame>
</ScrollChild>
</ScrollFrame>
</Frames>
</Frame>
<!-- The eight subgroups: two columns of four, ref l.533-604. -->
<Frame name="RaidGroup1" inherits="RaidGroupTemplate" id="1">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="16" y="-70"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidGroup2" inherits="RaidGroupTemplate" id="2">
<Anchors><Anchor point="LEFT" relativeTo="RaidGroup1" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidGroup3" inherits="RaidGroupTemplate" id="3">
<Anchors><Anchor point="TOP" relativeTo="RaidGroup1" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidGroup4" inherits="RaidGroupTemplate" id="4">
<Anchors><Anchor point="LEFT" relativeTo="RaidGroup3" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidGroup5" inherits="RaidGroupTemplate" id="5">
<Anchors><Anchor point="TOP" relativeTo="RaidGroup3" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidGroup6" inherits="RaidGroupTemplate" id="6">
<Anchors><Anchor point="LEFT" relativeTo="RaidGroup5" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidGroup7" inherits="RaidGroupTemplate" id="7">
<Anchors><Anchor point="TOP" relativeTo="RaidGroup5" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
</Frame>
<Frame name="RaidGroup8" inherits="RaidGroupTemplate" id="8">
<Anchors><Anchor point="LEFT" relativeTo="RaidGroup7" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
</Frame>
<!-- The forty rows. Unpositioned by declaration and hidden until the repaint seats
them in a slot — which is the reference's own arrangement, and the reason a row
can be dragged out of one group into another without re-parenting anything. -->
<Button name="RaidGroupButton1" inherits="RaidGroupButtonTemplate" id="1" hidden="true"/>
<Button name="RaidGroupButton2" inherits="RaidGroupButtonTemplate" id="2" hidden="true"/>
<Button name="RaidGroupButton3" inherits="RaidGroupButtonTemplate" id="3" hidden="true"/>
<Button name="RaidGroupButton4" inherits="RaidGroupButtonTemplate" id="4" hidden="true"/>
<Button name="RaidGroupButton5" inherits="RaidGroupButtonTemplate" id="5" hidden="true"/>
<Button name="RaidGroupButton6" inherits="RaidGroupButtonTemplate" id="6" hidden="true"/>
<Button name="RaidGroupButton7" inherits="RaidGroupButtonTemplate" id="7" hidden="true"/>
<Button name="RaidGroupButton8" inherits="RaidGroupButtonTemplate" id="8" hidden="true"/>
<Button name="RaidGroupButton9" inherits="RaidGroupButtonTemplate" id="9" hidden="true"/>
<Button name="RaidGroupButton10" inherits="RaidGroupButtonTemplate" id="10" hidden="true"/>
<Button name="RaidGroupButton11" inherits="RaidGroupButtonTemplate" id="11" hidden="true"/>
<Button name="RaidGroupButton12" inherits="RaidGroupButtonTemplate" id="12" hidden="true"/>
<Button name="RaidGroupButton13" inherits="RaidGroupButtonTemplate" id="13" hidden="true"/>
<Button name="RaidGroupButton14" inherits="RaidGroupButtonTemplate" id="14" hidden="true"/>
<Button name="RaidGroupButton15" inherits="RaidGroupButtonTemplate" id="15" hidden="true"/>
<Button name="RaidGroupButton16" inherits="RaidGroupButtonTemplate" id="16" hidden="true"/>
<Button name="RaidGroupButton17" inherits="RaidGroupButtonTemplate" id="17" hidden="true"/>
<Button name="RaidGroupButton18" inherits="RaidGroupButtonTemplate" id="18" hidden="true"/>
<Button name="RaidGroupButton19" inherits="RaidGroupButtonTemplate" id="19" hidden="true"/>
<Button name="RaidGroupButton20" inherits="RaidGroupButtonTemplate" id="20" hidden="true"/>
<Button name="RaidGroupButton21" inherits="RaidGroupButtonTemplate" id="21" hidden="true"/>
<Button name="RaidGroupButton22" inherits="RaidGroupButtonTemplate" id="22" hidden="true"/>
<Button name="RaidGroupButton23" inherits="RaidGroupButtonTemplate" id="23" hidden="true"/>
<Button name="RaidGroupButton24" inherits="RaidGroupButtonTemplate" id="24" hidden="true"/>
<Button name="RaidGroupButton25" inherits="RaidGroupButtonTemplate" id="25" hidden="true"/>
<Button name="RaidGroupButton26" inherits="RaidGroupButtonTemplate" id="26" hidden="true"/>
<Button name="RaidGroupButton27" inherits="RaidGroupButtonTemplate" id="27" hidden="true"/>
<Button name="RaidGroupButton28" inherits="RaidGroupButtonTemplate" id="28" hidden="true"/>
<Button name="RaidGroupButton29" inherits="RaidGroupButtonTemplate" id="29" hidden="true"/>
<Button name="RaidGroupButton30" inherits="RaidGroupButtonTemplate" id="30" hidden="true"/>
<Button name="RaidGroupButton31" inherits="RaidGroupButtonTemplate" id="31" hidden="true"/>
<Button name="RaidGroupButton32" inherits="RaidGroupButtonTemplate" id="32" hidden="true"/>
<Button name="RaidGroupButton33" inherits="RaidGroupButtonTemplate" id="33" hidden="true"/>
<Button name="RaidGroupButton34" inherits="RaidGroupButtonTemplate" id="34" hidden="true"/>
<Button name="RaidGroupButton35" inherits="RaidGroupButtonTemplate" id="35" hidden="true"/>
<Button name="RaidGroupButton36" inherits="RaidGroupButtonTemplate" id="36" hidden="true"/>
<Button name="RaidGroupButton37" inherits="RaidGroupButtonTemplate" id="37" hidden="true"/>
<Button name="RaidGroupButton38" inherits="RaidGroupButtonTemplate" id="38" hidden="true"/>
<Button name="RaidGroupButton39" inherits="RaidGroupButtonTemplate" id="39" hidden="true"/>
<Button name="RaidGroupButton40" inherits="RaidGroupButtonTemplate" id="40" hidden="true"/>
</Frames>
<Scripts>
<!-- The reference splits these across two files: RaidFrame.xml wires OnLoad/OnShow/
OnEvent and Blizzard_RaidUI's `RaidGroupFrame_OnLoad` then REPLACES OnEvent,
OnHide and OnUpdate with the grid's versions once the addon loads. One file means
one wiring, and `RaidGroupFrame_OnEvent` (which calls `RaidFrame_OnEvent` first,
exactly as the replacement does) is the merged one. -->
<OnLoad>RaidFrame_OnLoad()</OnLoad>
<OnShow>
RaidFrame_Update()
RequestRaidInfo()
</OnShow>
<OnHide>RaidGroupFrame_OnHide()</OnHide>
<OnEvent>RaidGroupFrame_OnEvent(event, arg1)</OnEvent>
<OnUpdate>RaidGroupFrame_OnUpdate()</OnUpdate>
</Scripts>
</Frame>
<!-- The ready-check popup (ref Blizzard_RaidUI.xml l.649-750). Toplevel and on UIParent, not
on the pane: a ready check reaches you whether or not the social window has ever been
opened. -->
<Frame name="ReadyCheckFrame" toplevel="true" parent="UIParent" hidden="true">
<Size><AbsDimension x="323" y="97"/></Size>
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="-10"/></Offset></Anchor></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture name="ReadyCheckPortrait">
<Size><AbsDimension x="50" y="50"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
<Layer level="ARTWORK">
<Texture file="Interface\RaidFrame\UI-ReadyCheckFrame">
<Size><AbsDimension x="323" y="97"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<TexCoords left="0" right="0.630859375" top="0" bottom="0.7578125"/>
</Texture>
<FontString name="ReadyCheckFrameText" inherits="GameFontNormal">
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="15" y="-24"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Button name="ReadyCheckFrameYesButton" inherits="UIPanelButtonTemplate" text="YES">
<Size><AbsDimension x="109" y="24"/></Size>
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="55" y="18"/></Offset></Anchor></Anchors>
<Scripts>
<OnClick>
ConfirmReadyCheck(1)
ReadyCheckFrame.timer = nil
ReadyCheckFrame:Hide()
</OnClick>
</Scripts>
</Button>
<!-- No passes no argument at all — the binding reads its argument for TRUTH, so
"absent" is the not-ready answer. The reference's own spelling. -->
<Button name="ReadyCheckFrameNoButton" inherits="UIPanelButtonTemplate" text="NO">
<Size><AbsDimension x="109" y="24"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="ReadyCheckFrameYesButton" relativePoint="RIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
<Scripts>
<OnClick>
ConfirmReadyCheck()
ReadyCheckFrame.timer = nil
ReadyCheckFrame:Hide()
</OnClick>
</Scripts>
</Button>
</Frames>
<Scripts>
<OnLoad>this:RegisterEvent("RAID_ROSTER_UPDATE")</OnLoad>
<OnEvent>
-- The raid ending ends the question with it.
if event == "RAID_ROSTER_UPDATE" and GetNumRaidMembers() == 0 then
ReadyCheckFrame.timer = nil
ReadyCheckFrame:Hide()
end
</OnEvent>
<OnUpdate>ReadyCheck_OnUpdate(arg1)</OnUpdate>
</Scripts>
</Frame>
</Ui>