Files
moon-well-ui/data/addon/ForeverUI/GroupFinderRaid.lua
T
Adrestie 5842eb7a2b ForeverUI : code en anglais, commentaires resserres, code mort retire
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>
2026-09-29 09:15:52 +02:00

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)