5842eb7a2b
Traduction - Identifiants en anglais dans tout le module : addon, ecrans d'accueil, outils Python, banc d'essai et patcheur C#. - Fichiers au nom francais renommes (Gabarits.lua -> Templates.lua, Textes_*.lua -> Texts_*.lua, deployer.py -> deploy.py, tools/patcheur -> tools/patcher, etc.). - Commandes /fui et options de deploy.py en anglais (--check, --server, --remove-dbc). - Cles des variables sauvegardees et de data/dbc/statistics.json traduites, sans reprise des anciennes cles. - API de la micro-barre : ForeverUI.AddMicroButton(def) avec les champs name, atlasSet, after, tooltip, onClick, ready, et ForeverUI.UpdateMicro(name, isOpen). - Commentaires reecrits en anglais, courts, sans dates ni historique ; les fonctions au nom peu parlant et leurs parametres sont expliques. Code mort - Exports, fonctions et variables jamais lus retires. - Retires a la demande des modules : ForeverUI.PvPJauge, les exports FondEntete / TuilerEntete / Polices de TradeSkill, l'art legacy-up-c60, honorfill(half)blue et inv_misc_book_09, leurs entrees d'atlas et les parties d'outils correspondantes. Verifications : liaisons des noms comparees avant et apres, banc vert, chemins de textures inchanges, generateurs reproduisant les fichiers, patcheur identique octet pour octet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
893 lines
32 KiB
Lua
893 lines
32 KiB
Lua
-- WotLK raid browser inside the group finder window (GroupFinder.lua), styled after camelot's
|
|
-- group finder: a side tab plus two bottom tabs from the Social window, "List My Group"
|
|
-- (camelot LFGListingFrame) and "Join" (LFGBrowseFrame). The active tab is the client's
|
|
-- (LFRParentFrame.activeTab), so the raid reopens on the last panel seen.
|
|
-- Data and actions come from WotLK (LFRFrame.lua): LFRQueueFrame and LFRBrowseFrame stay shown
|
|
-- but transparent under our window; we copy their state and act through their functions.
|
|
-- One raid at a time: the server tracks one raid search per player (AzerothCore LFGMgr,
|
|
-- RBSearchersStore), and SearchLFGJoin is protected, so it only runs from a click or a key.
|
|
|
|
local ForeverUI = ForeverUI or {}
|
|
_G.ForeverUI = ForeverUI
|
|
|
|
local F = ForeverUI.GroupFinder
|
|
local R = {}
|
|
ForeverUI.GroupFinderRaid = R
|
|
|
|
if not (F and F.frame and LFRParentFrame and LFRQueueFrame and LFRBrowseFrame) then return end
|
|
|
|
local SEP = string.char(92)
|
|
local G = F.G
|
|
local txt, active = F.txt, F.active
|
|
|
|
local M = {
|
|
-- Sign-up page
|
|
-- The raid list stops 88 + 4 above the view bottom; its categories use the shared list
|
|
-- (GroupFinder.lua, F.LC)
|
|
ruleY = 40 + 80, listBottom = 40 + 88 + 4,
|
|
commentW = 394, commentH = 47, commentY = 40 + 19, maxLetters = 64,
|
|
-- Browse page
|
|
browseY = -80, menuX = 70, menuY = -43, menuW = 300, menuH = 25,
|
|
refresh = 32, refreshX = 9,
|
|
-- Without the bar, at -7 like the left side (G.listX1: 7)
|
|
result = 48, resultsY = -83, resultsX2 = -22, resultsNoBar = -7,
|
|
-- Bottom tabs, as in Social: the first at (5, 2) under the window, the next ones 3 apart
|
|
tabX = 5, tabY = 2, tabGap = 3,
|
|
edge = "Interface" .. SEP .. "Common" .. SEP .. "Common-Input-Border-",
|
|
square = "interface" .. SEP .. "ForeverUI" .. SEP .. "buttons" .. SEP,
|
|
leader = "Interface" .. SEP .. "GroupFrame" .. SEP .. "UI-Group-LeaderIcon",
|
|
gray = { 0.3, 0.3, 0.3 },
|
|
}
|
|
|
|
-- ---------------------------------------------------------------- Source
|
|
-- Raid list (LFRFrame.lua): in a group, a single raid picked by a radio button
|
|
-- (LFRQueueFrame.selectedLFM); solo, checkboxes
|
|
local SOURCE_LFR = {
|
|
-- camelot checkbox style (GroupFinder.lua)
|
|
style = "camelot",
|
|
collapse = function(id, collapsed) LFRList_SetHeaderCollapsed(id, collapsed) end,
|
|
list = function() return LFRRaidList or {} end,
|
|
empowered = function() return LFR_IsEmpowered() end,
|
|
plus = function(b) LFRQueueFrameExpandOrCollapseButton_OnClick(b) end,
|
|
checkbox = function(c) LFRQueueFrameDungeonChoiceEnableButton_OnClick(c) end,
|
|
multiple = function() return LFR_CanQueueForMultiple() end,
|
|
lock = function(id) return not LFR_CanQueueForLockedInstances() and LFGLockList[id] end,
|
|
state = function(id, mode)
|
|
if mode == "queued" or mode == "listed" then return LFGQueuedForList[id] end
|
|
if not LFR_CanQueueForMultiple() then return id == LFRQueueFrame.selectedLFM end
|
|
return LFGEnabledList[id]
|
|
end,
|
|
}
|
|
R.SOURCE = SOURCE_LFR
|
|
|
|
local function textOf(name)
|
|
local r = _G[name]
|
|
return r and r.GetText and r:GetText() or ""
|
|
end
|
|
|
|
-- Copies a client button's text and enabled state to b; keepText: keep b's own text
|
|
local function mirror(b, clientName, keepText)
|
|
local c = _G[clientName]
|
|
if not c then return end
|
|
if not keepText then b:SetText(c:GetText() or "") end
|
|
b:Activate(active(c))
|
|
end
|
|
|
|
-- ---------------------------------------------------------------- Sign-up
|
|
|
|
-- LFRQueueFrame roles, at camelot's positions
|
|
local function raidRoles()
|
|
local l = {}
|
|
local clients = { tank = "LFRQueueFrameRoleButtonTank", healer = "LFRQueueFrameRoleButtonHealer",
|
|
damage = "LFRQueueFrameRoleButtonDPS" }
|
|
for _, r in ipairs(F.ROLES) do
|
|
if clients[r.key] then
|
|
table.insert(l, { key = r.key, page = "Raid", client = clients[r.key], id = r.id, x = r.x,
|
|
alpha = r.alpha, icon = r.icon, background = r.background })
|
|
end
|
|
end
|
|
return l
|
|
end
|
|
|
|
-- Comment: camelot's frame around a WotLK edit box. The client's LFRQueueFrameComment stays
|
|
-- the one LFRQueueFrame_Join reads, so ours copies its text into it; focus and send rules
|
|
-- follow its XML (OnEditFocusGained / OnEditFocusLost). p: parent; level: frame level
|
|
local function buildComment(p, level)
|
|
local c = CreateFrame("Frame", "ForeverUIGroupFinderRaidComment", p)
|
|
c:SetWidth(M.commentW)
|
|
c:SetHeight(M.commentH)
|
|
c:SetFrameLevel(level)
|
|
c:EnableMouse(true)
|
|
local function piece(suffix)
|
|
local t = c:CreateTexture(nil, "BACKGROUND")
|
|
t:SetTexture(M.edge .. suffix)
|
|
return t
|
|
end
|
|
local topLeft, topRight, bottomLeft, bottomRight = piece("TL"), piece("TR"), piece("BL"), piece("BR")
|
|
for _, t in ipairs({ topLeft, topRight, bottomLeft, bottomRight }) do t:SetWidth(8) t:SetHeight(8) end
|
|
topLeft:SetPoint("TOPLEFT", c, "TOPLEFT", -5, 5)
|
|
topRight:SetPoint("TOPRIGHT", c, "TOPRIGHT", 5, 5)
|
|
bottomLeft:SetPoint("BOTTOMLEFT", c, "BOTTOMLEFT", -5, -5)
|
|
bottomRight:SetPoint("BOTTOMRIGHT", c, "BOTTOMRIGHT", 5, -5)
|
|
local top, down, left, right = piece("T"), piece("B"), piece("L"), piece("R")
|
|
top:SetPoint("TOPLEFT", topLeft, "TOPRIGHT", 0, 0)
|
|
top:SetPoint("BOTTOMRIGHT", topRight, "BOTTOMLEFT", 0, 0)
|
|
down:SetPoint("TOPLEFT", bottomLeft, "TOPRIGHT", 0, 0)
|
|
down:SetPoint("BOTTOMRIGHT", bottomRight, "BOTTOMLEFT", 0, 0)
|
|
left:SetPoint("TOPLEFT", topLeft, "BOTTOMLEFT", 0, 0)
|
|
left:SetPoint("BOTTOMRIGHT", bottomLeft, "TOPRIGHT", 0, 0)
|
|
right:SetPoint("TOPLEFT", topRight, "BOTTOMLEFT", 0, 0)
|
|
right:SetPoint("BOTTOMRIGHT", bottomRight, "TOPRIGHT", 0, 0)
|
|
local middle = piece("M")
|
|
middle:SetPoint("TOPLEFT", left, "TOPRIGHT", 0, 0)
|
|
middle:SetPoint("BOTTOMRIGHT", right, "BOTTOMLEFT", 0, 0)
|
|
|
|
local e = CreateFrame("EditBox", "ForeverUIGroupFinderRaidCommentEditBox", c)
|
|
e:SetMultiLine(true)
|
|
e:SetAutoFocus(false)
|
|
e:SetMaxLetters(M.maxLetters)
|
|
-- Full width: camelot removes 18 for its scroll bar; this comment does not scroll
|
|
e:SetWidth(M.commentW)
|
|
e:SetHeight(M.commentH)
|
|
e:SetPoint("TOPLEFT", c, "TOPLEFT", 0, 0)
|
|
e:SetFontObject(GameFontHighlightSmall)
|
|
local instruction = e:CreateFontString(nil, "BORDER", "GameFontNormalSmall")
|
|
instruction:SetPoint("TOPLEFT", e, "TOPLEFT", 0, 0)
|
|
instruction:SetWidth(M.commentW)
|
|
instruction:SetJustifyH("LEFT")
|
|
instruction:SetJustifyV("TOP")
|
|
instruction:SetTextColor(0.35, 0.35, 0.35)
|
|
instruction:SetText(txt("TYPE_LFR_COMMENT_HERE"))
|
|
e.instruction = instruction
|
|
e:SetScript("OnEditFocusGained", function(self)
|
|
if LFR_IsEmpowered() and LFRRaidList and LFRRaidList[1] then
|
|
self.instruction:Hide()
|
|
else
|
|
self:ClearFocus()
|
|
end
|
|
end)
|
|
e:SetScript("OnEditFocusLost", function(self)
|
|
local t = self:GetText() or ""
|
|
if strtrim(t) == "" then self.instruction:Show() end
|
|
LFRQueueFrameComment:SetText(t)
|
|
SetLFGComment(t)
|
|
end)
|
|
e:SetScript("OnTextChanged", function(self)
|
|
LFRQueueFrameComment:SetText(self:GetText() or "")
|
|
end)
|
|
e:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
|
|
e:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
|
|
c:SetScript("OnMouseDown", function() e:SetFocus() end)
|
|
R.input = e
|
|
R.comment = c
|
|
end
|
|
|
|
-- Raid list with categories: the shared list (GroupFinder.lua, F.createCatList: character
|
|
-- sheet headers, entries shared with dungeons, source SOURCE_LFR)
|
|
function R.updateCategories()
|
|
F.updateCategoryList(R.list)
|
|
end
|
|
|
|
-- "List My Group" panel: camelot's sign-up page
|
|
local function buildSignUp(p)
|
|
local f = F.frame
|
|
R.blue, R.roles = F.buildBanner(p, raidRoles())
|
|
local frameBox = F.buildFrameBox(p, "ForeverUIGroupFinderRaidInset", G.frameBoxY1, true)
|
|
R.frameBox = frameBox
|
|
|
|
local zone = F.createCatList(p, "ForeverUIGroupFinderRaidList", "ForeverUIGroupFinderRaid", SOURCE_LFR,
|
|
M.listBottom, frameBox)
|
|
R.list = zone
|
|
-- "No raids": LFRQueueFrameSpecificNoRaidsAvailable
|
|
local none = zone:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
|
none:SetWidth(300)
|
|
none:SetPoint("TOP", zone, "TOP", 0, -40)
|
|
none:SetText(txt("NO_RAIDS_AVAILABLE"))
|
|
none:Hide()
|
|
R.none = none
|
|
|
|
-- Rule and comment, above the inset
|
|
local hovered = CreateFrame("Frame", nil, p)
|
|
hovered:SetAllPoints(f)
|
|
hovered:SetFrameLevel(frameBox:GetFrameLevel() + 1)
|
|
local rule = hovered:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(rule, "shop-list-rule", true)
|
|
rule:SetHeight(G.ruleH)
|
|
rule:SetPoint("LEFT", f, "BOTTOMLEFT", G.listX1, M.ruleY)
|
|
rule:SetPoint("RIGHT", f, "BOTTOMRIGHT", G.listX2, M.ruleY)
|
|
R.rule = rule
|
|
buildComment(p, frameBox:GetFrameLevel() + 2)
|
|
R.comment:SetPoint("BOTTOM", f, "BOTTOM", 0, M.commentY)
|
|
|
|
-- "No raid while in the dungeon queue": the WotLK veil
|
|
local veil = F.createVeil("ForeverUIGroupFinderNoLFRWhileLFD", 16, p, frameBox)
|
|
veil:SetAllPoints(frameBox)
|
|
veil.quit = ForeverUI.CreatePanelButton(veil, txt("LEAVE_QUEUE"), 153, 22, nil, "GameFontNormal")
|
|
veil.quit:SetPoint("TOP", veil.description, "BOTTOM", 0, -10)
|
|
veil.quit:SetScript("OnClick", function()
|
|
if LFRQueueFrameNoLFRWhileLFDLeaveQueueButton then LFRQueueFrameNoLFRWhileLFDLeaveQueueButton:Click() end
|
|
end)
|
|
R.veil = veil
|
|
|
|
-- "Set Comment" and "List Me" drive the client's buttons
|
|
local commentButton = ForeverUI.CreatePanelButton(p, txt("ACCEPT_COMMENT"), G.backL, G.buttonH,
|
|
"ForeverUIGroupFinderRaidCommentButton", "GameFontNormal")
|
|
commentButton:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", G.buttonSide, G.buttonBottom)
|
|
-- LFRQueueFrameAcceptCommentButton OnClick: losing focus sends the comment;
|
|
-- without focus, just a sound
|
|
commentButton:SetScript("OnClick", function()
|
|
if R.input:HasFocus() then
|
|
R.input:ClearFocus()
|
|
else
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
end
|
|
end)
|
|
R.commentButton = commentButton
|
|
local register = ForeverUI.CreatePanelButton(p, txt("LIST_ME"), G.postW, G.buttonH,
|
|
"ForeverUIGroupFinderRaidListButton", "GameFontNormal")
|
|
register:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -G.buttonSide, G.buttonBottom)
|
|
register:SetScript("OnClick", function()
|
|
if R.input:HasFocus() then R.input:ClearFocus() end
|
|
if LFRQueueFrameFindGroupButton and active(LFRQueueFrameFindGroupButton) then
|
|
LFRQueueFrameFindGroupButton:Click()
|
|
end
|
|
end)
|
|
R.register = register
|
|
end
|
|
|
|
local function updateSignUp()
|
|
R.updateCategories()
|
|
if LFRQueueFrameSpecificNoRaidsAvailable and LFRQueueFrameSpecificNoRaidsAvailable:IsShown() then
|
|
R.none:Show()
|
|
else
|
|
R.none:Hide()
|
|
end
|
|
-- The client's comment, while we are not typing
|
|
if not R.input:HasFocus() then
|
|
R.input:SetText(LFRQueueFrameComment:GetText() or "")
|
|
if LFRQueueFrameCommentExplanation and not LFRQueueFrameCommentExplanation:IsShown()
|
|
and strtrim(R.input:GetText() or "") ~= "" then
|
|
R.input.instruction:Hide()
|
|
else
|
|
R.input.instruction:Show()
|
|
end
|
|
end
|
|
local v = LFRQueueFrameNoLFRWhileLFD
|
|
if v and v:IsShown() then
|
|
R.veil.description:SetText(textOf("LFRQueueFrameNoLFRWhileLFDDescription"))
|
|
mirror(R.veil.quit, "LFRQueueFrameNoLFRWhileLFDLeaveQueueButton", true)
|
|
R.veil:Show()
|
|
else
|
|
R.veil:Hide()
|
|
end
|
|
mirror(R.commentButton, "LFRQueueFrameAcceptCommentButton")
|
|
mirror(R.register, "LFRQueueFrameFindGroupButton")
|
|
end
|
|
|
|
-- ---------------------------------------------------------------- Browse
|
|
|
|
-- WowStyle1DropdownTemplate, as for the map floors: common-dropdown-textholder-c60 in three
|
|
-- slices (16 / 19), arrow at RIGHT (1, -3), GameFontHighlight text from (8, -8) to the arrow
|
|
local function buildMenu(p)
|
|
local b = CreateFrame("Button", "ForeverUIGroupFinderRaidDropDown", p)
|
|
b:SetWidth(M.menuW)
|
|
b:SetHeight(M.menuH)
|
|
b:SetPoint("TOPLEFT", p, "TOPLEFT", M.menuX, M.menuY)
|
|
local e = ForeverUI.AtlasEntry("common-dropdown-textholder-c60")
|
|
local background = CreateFrame("Frame", nil, b)
|
|
background:SetPoint("TOPLEFT", b, "TOPLEFT", -8, 7)
|
|
background:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 8, -9)
|
|
if e then
|
|
local du = (e[3] - e[2]) / e[6]
|
|
local u1, u2 = e[2] + 16 * du, e[3] - 19 * du
|
|
local function piece(a, z)
|
|
local t = b:CreateTexture(nil, "BACKGROUND")
|
|
t:SetTexture(e[1])
|
|
t:SetTexCoord(a, z, e[4], e[5])
|
|
return t
|
|
end
|
|
local g = piece(e[2], u1)
|
|
g:SetWidth(16)
|
|
g:SetPoint("TOPLEFT", background, "TOPLEFT", 0, 0)
|
|
g:SetPoint("BOTTOMLEFT", background, "BOTTOMLEFT", 0, 0)
|
|
local d = piece(u2, e[3])
|
|
d:SetWidth(19)
|
|
d:SetPoint("TOPRIGHT", background, "TOPRIGHT", 0, 0)
|
|
d:SetPoint("BOTTOMRIGHT", background, "BOTTOMRIGHT", 0, 0)
|
|
local m = piece(u1, u2)
|
|
m:SetPoint("TOPLEFT", g, "TOPRIGHT", 0, 0)
|
|
m:SetPoint("BOTTOMRIGHT", d, "BOTTOMLEFT", 0, 0)
|
|
end
|
|
local arrow = b:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(arrow, "common-dropdown-a-button")
|
|
arrow:SetPoint("RIGHT", b, "RIGHT", 1, -3)
|
|
local text = b:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
|
text:SetJustifyH("LEFT")
|
|
text:SetHeight(10)
|
|
text:SetPoint("TOPLEFT", b, "TOPLEFT", 8, -8)
|
|
text:SetPoint("TOPRIGHT", arrow, "LEFT", 0, 0)
|
|
b.text = text
|
|
b.arrow = arrow
|
|
b:SetScript("OnMouseDown", function(self) ForeverUI.SetAtlas(self.arrow, "common-dropdown-a-button-pressed", true) end)
|
|
b:SetScript("OnMouseUp", function(self) ForeverUI.SetAtlas(self.arrow, "common-dropdown-a-button", true) end)
|
|
-- Our menu under our button, built like the client's (LFRBrowseFrameRaidDropDown_Initialize).
|
|
-- With no client button: displayMode "MENU", as wide as its content and at least as wide
|
|
-- as our button (DropDown.lua, foreverMinimum)
|
|
local menu = CreateFrame("Frame", "ForeverUIGroupFinderRaidMenu", p)
|
|
menu.displayMode = "MENU"
|
|
menu.foreverMinimum = M.menuW
|
|
menu.initialize = function(self, level) R.initMenu(level) end
|
|
R.menuList = menu
|
|
b:SetScript("OnClick", function(self)
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
ToggleDropDownMenu(1, nil, menu, self, 0, 0)
|
|
end)
|
|
R.menu = b
|
|
end
|
|
|
|
-- ---------------------------------------------------------------- Search
|
|
-- R.search = { name, ids = { raid } }; R.hidden[raid] = the entries collected for that raid
|
|
R.hidden = {}
|
|
|
|
-- LFG type of a dungeon id, as SearchLFGJoin expects
|
|
local function typeOf(id)
|
|
local info = LFGGetDungeonInfoByID and LFGGetDungeonInfoByID(id)
|
|
return info and info[2] or 0
|
|
end
|
|
|
|
-- Raid name with its level prefix
|
|
local function nameOf(id)
|
|
local info = LFGGetDungeonInfoByID and LFGGetDungeonInfoByID(id)
|
|
if not info then return "" end
|
|
return format(txt("LFD_LEVEL_FORMAT_SINGLE"), info[12] or 0) .. " " .. (info[1] or "")
|
|
end
|
|
|
|
-- Collects the server answer for the searched raid: everything the client tooltip reads
|
|
-- (SearchLFGGetResults, GetPartyResults, GetEncounterResults)
|
|
function R.collect(raid)
|
|
local l = {}
|
|
local count = SearchLFGGetNumResults() or 0
|
|
for i = 1, count do
|
|
local d = { SearchLFGGetResults(i) }
|
|
local e = { raid = raid, name = d[1], level = d[2], zone = d[3], localizedClass = d[4], comment = d[5],
|
|
members = d[6] or 0, className = d[8], bossTotal = d[9] or 0, bossKilled = d[10] or 0, leader = d[11],
|
|
tank = d[12], healer = d[13], damage = d[14], group = {}, boss = {} }
|
|
for j = 1, e.members do
|
|
local nm, _, link = SearchLFGGetPartyResults(i, j)
|
|
e.group[j] = { name = nm, link = link }
|
|
end
|
|
if e.bossKilled > 0 then
|
|
for j = 1, e.bossTotal do
|
|
local bn, _, killed = SearchLFGGetEncounterResults(i, j)
|
|
e.boss[j] = { name = bn, killed = killed }
|
|
end
|
|
end
|
|
l[i] = e
|
|
end
|
|
R.hidden[raid] = l
|
|
end
|
|
|
|
-- LFRBrowseFrameRaidDropDownButton_OnClick: no raid (nil) or one raid id. Called from the
|
|
-- menu line click: SearchLFGJoin is protected and only runs from a click.
|
|
function R.choose(value)
|
|
CloseDropDownMenus()
|
|
R.choice = nil
|
|
if value == nil then
|
|
R.search = nil
|
|
SearchLFGLeave()
|
|
else
|
|
R.search = { name = nameOf(value), ids = { value }, fromIndex = GetTime() }
|
|
SearchLFGJoin(typeOf(value), value)
|
|
end
|
|
F.request()
|
|
end
|
|
|
|
-- LFRBrowseFrameRaidDropDown_Initialize; top-level categories have no checkbox
|
|
function R.initMenu(level)
|
|
local order, list = GetFullRaidList()
|
|
local r = R.search
|
|
local info = UIDropDownMenu_CreateInfo()
|
|
if not level or level == 1 then
|
|
info.text = txt("NONE")
|
|
info.func = function() R.choose(nil) end
|
|
info.notCheckable = true
|
|
UIDropDownMenu_AddButton(info, 1)
|
|
for _, group in ipairs(order) do
|
|
info = UIDropDownMenu_CreateInfo()
|
|
info.text = LFGGetDungeonInfoByID(group)[1]
|
|
info.value = group
|
|
info.hasArrow = true
|
|
info.notCheckable = true
|
|
UIDropDownMenu_AddButton(info, 1)
|
|
end
|
|
elseif level == 2 then
|
|
for _, id in ipairs(list[UIDROPDOWNMENU_MENU_VALUE] or {}) do
|
|
info = UIDropDownMenu_CreateInfo()
|
|
info.text = nameOf(id)
|
|
info.value = id
|
|
info.func = function() R.choose(id) end
|
|
info.checked = r ~= nil and r.ids[1] == id
|
|
UIDropDownMenu_AddButton(info, 2)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- What the list shows: the entries of the searched raids, in order
|
|
function R.collectListed()
|
|
local r = R.search
|
|
local l = {}
|
|
if not r then return l end
|
|
for _, id in ipairs(r.ids) do
|
|
for _, e in ipairs(R.hidden[id] or {}) do table.insert(l, e) end
|
|
end
|
|
return l
|
|
end
|
|
|
|
local function buildRefresh(p)
|
|
local b = CreateFrame("Button", "ForeverUIGroupFinderRaidRefresh", p)
|
|
b:SetWidth(M.refresh)
|
|
b:SetHeight(M.refresh)
|
|
b:SetPoint("LEFT", R.menu, "RIGHT", M.refreshX, 0)
|
|
b:SetNormalTexture(M.square .. "ui-squarebutton-up")
|
|
b:SetPushedTexture(M.square .. "ui-squarebutton-down")
|
|
b:SetHighlightTexture(M.square .. "ui-common-mousehilight")
|
|
local s = b:GetHighlightTexture()
|
|
if s then s:SetBlendMode("ADD") end
|
|
local icon = b:CreateTexture(nil, "ARTWORK")
|
|
icon:SetTexture(M.square .. "ui-refreshbutton")
|
|
icon:SetWidth(16)
|
|
icon:SetHeight(16)
|
|
icon:SetPoint("CENTER", b, "CENTER", -1, 0)
|
|
b.icon = icon
|
|
b:SetScript("OnMouseDown", function(self)
|
|
self.icon:ClearAllPoints()
|
|
self.icon:SetPoint("CENTER", self, "CENTER", -2, -1)
|
|
end)
|
|
b:SetScript("OnMouseUp", function(self)
|
|
self.icon:ClearAllPoints()
|
|
self.icon:SetPoint("CENTER", self, "CENTER", -1, 0)
|
|
end)
|
|
b:SetScript("OnEnter", function(self)
|
|
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
|
GameTooltip:SetText(txt("REFRESH"))
|
|
GameTooltip:Show()
|
|
end)
|
|
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
-- Client button: RefreshLFGList
|
|
b:SetScript("OnClick", function()
|
|
if LFRBrowseFrameRefreshButton then LFRBrowseFrameRefreshButton:Click() end
|
|
end)
|
|
R.refresh = b
|
|
end
|
|
|
|
-- LFGBrowseSearchEntryTemplate
|
|
local function createResult(l)
|
|
local background = l:CreateTexture(nil, "BACKGROUND")
|
|
background:SetTexture(1, 1, 1, 0.04)
|
|
background:SetPoint("TOPLEFT", l, "TOPLEFT", 3, -2)
|
|
background:SetPoint("BOTTOMRIGHT", l, "BOTTOMRIGHT", -3, 0)
|
|
local leader = l:CreateTexture(nil, "ARTWORK")
|
|
leader:SetTexture(M.leader)
|
|
leader:SetWidth(24)
|
|
leader:SetHeight(24)
|
|
leader:SetPoint("TOPLEFT", l, "TOPLEFT", 8, -4)
|
|
l.leader = leader
|
|
local name = l:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
|
|
name:SetHeight(14)
|
|
name:SetJustifyH("LEFT")
|
|
l.name = name
|
|
local level = l:CreateFontString(nil, "ARTWORK", "GameFontDisableLeft")
|
|
level:SetHeight(14)
|
|
level:SetPoint("BOTTOMLEFT", name, "BOTTOMRIGHT", 4, 0)
|
|
l.level = level
|
|
local className = l:CreateTexture(nil, "ARTWORK")
|
|
className:SetWidth(24)
|
|
className:SetHeight(24)
|
|
className:SetPoint("BOTTOMLEFT", level, "BOTTOMRIGHT", 3, -5)
|
|
l.className = className
|
|
local down = l:CreateFontString(nil, "ARTWORK", "GameFontDisableLeft")
|
|
down:SetHeight(15)
|
|
down:SetPoint("BOTTOMLEFT", l, "BOTTOMLEFT", 10, 5)
|
|
down:SetPoint("RIGHT", l, "RIGHT", -120, 0)
|
|
down:SetJustifyH("LEFT")
|
|
l.down = down
|
|
local function bar(atlas)
|
|
local t = l:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(t, atlas, true)
|
|
t:SetBlendMode("ADD")
|
|
t:SetPoint("TOPLEFT", l, "TOPLEFT", 3, -3)
|
|
t:SetPoint("BOTTOMRIGHT", l, "BOTTOMRIGHT", -3, -1)
|
|
t:Hide()
|
|
return t
|
|
end
|
|
l.selected = bar("groupfinder-highlightbar-yellow")
|
|
l.hover = bar("groupfinder-highlightbar-blue")
|
|
|
|
-- Right side: a solo player's roles, or a group's size
|
|
local roles = l:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
|
|
roles:SetHeight(20)
|
|
roles:SetPoint("RIGHT", l, "RIGHT", -2 - 120, -1)
|
|
roles:SetText(txt("LFG_TOOLTIP_ROLES"))
|
|
l.roles = roles
|
|
l.roleIcons = {}
|
|
for i = 1, 3 do
|
|
local t = l:CreateTexture(nil, "ARTWORK")
|
|
t:SetWidth(20)
|
|
t:SetHeight(20)
|
|
t:SetPoint("LEFT", roles, "RIGHT", 4 + (i - 1) * 24, 0)
|
|
l.roleIcons[i] = t
|
|
end
|
|
local pending = l:CreateTexture(nil, "ARTWORK")
|
|
ForeverUI.SetAtlas(pending, "groupfinder-waitdot", true)
|
|
pending:SetWidth(18)
|
|
pending:SetHeight(17)
|
|
pending:SetPoint("RIGHT", l, "RIGHT", -2 - 16, -1)
|
|
l.pending = pending
|
|
local count = l:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
|
|
count:SetWidth(22)
|
|
count:SetHeight(18)
|
|
count:SetJustifyH("RIGHT")
|
|
count:SetPoint("RIGHT", pending, "LEFT", -1, 0)
|
|
l.count = count
|
|
|
|
-- Hover and tooltip (R.tooltip, from the client); click as LFRBrowseButton_OnClick:
|
|
-- select, or clear the current selection
|
|
l:SetScript("OnEnter", function(self)
|
|
if not self.selected:IsShown() then self.hover:Show() end
|
|
if self.record then R.tooltip(self, self.record) end
|
|
end)
|
|
l:SetScript("OnLeave", function(self)
|
|
self.hover:Hide()
|
|
GameTooltip:Hide()
|
|
end)
|
|
l:SetScript("OnClick", function(self)
|
|
local e = self.record
|
|
if self.isMe or not e then return end
|
|
if R.choice and R.choice.name == e.name then
|
|
PlaySound("igMainMenuOptionCheckBoxOff")
|
|
R.choice = nil
|
|
else
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
R.choice = { name = e.name, group = e.members > 0 }
|
|
end
|
|
F.request()
|
|
end)
|
|
end
|
|
|
|
-- LFRBrowseButton_OnEnter, on the collected data
|
|
function R.tooltip(l, e)
|
|
local red = RED_FONT_COLOR or { r = 1, g = 0.1, b = 0.1 }
|
|
local green = GREEN_FONT_COLOR or { r = 0.1, g = 1, b = 0.1 }
|
|
local white = HIGHLIGHT_FONT_COLOR or { r = 1, g = 1, b = 1 }
|
|
local role = "Interface" .. SEP .. "LFGFrame" .. SEP .. "LFGRole"
|
|
GameTooltip:SetOwner(l, "ANCHOR_RIGHT", 27, -37)
|
|
if e.members > 0 then
|
|
GameTooltip:AddLine(txt("LOOKING_FOR_RAID"))
|
|
GameTooltip:AddLine(e.name)
|
|
GameTooltip:AddTexture(role, 0, 0.25, 0, 1)
|
|
GameTooltip:AddLine(format(txt("LFM_NUM_RAID_MEMBER_TEMPLATE"), e.members))
|
|
GameTooltip:AddTexture("")
|
|
local title = false
|
|
for _, m in ipairs(e.group) do
|
|
if m.link then
|
|
if not title then
|
|
title = true
|
|
GameTooltip:AddLine("\n" .. txt("IMPORTANT_PEOPLE_IN_GROUP"))
|
|
end
|
|
if m.link == "ignored" then
|
|
GameTooltip:AddDoubleLine(m.name, txt("IGNORED"), red.r, red.g, red.b, red.r, red.g, red.b)
|
|
elseif m.link == "friend" then
|
|
GameTooltip:AddDoubleLine(m.name, txt("FRIEND"), green.r, green.g, green.b, green.r, green.g, green.b)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
GameTooltip:AddLine(e.name)
|
|
GameTooltip:AddLine(format(txt("FRIENDS_LEVEL_TEMPLATE"), e.level or 0, e.localizedClass or ""))
|
|
end
|
|
if e.comment and e.comment ~= "" then
|
|
GameTooltip:AddLine("\n" .. e.comment, white.r, white.g, white.b, 1)
|
|
end
|
|
if e.members == 0 then
|
|
GameTooltip:AddLine("\n" .. txt("LFG_TOOLTIP_ROLES"))
|
|
if e.tank then GameTooltip:AddLine(txt("TANK")) GameTooltip:AddTexture(role, 0.5, 0.75, 0, 1) end
|
|
if e.healer then GameTooltip:AddLine(txt("HEALER")) GameTooltip:AddTexture(role, 0.75, 1, 0, 1) end
|
|
if e.damage then GameTooltip:AddLine(txt("DAMAGER")) GameTooltip:AddTexture(role, 0.25, 0.5, 0, 1) end
|
|
end
|
|
if e.bossKilled > 0 then
|
|
GameTooltip:AddLine("\n" .. txt("BOSSES"))
|
|
for _, b in ipairs(e.boss) do
|
|
if b.killed then
|
|
GameTooltip:AddDoubleLine(b.name, txt("BOSS_DEAD"), red.r, red.g, red.b, red.r, red.g, red.b)
|
|
else
|
|
GameTooltip:AddDoubleLine(b.name, txt("BOSS_ALIVE"), green.r, green.g, green.b, green.r, green.g, green.b)
|
|
end
|
|
end
|
|
elseif e.members > 0 and e.bossTotal > 0 then
|
|
GameTooltip:AddLine("\n" .. txt("ALL_BOSSES_ALIVE"))
|
|
end
|
|
GameTooltip:Show()
|
|
end
|
|
|
|
local MICRO_ROLES = { "groupfinder-icon-role-micro-tank", "groupfinder-icon-role-micro-heal",
|
|
"groupfinder-icon-role-micro-dps" }
|
|
|
|
-- Sets fs color from c, given as { r, g, b } or { r =, g =, b = }
|
|
local function colors(fs, c)
|
|
fs:SetTextColor(c[1] or c.r, c[2] or c.g, c[3] or c.b)
|
|
end
|
|
|
|
-- Fills result row l with R.listed_entries[index]
|
|
local function fillResult(l, index)
|
|
local e = R.listed_entries[index]
|
|
l.record = e
|
|
local name, level, class = e.name, e.level, e.className
|
|
l.isMe = name == UnitName("player")
|
|
local group = e.members > 0
|
|
-- The name is anchored on the row, not on the leader icon; at most 228 wide (NameMaxWidth)
|
|
l.name:ClearAllPoints()
|
|
if group then
|
|
l.leader:Show()
|
|
l.name:SetPoint("TOPLEFT", l, "TOPLEFT", 8 + 24, -4 - 5)
|
|
l.level:Hide()
|
|
l.className:Hide()
|
|
else
|
|
l.leader:Hide()
|
|
l.name:SetPoint("TOPLEFT", l, "TOPLEFT", 8 + 1, -4 - 5)
|
|
l.level:SetText(txt("LEVEL_ABBR") .. " " .. (level or ""))
|
|
l.level:Show()
|
|
local a = class and ForeverUI.AtlasEntry("groupfinder-icon-class-" .. string.lower(class))
|
|
if a then
|
|
ForeverUI.SetAtlas(l.className, "groupfinder-icon-class-" .. string.lower(class), true)
|
|
l.className:Show()
|
|
else
|
|
l.className:Hide()
|
|
end
|
|
end
|
|
l.name:SetWidth(0)
|
|
l.name:SetText(name or "")
|
|
if (l.name:GetStringWidth() or 0) > 228 then l.name:SetWidth(228) end
|
|
-- Bottom line: the comment, else the zone
|
|
l.down:SetText((e.comment and e.comment ~= "") and e.comment or (e.zone or ""))
|
|
-- Right side
|
|
if group then
|
|
l.roles:Hide()
|
|
for _, t in ipairs(l.roleIcons) do t:Hide() end
|
|
l.pending:Show()
|
|
l.count:SetText(e.members)
|
|
l.count:Show()
|
|
else
|
|
l.pending:Hide()
|
|
l.count:Hide()
|
|
l.roles:Show()
|
|
local n = 0
|
|
for i, yes in ipairs({ e.tank and true or false, e.healer and true or false, e.damage and true or false }) do
|
|
if yes then
|
|
n = n + 1
|
|
ForeverUI.SetAtlas(l.roleIcons[n], MICRO_ROLES[i], true)
|
|
l.roleIcons[n]:Show()
|
|
end
|
|
end
|
|
for i = n + 1, 3 do l.roleIcons[i]:Hide() end
|
|
end
|
|
-- Colors: class color; the player themself is gray and inactive
|
|
local cc = (class and RAID_CLASS_COLORS and RAID_CLASS_COLORS[class]) or NORMAL_FONT_COLOR or { r = 1, g = 0.82, b = 0 }
|
|
if l.isMe then
|
|
colors(l.name, M.gray)
|
|
colors(l.level, M.gray)
|
|
colors(l.down, M.gray)
|
|
l.className:SetDesaturated(true)
|
|
else
|
|
colors(l.name, cc)
|
|
local g = GRAY_FONT_COLOR or { r = 0.5, g = 0.5, b = 0.5 }
|
|
colors(l.level, g)
|
|
colors(l.down, g)
|
|
l.className:SetDesaturated(false)
|
|
end
|
|
if R.choice and R.choice.name == name then
|
|
l.selected:Show()
|
|
l.hover:Hide()
|
|
else
|
|
l.selected:Hide()
|
|
end
|
|
end
|
|
|
|
local function buildBrowse(p)
|
|
local f = F.frame
|
|
local frameBox = F.buildFrameBox(p, "ForeverUIGroupFinderBrowseInset", M.browseY, false)
|
|
buildMenu(p)
|
|
buildRefresh(p)
|
|
local S = ForeverUI.Social
|
|
local list = S.createList(p, "ForeverUIGroupFinderBrowseList", M.result, createResult, fillResult)
|
|
list:SetFrameLevel(frameBox:GetFrameLevel() + 1)
|
|
list.bar:SetFrameLevel(frameBox:GetFrameLevel() + 2)
|
|
list:FollowBar({ "TOPLEFT", f, "TOPLEFT", G.listX1, M.resultsY },
|
|
{ "BOTTOMRIGHT", f, "BOTTOMRIGHT", M.resultsX2, G.listY2 }, M.resultsNoBar)
|
|
list.bar:ClearAllPoints()
|
|
list.bar:SetPoint("TOPLEFT", list, "TOPRIGHT", 2, -4)
|
|
list.bar:SetPoint("BOTTOMLEFT", list, "BOTTOMRIGHT", 2, 2)
|
|
R.results = list
|
|
|
|
local message = ForeverUI.CreatePanelButton(p, txt("SEND_MESSAGE"), G.backL, G.buttonH,
|
|
"ForeverUIGroupFinderRaidMessageButton", "GameFontNormal")
|
|
message:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", G.buttonSide, G.buttonBottom)
|
|
-- LFRBrowseFrameSendMessageButton: ChatFrame_SendTell
|
|
message:SetScript("OnClick", function()
|
|
if R.choice then ChatFrame_SendTell(R.choice.name) end
|
|
end)
|
|
R.message = message
|
|
local inviteButton = ForeverUI.CreatePanelButton(p, txt("INVITE"), G.postW, G.buttonH,
|
|
"ForeverUIGroupFinderRaidInviteButton", "GameFontNormal")
|
|
inviteButton:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -G.buttonSide, G.buttonBottom)
|
|
-- LFRBrowseFrameInviteButton: InviteUnit
|
|
inviteButton:SetScript("OnClick", function()
|
|
if R.choice then InviteUnit(R.choice.name) end
|
|
end)
|
|
R.inviteButton = inviteButton
|
|
end
|
|
|
|
local function updateBrowse()
|
|
-- A search made elsewhere (before us, or left by the client after 40 s): ours follows
|
|
-- the server's
|
|
local searching = SearchLFGGetJoinedID and SearchLFGGetJoinedID()
|
|
-- A search just started waits for the server answer
|
|
local recent = R.search and R.search.fromIndex and GetTime() - R.search.fromIndex < 5
|
|
if not searching and not recent then
|
|
R.search = nil
|
|
elseif searching and not R.search then
|
|
R.search = { name = nameOf(searching), ids = { searching } }
|
|
end
|
|
R.menu.text:SetText(R.search and R.search.name or txt("NONE"))
|
|
R.listed_entries = R.collectListed()
|
|
-- A selection no longer listed is dropped, as in the client
|
|
if R.choice then
|
|
local found = false
|
|
for _, e in ipairs(R.listed_entries) do
|
|
if e.name == R.choice.name then found = true break end
|
|
end
|
|
if not found then R.choice = nil end
|
|
end
|
|
R.results:Update(#R.listed_entries)
|
|
-- LFRBrowse_UpdateButtonStates
|
|
local c = R.choice
|
|
R.message:Activate(c ~= nil and c.name ~= UnitName("player"))
|
|
R.inviteButton:Activate(c ~= nil and c.name ~= UnitName("player") and not c.group and CanGroupInvite() and true or false)
|
|
end
|
|
|
|
-- ---------------------------------------------------------------- Client panel
|
|
|
|
-- Silences the client panel as for dungeons: its icon, tabs and close button hide;
|
|
-- both contents stay shown, transparent, under our window.
|
|
local function suppressClient()
|
|
local p = LFRParentFrame
|
|
for _, r in ipairs({ p:GetRegions() }) do r:Hide() end
|
|
for _, c in ipairs({ p:GetChildren() }) do
|
|
if c ~= F.frame and c ~= LFRQueueFrame and c ~= LFRBrowseFrame then c:Hide() end
|
|
end
|
|
p:EnableMouse(false)
|
|
for _, c in ipairs({ LFRQueueFrame, LFRBrowseFrame }) do
|
|
c:SetAlpha(0)
|
|
c:ClearAllPoints()
|
|
c:SetPoint("TOPLEFT", F.frame, "TOPLEFT", 0, 0)
|
|
c:SetWidth(p:GetWidth())
|
|
c:SetHeight(p:GetHeight())
|
|
end
|
|
end
|
|
|
|
-- ---------------------------------------------------------------- Page
|
|
|
|
-- Bottom tabs, as in the Social window; each maps to a panel and a client tab
|
|
-- (1: sign-up, 2: browse)
|
|
local TABS = {
|
|
{ text = "LIST_MY_GROUP", client = 1 },
|
|
{ text = "JOIN", client = 2 },
|
|
}
|
|
|
|
-- Shows the panel of the client's active tab and selects the matching bottom tab
|
|
function R.showPanel()
|
|
local n = (LFRParentFrame.activeTab == 2) and 2 or 1
|
|
R.panel = n
|
|
local S = ForeverUI.Social
|
|
for i, o in ipairs(R.tabs) do
|
|
S.selectTab(o, i == n, true)
|
|
o:SetWidth(S.tabWidth(o))
|
|
end
|
|
if n == 1 then
|
|
R.inscription:Show()
|
|
R.browser:Hide()
|
|
else
|
|
R.browser:Show()
|
|
R.inscription:Hide()
|
|
end
|
|
end
|
|
|
|
local function buildPage(f)
|
|
local p = CreateFrame("Frame", "ForeverUIGroupFinderRaid", f)
|
|
p:SetAllPoints(f)
|
|
p:Hide()
|
|
R.page = p
|
|
R.inscription = CreateFrame("Frame", "ForeverUIGroupFinderRaidQueue", p)
|
|
R.inscription:SetAllPoints(f)
|
|
R.browser = CreateFrame("Frame", "ForeverUIGroupFinderRaidBrowse", p)
|
|
R.browser:SetAllPoints(f)
|
|
buildSignUp(R.inscription)
|
|
buildBrowse(R.browser)
|
|
|
|
local S = ForeverUI.Social
|
|
R.tabs = {}
|
|
local previous
|
|
for i, def in ipairs(TABS) do
|
|
local o = S.createTab(p, "ForeverUIGroupFinderRaidTab" .. i, false)
|
|
o:SetText(txt(def.text))
|
|
if previous then
|
|
o:SetPoint("TOPLEFT", previous, "TOPRIGHT", M.tabGap, 0)
|
|
else
|
|
o:SetPoint("TOPLEFT", f, "BOTTOMLEFT", M.tabX, M.tabY)
|
|
end
|
|
-- The client tab does the rest: LFRFrame_SetActiveTab
|
|
o:SetScript("OnClick", function()
|
|
PlaySound("igCharacterInfoTab")
|
|
LFRFrame_SetActiveTab(def.client)
|
|
end)
|
|
R.tabs[i] = o
|
|
previous = o
|
|
end
|
|
end
|
|
|
|
local function updatePage()
|
|
if R.panel == 2 then updateBrowse() else updateSignUp() end
|
|
end
|
|
|
|
-- Shows our window over LFRParentFrame when the client opens it
|
|
local function open()
|
|
if LFDParentFrame and LFDParentFrame:IsShown() then HideUIPanel(LFDParentFrame) end
|
|
F.attach(LFRParentFrame)
|
|
suppressClient()
|
|
-- Both client contents at once: sign-up and browse
|
|
LFRQueueFrame:Show()
|
|
LFRBrowseFrame:Show()
|
|
F.frame:Show()
|
|
R.showPanel()
|
|
F.show("raid")
|
|
end
|
|
|
|
buildPage(F.frame)
|
|
F.registerPage("raid", R.page, updatePage)
|
|
LFRParentFrame:HookScript("OnShow", open)
|
|
LFRParentFrame:HookScript("OnHide", function()
|
|
if F.frame:GetParent() == LFRParentFrame then F.frame:Hide() end
|
|
end)
|
|
-- The client shows only one of its two contents: show the other again, and our panel
|
|
-- follows its tab
|
|
if LFRFrame_SetActiveTab then
|
|
hooksecurefunc("LFRFrame_SetActiveTab", function()
|
|
if LFRParentFrame:IsShown() then
|
|
LFRQueueFrame:Show()
|
|
LFRBrowseFrame:Show()
|
|
if F.frame:GetParent() == LFRParentFrame then
|
|
R.showPanel()
|
|
F.update()
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
for _, name in ipairs({ "LFRQueueFrameSpecificList_Update", "LFRQueueFrameFindGroupButton_Update",
|
|
"LFRBrowseFrameList_Update", "LFRBrowse_UpdateButtonStates" }) do
|
|
if _G[name] then hooksecurefunc(name, F.request) end
|
|
end
|
|
-- Server answer: collected for the searched raid
|
|
local listener = CreateFrame("Frame")
|
|
listener:RegisterEvent("UPDATE_LFG_LIST")
|
|
listener:SetScript("OnEvent", function()
|
|
local searching = SearchLFGGetJoinedID and SearchLFGGetJoinedID()
|
|
if searching then R.collect(searching) end
|
|
F.request()
|
|
end)
|
|
ForeverUI.WindowStack.register("raidFinder", LFRParentFrame, function()
|
|
local z = { F.frame }
|
|
for _, o in ipairs(F.tabs) do z[#z + 1] = o end
|
|
-- The bottom tabs lie outside the window
|
|
for _, o in ipairs(R.tabs) do z[#z + 1] = o end
|
|
return z
|
|
end)
|