local _, Cell = ... local L = Cell.L local F = Cell.funcs local P = Cell.pixelPerfectFuncs local A = Cell.animations local LibTranslit = LibStub("LibTranslit-1.0") local LCG = LibStub("LibCustomGlow-1.0") local GetRaidRosterInfo = GetRaidRosterInfo local SwapRaidSubgroup = SwapRaidSubgroup local SetRaidSubgroup = SetRaidSubgroup local LoadRoster, UpdateRoster local UpdateMode local PremadeSwap, PremadeSet, PremadeApply, ProcessNext local groups = {} -- contains girds local changes = {} -- store subgroup changed member indices local queue -- local premadeGroups = {} -- contains member nums of each sub group local isInstantMode = true local isProcessing = false local modeBtn, assistantCB, processingFrame, progressBar, combatTips local function Reset(reload) -- print("RESET", reload) queue = nil isInstantMode = true isProcessing = false wipe(changes) UpdateMode() if reload then LoadRoster() end end ------------------------------------------------- -- raid roster frame ------------------------------------------------- local raidRosterFrame = Cell:CreateFrame("CellRaidRosterFrame", Cell.frames.mainFrame, 405, 230) Cell.frames.raidRosterFrame = raidRosterFrame raidRosterFrame:SetFrameStrata("DIALOG") raidRosterFrame:SetFrameLevel(5) local function CreateWidgets() -- mode modeBtn = Cell:CreateButton(raidRosterFrame, L["Instant Mode"], "accent", {127, 17}) modeBtn:SetTexture("Interface\\AddOns\\Cell\\Media\\Icons\\instant", {13, 13}, {"LEFT", 4, 0}) modeBtn:RegisterForClicks("LeftButtonUp", "RightButtonUp") modeBtn:SetScript("OnClick", function(self, button) if button == "LeftButton" then -- switch mode / apply changes if isInstantMode then isInstantMode = false UpdateMode() else if not isProcessing then isProcessing = true PremadeApply() end end else -- discard changes if isProcessing then processingFrame:Hide() else Reset(true) end end end) Cell:SetTooltips(modeBtn, "ANCHOR_TOPRIGHT", 0, 2, "|cffff2727EXPERIMENTAL|r", L["No support for rearrangement of members within a same subgroup"], L["No guarantee of the order of members in each subgroup"], "|cffffb5c5"..L["Left-Click"]..":|r "..L["change mode / apply changes"], "|cffffb5c5"..L["Right-Click"]..":|r "..L["discard changes"] ) -- SetEveryoneIsAssistant assistantCB = Cell:CreateCheckButton(raidRosterFrame, "|TInterface\\GroupFrame\\UI-Group-AssistantIcon:16:16|t", function(checked) SetEveryoneIsAssistant(checked) end) assistantCB:SetPoint("BOTTOMRIGHT", -25, 5) local tips = Cell:CreateScrollTextFrame(raidRosterFrame, "|cffb7b7b7"..L["raidRosterTips"], 0.02, nil, 2) tips:SetPoint("BOTTOMLEFT", raidRosterFrame, 5, 2) tips:SetPoint("RIGHT", assistantCB, "LEFT", -5, 0) end local function UpdateModeBtnPosition() local anchor = Cell.vars.currentLayoutTable.main.anchor modeBtn:ClearAllPoints() if anchor == "TOPLEFT" then modeBtn:SetPoint("BOTTOMRIGHT", raidRosterFrame, "TOPRIGHT", 0, 4) elseif anchor == "TOPRIGHT" then modeBtn:SetPoint("BOTTOMLEFT", raidRosterFrame, "TOPLEFT", 0, 4) elseif anchor == "BOTTOMLEFT" then modeBtn:SetPoint("TOPRIGHT", raidRosterFrame, "BOTTOMRIGHT", 0, -4) elseif anchor == "BOTTOMRIGHT" then modeBtn:SetPoint("TOPLEFT", raidRosterFrame, "BOTTOMLEFT", 0, -4) end end UpdateMode = function() -- update button if isInstantMode then raidRosterFrame:RegisterEvent("GROUP_ROSTER_UPDATE") modeBtn:SetText(L["Instant Mode"]) modeBtn.tex:SetTexture("Interface\\AddOns\\Cell\\Media\\Icons\\instant") LCG.PixelGlow_Stop(modeBtn) else raidRosterFrame:UnregisterEvent("GROUP_ROSTER_UPDATE") modeBtn:SetText(L["Premade Mode"]) modeBtn.tex:SetTexture("Interface\\AddOns\\Cell\\Media\\Icons\\premade") LCG.PixelGlow_Start(modeBtn, Cell:GetAccentColorTable(1), 12, 0.25, 10, 1) end end local function CreateProcessingFrame() -- processing processingFrame = CreateFrame("Frame", nil, raidRosterFrame, "BackdropTemplate") processingFrame:SetPoint("TOPLEFT", P:Scale(1), P:Scale(-1)) processingFrame:SetPoint("BOTTOMRIGHT", P:Scale(-1), P:Scale(1)) Cell:StylizeFrame(processingFrame, {0.15, 0.15, 0.15, 0.7}, {0, 0, 0, 0}) processingFrame:SetFrameLevel(raidRosterFrame:GetFrameLevel()+30) processingFrame:EnableMouse(true) processingFrame:Hide() processingFrame:SetScript("OnShow", function() processingFrame:RegisterEvent("GROUP_ROSTER_UPDATE") ProcessNext() end) processingFrame:SetScript("OnHide", function() processingFrame:Hide() processingFrame:UnregisterAllEvents() Reset(true) end) processingFrame:SetScript("OnEvent", function(self, event) if event == "PLAYER_REGEN_ENABLED" then processingFrame:UnregisterEvent("PLAYER_REGEN_ENABLED") combatTips:Hide() end ProcessNext() end) A:CreateFadeOut(processingFrame, 1, 0, 0.5, 0.5) -- progress bar progressBar = Cell:CreateStatusBar(nil, processingFrame, 1, 1, 100, true, nil, true, "Interface\\AddOns\\Cell\\Media\\statusbar", Cell:GetAccentColorTable()) progressBar:SetPoint("TOPLEFT", 10, -103) progressBar:SetPoint("BOTTOMRIGHT", -10, 102) -- combat tips combatTips = processingFrame:CreateFontString(nil, "OVERLAY", "CELL_FONT_WIDGET") combatTips:SetPoint("TOP", progressBar, "BOTTOM", 0, -5) combatTips:SetTextColor(1, 0.2, 0.2) combatTips:SetText(L["Waiting for combat to end..."]) combatTips:Hide() end ------------------------------------------------- -- premade ------------------------------------------------- PremadeSwap = function(grid1, grid2) if grid1.subgroup == grid2.subgroup and grid1._subgroup == grid2._subgroup then -- NOTE: in same group, don't swap return end local tempPoint1 = grid1._point1 or grid1.point1 local tempPoint2 = grid1._point2 or grid1.point2 grid1._point1 = grid2._point1 or grid2.point1 grid1._point2 = grid2._point2 or grid2.point2 grid2._point1 = tempPoint1 grid2._point2 = tempPoint2 local anchor1 = grid1._subgroup and groups[grid1._subgroup] or groups[grid1.subgroup] local anchor2 = grid2._subgroup and groups[grid2._subgroup] or groups[grid2.subgroup] grid1._anchor = anchor2 grid2._anchor = anchor1 grid1:ClearAllPoints() grid1:SetPoint(grid1._point1[1], anchor2, grid1._point1[2], grid1._point1[3]) grid1:SetPoint(grid1._point2[1], anchor2, grid1._point2[2], grid1._point2[3]) grid2:ClearAllPoints() grid2:SetPoint(grid2._point1[1], anchor1, grid2._point1[2], grid2._point1[3]) grid2:SetPoint(grid2._point2[1], anchor1, grid2._point2[2], grid2._point2[3]) local subgroup = grid1._subgroup or grid1.subgroup grid1._subgroup = grid2._subgroup or grid2.subgroup grid2._subgroup = subgroup local index = grid1._index or grid1.index grid1._index = grid2._index or grid2.index grid2._index = index if grid1.hasUnit then if grid1._subgroup ~= grid1.subgroup then changes[grid1.fullName] = {grid1._subgroup, grid1._index, grid2.fullName} else changes[grid1.fullName] = nil end end if grid2.hasUnit then if grid2._subgroup ~= grid2.subgroup then changes[grid2.fullName] = {grid2._subgroup, grid2._index, grid1.fullName} else changes[grid2.fullName] = nil end end end PremadeSet = function(grid, emptyGrid) -- premadeGroups[grid._subgroup or grid.subgroup] = premadeGroups[grid._subgroup or grid.subgroup] - 1 -- premadeGroups[emptyGrid._subgroup or emptyGrid.subgroup] = premadeGroups[emptyGrid._subgroup or emptyGrid.subgroup] + 1 PremadeSwap(grid, emptyGrid) end ProcessNext = function() -- print("ProcessNext", queue and queue[1] or nil) if queue and queue[1] then local noAction = true local next = queue[1] local fromIndex, fromSubgroup = F:GetRaidInfoByName(next) local targetSubgroup = changes[next][1] local targetIndex = changes[next][2] -- index in subgroup, not raidIndex local targetPlayer = changes[next][3] if fromIndex then -- "next" still in raid local targetPlayerTarget = changes[targetPlayer] and changes[targetPlayer][3] or nil local toIndex, toName = F:GetRaidInfoBySubgroupIndex(targetSubgroup, targetIndex) -- print(next, "raidIndex:", fromIndex, "subgroup:", fromSubgroup.."->"..targetSubgroup, "targetIndex:", targetIndex, "targetPlayer:", targetPlayer, targetPlayerTarget) if toIndex and targetPlayerTarget == next then -- NOTE: unit to be swapped with exists, and requires a swap with "next" if fromIndex ~= toIndex then if not InCombatLockdown() then noAction = false SwapRaidSubgroup(fromIndex, toIndex) else processingFrame:RegisterEvent("PLAYER_REGEN_ENABLED") combatTips:Show() return end end else -- NOTE: non-full subgroup, set if fromSubgroup ~= targetSubgroup and F:GetNumSubgroupMembers(targetSubgroup) < 5 then if not InCombatLockdown() then noAction = false SetRaidSubgroup(fromIndex, targetSubgroup) else processingFrame:RegisterEvent("PLAYER_REGEN_ENABLED") combatTips:Show() return end end end end tremove(queue, 1) progressBar.value = progressBar.value + 1 progressBar:SetSmoothedValue(progressBar.value) -- NOTE: run next immediately if noAction then ProcessNext() end else processingFrame:FadeOut() end end PremadeApply = function() queue = F:GetKeys(changes) local n = #queue if n ~= 0 then progressBar:SetMaxValue(n) progressBar:SetValue(0) progressBar.value = 0 -- texplore(queue) processingFrame:Show() else Reset(true) end end ------------------------------------------------- -- roster ------------------------------------------------- local movingGrid local function CreateRaidRosterGrid(parent, index) local grid = CreateFrame("Button", parent:GetName().."Unit"..index, parent, "BackdropTemplate") P:Size(grid, 100, 17) Cell:StylizeFrame(grid, {0.1, 0.1, 0.1, 0.5}) grid.color = {0.5, 0.5, 0.5} grid:SetFrameLevel(7) local roleIconBg = grid:CreateTexture(nil, "BORDER") roleIconBg:SetPoint("TOPLEFT", 2, -2) roleIconBg:SetSize(13, 13) roleIconBg:SetColorTexture(0, 0, 0, 1) local roleIcon = grid:CreateTexture(nil, "ARTWORK") roleIcon:SetPoint("TOPLEFT", roleIconBg, P:Scale(1), P:Scale(-1)) roleIcon:SetPoint("BOTTOMRIGHT", roleIconBg, P:Scale(-1), P:Scale(1)) roleIcon:SetTexCoord(0.1, 0.9, 0.1, 0.9) local nameText = grid:CreateFontString(nil, "OVERLAY", "CELL_FONT_WIDGET") nameText:SetPoint("LEFT", roleIcon, "RIGHT", 2, 0) nameText:SetPoint("RIGHT", -2, 0) nameText:SetWordWrap(false) nameText:SetJustifyH("LEFT") -- click grid:RegisterForClicks("RightButtonDown") grid:SetScript("OnClick", function() if IsAltKeyDown() then UninviteUnit(grid.name) else if not UnitIsGroupLeader("player") then return end if UnitIsGroupLeader(grid.unit) then return end if UnitIsGroupAssistant(grid.unit) then DemoteAssistant(grid.unit) else PromoteToAssistant(grid.unit) end end end) -- drag grid:SetMovable(true) grid:RegisterForDrag("LeftButton") grid:SetScript("OnDragStart", function() grid:SetFrameLevel(9) grid:StartMoving() grid:SetUserPlaced(false) grid:SetBackdropBorderColor(unpack(grid.color)) grid:SetBackdropColor(0.1, 0.1, 0.1, 0.9) grid.isMoving = true movingGrid = grid end) grid:SetScript("OnDragStop", function() grid:SetFrameLevel(7) grid:StopMovingOrSizing() grid:ClearAllPoints() if grid._anchor then grid:SetPoint(grid._point1[1], grid._anchor, grid._point1[2], grid._point1[3]) grid:SetPoint(grid._point2[1], grid._anchor, grid._point2[2], grid._point2[3]) else grid:SetPoint(unpack(grid.point1)) grid:SetPoint(unpack(grid.point2)) end grid:SetBackdropBorderColor(0, 0, 0, 1) grid:SetBackdropColor(0.1, 0.1, 0.1, 0.5) grid.isMoving = nil end) -- swap grid:SetScript("OnShow", function() grid:RegisterEvent("GLOBAL_MOUSE_UP") end) grid:SetScript("OnHide", function() grid:UnregisterEvent("GLOBAL_MOUSE_UP") end) grid:SetScript("OnEvent", function(self, event) if movingGrid and movingGrid ~= self and self:IsMouseOver() then if isInstantMode then if not InCombatLockdown() then if self.hasUnit then -- print("SWAP "..self:GetName().." WITH "..movingGrid:GetName()) SwapRaidSubgroup(movingGrid.raidIndex, self.raidIndex) else SetRaidSubgroup(movingGrid.raidIndex, self.subgroup) end end else if self.hasUnit then PremadeSwap(movingGrid, self) else PremadeSet(movingGrid, self) end end movingGrid = nil end end) -- onupdate grid:SetScript("OnUpdate", function() if not grid.isMoving then if grid:IsMouseOver() then grid:SetBackdropColor(grid.color[1], grid.color[2], grid.color[3], 0.2) else grid:SetBackdropColor(0.1, 0.1, 0.1, 0.5) end end end) function grid:Update() nameText:SetText(grid.name) nameText:SetTextColor(unpack(grid.color)) roleIcon:Show() roleIconBg:Show() if role == "NONE" then roleIcon:SetTexture(134400) else roleIcon:SetTexture("Interface\\AddOns\\Cell\\Media\\Roles\\"..grid.role) end if grid.isLeader then roleIconBg:SetColorTexture(1, 0.84, 0, 1) elseif grid.isAssistant then roleIconBg:SetColorTexture(0.7, 0.7, 0.7, 1) else roleIconBg:SetColorTexture(0, 0, 0, 1) end end function grid:Reset() F:RemoveElementsByKeys(grid, "hasUnit", "raidIndex", "unit", "fullName", "name", "role", "isLeader", "isAssistant", "_subgroup", "_index", "_point1", "_point2", "_anchor" -- premade temps ) grid.color[1], grid.color[2], grid.color[3] = 0.5, 0.5, 0.5 nameText:SetText("") nameText:SetTextColor(1, 1, 1) roleIconBg:SetColorTexture(0, 0, 0, 1) roleIconBg:Hide() roleIcon:Hide() grid:ClearAllPoints() grid:SetPoint(unpack(grid.point1)) grid:SetPoint(unpack(grid.point2)) grid:EnableMouse(false) end function grid:Set(raidIndex) local name, _, subgroup, _, _, classFileName, _, _, _, _, _, combatRole = GetRaidRosterInfo(raidIndex) if not name then -- unknown target, retry C_Timer.After(0.5, function() grid:Set(raidIndex) end) return end -- save grid.fullName = name -- contains server name for cross-realm players if string.find(name, "-") then name = strsplit("-", name) end if CellDB["general"]["translit"] then name = LibTranslit:Transliterate(name) end grid.hasUnit = true grid.raidIndex = raidIndex grid.unit = "raid"..raidIndex grid.name = name grid.role = combatRole grid.color[1], grid.color[2], grid.color[3] = F:GetClassColor(classFileName) grid.isLeader = UnitIsGroupLeader(grid.unit) grid.isAssistant = UnitIsGroupAssistant(grid.unit) -- update grid:Update() grid:EnableMouse(true) end return grid end local function CreateRaidRosterGroup(parent, groupIndex) local group = CreateFrame("Frame", parent:GetName().."Subgroup"..groupIndex, parent, "BackdropTemplate") P:Size(group, 95, 81) Cell:StylizeFrame(group, {0.1, 0.1, 0.1, 0.5}) local headerText = group:CreateFontString(nil, "OVERLAY", "CELL_FONT_WIDGET") headerText:SetPoint("BOTTOM", group, "TOP", 0, 1) headerText:SetText("|cFFEEC900"..GROUP.." "..groupIndex) for i = 1, 5 do group[i] = CreateRaidRosterGrid(group, i) group[i].point1 = {"TOPLEFT", 0, -(i-1)*16} group[i]:SetPoint(unpack(group[i].point1)) group[i].point2 = {"TOPRIGHT", 0, -(i-1)*16} group[i]:SetPoint(unpack(group[i].point2)) group[i].subgroup = groupIndex group[i].index = i end group.numMembers = 0 function group:Reset() group.numMembers = 0 for i = 1, 5 do group[i]:Reset() end end function group:Insert(raidIndex) group.numMembers = group.numMembers + 1 group[group.numMembers]:Set(raidIndex) end return group end local function CreateRosterContainer() local rosterContainer = CreateFrame("Frame", "CellRaidRosterFrameContainer", raidRosterFrame) rosterContainer:SetPoint("TOPLEFT", 5, -5) rosterContainer:SetPoint("BOTTOMRIGHT", raidRosterFrame, "TOPRIGHT", -5, -207) for i = 1, 8 do groups[i] = CreateRaidRosterGroup(rosterContainer, i) if i % 4 == 1 then groups[i]:SetPoint("TOPLEFT", 0, -20-(math.modf(i/4)*(groups[i]:GetHeight()+20))) else groups[i]:SetPoint("TOPLEFT", groups[i-1], "TOPRIGHT", 5, 0) end end end ------------------------------------------------- -- functions ------------------------------------------------- LoadRoster = function() if movingGrid then movingGrid:GetScript("OnDragStop")() end -- reset for i = 1, 8 do groups[i]:Reset() -- premadeGroups[i] = 0 end -- insert for i = 1, GetNumGroupMembers() do local subgroup = select(3, GetRaidRosterInfo(i)) groups[subgroup]:Insert(i) -- premadeGroups[subgroup] = premadeGroups[subgroup] + 1 end end UpdateRoster = function() end ------------------------------------------------- -- scripts ------------------------------------------------- local function CheckPermission() if UnitIsGroupLeader("player") or UnitIsGroupAssistant("player") then if raidRosterFrame.mask then raidRosterFrame.mask:Hide() end else Cell:CreateMask(raidRosterFrame, L["You don't have permission to do this"], {1, -1, -1, 1}) end end raidRosterFrame:SetScript("OnEvent", function() LoadRoster() CheckPermission() assistantCB:SetChecked(IsEveryoneAssistant()) end) raidRosterFrame:SetScript("OnShow", function() raidRosterFrame:RegisterEvent("GROUP_ROSTER_UPDATE") LoadRoster() CheckPermission() assistantCB:SetChecked(IsEveryoneAssistant()) end) raidRosterFrame:SetScript("OnHide", function() raidRosterFrame:UnregisterEvent("GROUP_ROSTER_UPDATE") Reset() end) ------------------------------------------------- -- callbacks ------------------------------------------------- local function GroupTypeChanged(groupType) raidRosterFrame:Hide() end Cell:RegisterCallback("GroupTypeChanged", "RaidRosterFrame_GroupTypeChanged", GroupTypeChanged) local function UpdateLayout(layout, which) layout = Cell.vars.currentLayoutTable if not which or which == "main-arrangement" then raidRosterFrame:ClearAllPoints() raidRosterFrame:SetPoint(layout["main"]["anchor"], Cell.frames.mainFrame) if modeBtn then UpdateModeBtnPosition() end end end Cell:RegisterCallback("UpdateLayout", "RaidRosterFrame_UpdateLayout", UpdateLayout) ------------------------------------------------- -- show ------------------------------------------------- local init function F:ShowRaidRosterFrame() if not init then init = true raidRosterFrame:UpdatePixelPerfect() CreateWidgets() CreateProcessingFrame() UpdateModeBtnPosition() CreateRosterContainer() end if raidRosterFrame:IsShown() then raidRosterFrame:Hide() else raidRosterFrame:Show() -- texplore(changes) end end