PTUnitFrameGroup = {} PTUtil.SetEnvironment(PTUnitFrameGroup) PTUnitFrameGroup.__index = PTUnitFrameGroup local _G = getfenv(0) local PT = Puppeteer local util = PTUtil -- Height of the title bar above each frame group ("Party", "Raid", ...). The original was 20. HEADER_HEIGHT = 16 PTUnitFrameGroup.name = "???" PTUnitFrameGroup.raidmana = "??" PTUnitFrameGroup.healermana = "??" PTUnitFrameGroup.profile = nil PTUnitFrameGroup.container = nil PTUnitFrameGroup.borderFrame = nil PTUnitFrameGroup.header = nil PTUnitFrameGroup.label = nil PTUnitFrameGroup.manalabel = nil PTUnitFrameGroup.manaTooltipFrame = nil PTUnitFrameGroup.healermanalabel = nil PTUnitFrameGroup.healerManaTooltipFrame = nil PTUnitFrameGroup.reportManaButton = nil PTUnitFrameGroup.uis = nil PTUnitFrameGroup.units = nil PTUnitFrameGroup.petGroup = false PTUnitFrameGroup.environment = "all" -- party, raid, or all PTUnitFrameGroup.sortByRole = true PTUnitFrameGroup.moveContainer = CreateFrame("Frame", "PTUnitFrameGroupBulkMoveContainer", UIParent) PTUnitFrameGroup.moveContainer:EnableMouse(true) PTUnitFrameGroup.moveContainer:SetMovable(true) ContextMenu = PTGuiLib.Get("dropdown", UIParent) ContextMenu:SetDynamicOptions(function(addOption, level, args) addOption("text", "Lock Position", "func", function(self, gui) PuppeteerSettings.SetFrameLocked(gui.FrameGroup.name, not self.checked) end, "initFunc", function(self, gui) self.checked = PuppeteerSettings.IsFrameLocked(gui.FrameGroup.name) end) addOption("notCheckable", true, "disabled", true) addOption("text", "Open Settings", "notCheckable", true, "func", function(self, gui) PTSettingsGui.TabFrame:Show() end) end) function PTUnitFrameGroup:New(name, environment, units, petGroup, profile, sortByRole) local obj = setmetatable({name = name, environment = environment, uis = {}, units = units, petGroup = petGroup, profile = profile, sortByRole = sortByRole, -- Scratch tables reused every time the frames are sorted and positioned sortScratch = {groups = {}, sorted = {}, idSorted = {}, groupSet = {}, columns = {}}}, self) obj:Initialize() return obj end function PTUnitFrameGroup:EvaluateShown() -- Test mode shows every group (raid groups too) whatever the real group is local environment = Puppeteer.CurrentlyInRaid and "raid" or "party" if (Puppeteer.TestUI or self:CanShowInEnvironment(environment)) and self:ShowCondition() then self:Show() self:UpdateUIPositions() else self:Hide() end end function PTUnitFrameGroup:ShowCondition() if PTOptions.Hidden or PuppeteerSettings.IsFrameHidden(self.name) then return false end if not Puppeteer.TestUI and PTOptions.HideWhileSolo and (GetNumPartyMembers() == 0 and GetNumRaidMembers() == 0) then return false end for _, ui in pairs(self.uis) do if ui:IsShown() then return true end end return false end function PTUnitFrameGroup:AddUI(ui, noUpdate) self.uis[ui:GetUnit()] = ui ui:SetOwningGroup(self) if not noUpdate then self:UpdateUIPositions() end end function PTUnitFrameGroup:GetContainer() return self.container end function PTUnitFrameGroup:ResetFrameLevel() self.container:SetFrameLevel(0) self.borderFrame:SetFrameLevel(1) end function PTUnitFrameGroup:GetEnvironment() return self.environment end function PTUnitFrameGroup:CanShowInEnvironment(environment) return self.environment == "all" or self.environment == environment end function PTUnitFrameGroup:Show() self.container:Show() for _, ui in pairs(self.uis) do ui:UpdateAll() end self:UpdateReportManaButton() self:UpdateRaidMana() end function PTUnitFrameGroup:UpdateRaidMana() if self.name ~= "Raid" then return end if not PTOptions.ShowRaidMana then self.raidmana = "" self.healermana = "" self:SetFrameTitle() self:UpdateReportManaButton() return end local totalManaPct = 0 local totalManaUnits = 0 local healerManaPct = 0 local healerManaUnits = 0 for _, ui in pairs(self.uis) do if ui:IsShown() and UnitIsConnected(ui:GetUnit()) then local powerType, _, _, _, _ = UnitPowerType(ui:GetUnit()) if powerType == 0 then -- Mana local mana = ui:GetCurrentPower() local manaMax = ui:GetMaxPower() local manaPct = manaMax > 0 and ((mana / manaMax) * 100) or 0 totalManaPct = manaPct + totalManaPct totalManaUnits = totalManaUnits + 1 if ui:GetRole() == "Healer" then healerManaPct = manaPct + healerManaPct healerManaUnits = healerManaUnits + 1 end end end end if totalManaUnits > 0 then self.raidmana = string.format("%d%%", math.floor((totalManaPct / totalManaUnits))) else self.raidmana = "" end if healerManaUnits > 0 then self.healermana = string.format("%d%%", math.floor((healerManaPct / healerManaUnits))) else self.healermana = "" end self:UpdateReportManaButton() self:SetFrameTitle() end -- Power events fire constantly in a raid, so raid mana recalculation is coalesced to at most every 0.5 seconds local RAID_MANA_UPDATE_INTERVAL = 0.5 local raidManaUpdater = CreateFrame("Frame", "PTRaidManaUpdater") local pendingRaidManaGroup = nil local nextRaidManaUpdate = 0 local function RaidManaUpdater_OnUpdate() local time = GetTime() if time < nextRaidManaUpdate then return end raidManaUpdater:SetScript("OnUpdate", nil) nextRaidManaUpdate = time + RAID_MANA_UPDATE_INTERVAL local group = pendingRaidManaGroup pendingRaidManaGroup = nil if group then group:UpdateRaidMana() end end function PTUnitFrameGroup:RequestRaidManaUpdate() if self.name ~= "Raid" or not PTOptions.ShowRaidMana then return end pendingRaidManaGroup = self if not raidManaUpdater:GetScript("OnUpdate") then raidManaUpdater:SetScript("OnUpdate", RaidManaUpdater_OnUpdate) end end function PTUnitFrameGroup:ReportRaidMana() if not UnitInRaid("player") or not (IsRaidLeader() or IsRaidOfficer()) then return end if self.raidmana == "" then DEFAULT_CHAT_FRAME:AddMessage("No mana users in raid.") return end local msg = "Raid Mana: ".. self.raidmana if self.healermana ~= "" then msg = msg .. " || Healer Mana: " .. self.healermana end SendChatMessage(msg, "RAID_WARNING", nil, nil); end function PTUnitFrameGroup:Hide() self.container:Hide() end -- Used while moving frames to avoid the lag while moving over other toplevel frames function PTUnitFrameGroup:RemoveToplevel() self.container:SetToplevel(false) self.container:SetFrameStrata("HIGH") end function PTUnitFrameGroup:ApplyToplevel() self.container:SetToplevel(true) self.container:SetFrameStrata("MEDIUM") end function PTUnitFrameGroup:UpdateHeaderColor() local opacity = PuppeteerSettings.IsFrameLocked(self.name) and 0.1 or 0.5 self.header:SetBackdropColor(0, 0, 0, opacity) end function PTUnitFrameGroup:Initialize() local container = CreateFrame("Frame", "PTUnitFrameGroupContainer_"..self.name, UIParent) self.container = container container:EnableMouse(true) container:SetMovable(true) container:SetUserPlaced(false) self:ApplyToplevel() container:ClearAllPoints() local anchor, x, y = PuppeteerSettings.GetFramePosition(self.name) container:SetPoint(anchor or "TOPLEFT", UIParent, "TOPLEFT", x or 100, y or -100) container:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8"}) container:SetBackdropColor(0, 0, 0, self:GetProfile().BackgroundOpacity / 100) container:SetScript("OnMouseDown", function() local button = arg1 if button ~= "LeftButton" or container.isMoving then return end if PuppeteerSettings.IsFrameLocked(self.name) then return end container.isMoving = true if (util.GetKeyModifier() == PTOptions.FrameDrag.AltMoveKey) == PTOptions.FrameDrag.MoveAll then container:StartMoving() container:SetUserPlaced(false) -- StartMoving sets this and needs to be reverted self:RemoveToplevel() return end container.bulkMovement = true local moveContainer = PTUnitFrameGroup.moveContainer moveContainer:ClearAllPoints() moveContainer:SetPoint("TOPLEFT", 0, 0) -- If the container doesn't have a size, it doesn't move moveContainer:SetWidth(1) moveContainer:SetHeight(1) local movingGroups = {} for _, group in pairs(Puppeteer.UnitFrameGroups) do if not PuppeteerSettings.IsFrameLocked(group.name) and group:GetContainer():IsVisible() then group:RemoveToplevel() local gc = group:GetContainer() local xOffset = gc:GetLeft() local yOffset = gc:GetTop() - GetScreenHeight() gc:ClearAllPoints() gc:SetPoint("TOPLEFT", moveContainer, "TOPLEFT", xOffset, yOffset) table.insert(movingGroups, group) end end moveContainer.groups = movingGroups moveContainer:StartMoving() end) container:SetScript("OnMouseUp", function() local button = arg1 if button == "RightButton" and MouseIsOver(self.header) and self.header:IsVisible() then ContextMenu.FrameGroup = self ContextMenu:SetToggleState(false) ContextMenu:SetToggleState(true, container, container:GetWidth(), container:GetHeight()) PlaySound("igMainMenuOpen") return end if (button ~= "LeftButton" or not container.isMoving) then return end container.isMoving = false if not container.bulkMovement then container:StopMovingOrSizing() self:ApplyToplevel() util.ConvertAnchor(container, PuppeteerSettings.GetFramePosition(self.name)) return end container.bulkMovement = false local moveContainer = PTUnitFrameGroup.moveContainer moveContainer:StopMovingOrSizing() for _, group in pairs(moveContainer.groups) do group:ApplyToplevel() local gc = group:GetContainer() util.ConvertAnchor(gc, PuppeteerSettings.GetFramePosition(group.name)) end -- Prevent container from potentially blocking mouse by setting it back to 0 size moveContainer:SetWidth(0) moveContainer:SetHeight(0) end) container:SetScript("OnHide", function() if not container.isMoving then return end local prevArg = arg1 arg1 = "LeftButton" container:GetScript("OnMouseUp")() arg1 = prevArg end) local header = CreateFrame("Frame", "$parentHeader", container) self.header = header header:SetPoint("TOPLEFT", container, 0, 0) header:SetBackdrop({bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background"}) self:UpdateHeaderColor() local label = header:CreateFontString(header, "OVERLAY", "GameFontNormal") self.label = label label:SetPoint("CENTER", header, "CENTER", 0, 0) local mana = header:CreateFontString(header, "OVERLAY", "GameFontNormal") self.manalabel = mana mana:SetTextColor(0, 0.7, 1, 1) local manaTooltip = CreateFrame("Frame", nil, header) self.manaTooltipFrame = manaTooltip manaTooltip:SetAllPoints(mana) manaTooltip:EnableMouse(true) manaTooltip:RegisterForDrag("LeftButton") manaTooltip:SetScript("OnEnter", function() GameTooltip:SetOwner(manaTooltip, "ANCHOR_BOTTOM") GameTooltip:SetText("Raid Mana") GameTooltip:Show() end) manaTooltip:SetScript("OnLeave", function() GameTooltip:Hide() end) manaTooltip:SetScript("OnMouseDown", function() container:GetScript("OnMouseDown")() end) manaTooltip:SetScript("OnMouseUp", function() container:GetScript("OnMouseUp")() end) local healerMana = header:CreateFontString(header, "OVERLAY", "GameFontNormal") self.healermanalabel = healerMana healerMana:SetTextColor(0.2, 1, 0.4, 1) local healerManaTooltip = CreateFrame("Frame", nil, header) self.healerManaTooltipFrame = healerManaTooltip healerManaTooltip:SetAllPoints(healerMana) healerManaTooltip:EnableMouse(true) healerManaTooltip:RegisterForDrag("LeftButton") healerManaTooltip:SetScript("OnEnter", function() GameTooltip:SetOwner(healerManaTooltip, "ANCHOR_BOTTOM") GameTooltip:SetText("Healer Mana") GameTooltip:Show() end) healerManaTooltip:SetScript("OnLeave", function() GameTooltip:Hide() end) healerManaTooltip:SetScript("OnMouseDown", function() container:GetScript("OnMouseDown")() end) healerManaTooltip:SetScript("OnMouseUp", function() container:GetScript("OnMouseUp")() end) local reportBtn = CreateFrame("Button", "$parentReportMana", header) self.reportManaButton = reportBtn reportBtn:SetWidth(HEADER_HEIGHT - 2) reportBtn:SetHeight(HEADER_HEIGHT - 2) reportBtn:SetPoint("RIGHT", header, "RIGHT", -2, 0) reportBtn:SetNormalTexture("Interface\\Buttons\\UI-GuildButton-PublicNote-Up") reportBtn:SetHighlightTexture("Interface\\Buttons\\UI-GuildButton-PublicNote-Up") reportBtn:GetHighlightTexture():SetAlpha(0.3) reportBtn:SetScript("OnClick", function() self:ReportRaidMana() end) reportBtn:SetScript("OnEnter", function() GameTooltip:SetOwner(reportBtn, "ANCHOR_RIGHT") GameTooltip:SetText("Report raid & healer mana") GameTooltip:Show() end) reportBtn:SetScript("OnLeave", function() GameTooltip:Hide() end) reportBtn:Hide() self:SetFrameTitle() local borderFrame = CreateFrame("Frame", "$parentBorder", container) self.borderFrame = borderFrame borderFrame:SetPoint("CENTER", container, 0, 0) self:ApplyProfile() end function PTUnitFrameGroup:UpdateManaLabelLayout(headerWidth) if self.name ~= "Raid" then return end local hasButton = self.reportManaButton:IsShown() local btnSpace = hasButton and 20 or 0 local totalWidth = headerWidth or self.header:GetWidth() local availableWidth = totalWidth - btnSpace - 4 local fontSize = 12 if self.healermana ~= "" then -- Two labels sharing space need more aggressive sizing if availableWidth < 50 then fontSize = 7 elseif availableWidth < 90 then fontSize = 8 elseif availableWidth < 130 then fontSize = 9 elseif availableWidth < 170 then fontSize = 10 end else if availableWidth < 40 then fontSize = 7 elseif availableWidth < 60 then fontSize = 9 end end self.manalabel:SetFont("Fonts\\FRIZQT__.TTF", fontSize) self.healermanalabel:SetFont("Fonts\\FRIZQT__.TTF", fontSize) self.manalabel:ClearAllPoints() self.healermanalabel:ClearAllPoints() local healerVisible = self.healermanalabel:GetText() and self.healermanalabel:GetText() ~= "" if healerVisible then local halfWidth = (availableWidth - 4) / 2 self.manalabel:SetWidth(halfWidth) self.healermanalabel:SetWidth(halfWidth) self.manalabel:SetPoint("LEFT", self.header, "LEFT", 2, 0) self.healermanalabel:SetPoint("RIGHT", self.header, "RIGHT", -btnSpace - 2, 0) else self.manalabel:SetWidth(availableWidth) self.manalabel:SetPoint("CENTER", self.header, "CENTER", -btnSpace / 2, 0) end end function PTUnitFrameGroup:UpdateReportManaButton() if self.name ~= "Raid" or not self.reportManaButton then return end if UnitInRaid("player") and (IsRaidLeader() or IsRaidOfficer()) and PTOptions.ShowRaidMana then self.reportManaButton:Show() else self.reportManaButton:Hide() end end function PTUnitFrameGroup:UpdateManaLabels() if self.name ~= "Raid" or not PTOptions.ShowRaidMana then return end local hasButton = self.reportManaButton and self.reportManaButton:IsShown() local btnSpace = hasButton and 20 or 0 local availableWidth = self.header:GetWidth() - btnSpace - 4 if self.healermana ~= "" then if availableWidth >= 280 then self.manalabel:SetText("Raid: "..self.raidmana) self.healermanalabel:SetText("Heal: "..self.healermana) elseif availableWidth >= 130 then self.manalabel:SetText("R:"..self.raidmana) self.healermanalabel:SetText("H:"..self.healermana) elseif availableWidth >= 80 then self.manalabel:SetText(self.raidmana) self.healermanalabel:SetText(self.healermana) elseif availableWidth >= 50 then local r = string.gsub(self.raidmana, "%%", "") local h = string.gsub(self.healermana, "%%", "") self.manalabel:SetText(r) self.healermanalabel:SetText(h) else local r = string.gsub(self.raidmana, "%%", "") self.manalabel:SetText(r) self.healermanalabel:SetText("") end else if availableWidth >= 200 then self.manalabel:SetText("Raid: "..self.raidmana) elseif availableWidth >= 40 then self.manalabel:SetText(self.raidmana) else self.manalabel:SetText("") end self.healermanalabel:SetText("") end -- Show tooltips only when labels lack descriptive prefixes local needsTooltip = availableWidth < 130 self.manaTooltipFrame:EnableMouse(needsTooltip) self.healerManaTooltipFrame:EnableMouse(needsTooltip) self:UpdateManaLabelLayout() end function PTUnitFrameGroup:SetFrameTitle() if(self.name == "Raid") then if PTOptions.ShowRaidMana then self.label:SetText("") self:UpdateManaLabels() else self.label:SetText("Raid") self.manalabel:SetText("") self.healermanalabel:SetText("") end else self.label:SetText(self.name) self.manalabel:SetText("") self.healermanalabel:SetText("") end end function PTUnitFrameGroup:ApplyProfile() local profile = self:GetProfile() local borderFrame = self.borderFrame if profile.BorderStyle == "Tooltip" then borderFrame:SetBackdrop({edgeFile="Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 17, tile = true, tileSize = 17}) elseif profile.BorderStyle == "Dialog Box" then borderFrame:SetBackdrop({edgeFile="Interface\\DialogFrame\\UI-DialogBox-Border", edgeSize = 24, tile = true, tileSize = 24}) else borderFrame:SetBackdrop({}) end end local getn, setn = table.getn, table.setn local function clearArray(array) for i = getn(array), 1, -1 do array[i] = nil end setn(array, 0) end local function getScratchArray(arrays, index) local array = arrays[index] if array then clearArray(array) else array = {} arrays[index] = array end return array end function PTUnitFrameGroup:UpdateUIPositions() local profile = self:GetProfile() local profileWidth = profile.Width local profileHeight = profile:GetHeight() local maxUnitsInAxis = profile.MaxUnitsInAxis local orientation = profile.Orientation local headerEnabled = not PuppeteerSettings.IsTitleHidden(self.name) local headerHeight = headerEnabled and HEADER_HEIGHT or 0 local sortedUIs, numSorted = self:GetSortedUIs() -- Split the sorted groups into columns of at most maxUnitsInAxis units local columns = self.sortScratch.columns local numColumns = 0 for groupIndex = 1, numSorted do local column local unitsUntilShift = maxUnitsInAxis for _, ui in ipairs(sortedUIs[groupIndex]) do if not column or unitsUntilShift == 0 then numColumns = numColumns + 1 column = getScratchArray(columns, numColumns) unitsUntilShift = maxUnitsInAxis end table.insert(column, ui) unitsUntilShift = unitsUntilShift - 1 end end -- IMPORTANT: "Column" does not necessarily mean vertical! local largestColumn = orientation == "Vertical" and profile.MinUnitsY or profile.MinUnitsX for columnIndex = 1, numColumns do largestColumn = math.max(largestColumn, getn(columns[columnIndex])) end local xSpacing = profile.HorizontalSpacing local ySpacing = profile.VerticalSpacing for columnIndex = 1, numColumns do for i, ui in ipairs(columns[columnIndex]) do -- Column is guaranteed to be less than max units local container = ui:GetRootContainer() local x = orientation == "Vertical" and ((profileWidth + xSpacing) * (columnIndex - 1)) or ((profileWidth + xSpacing) * (i - 1)) local y = orientation == "Vertical" and ((profileHeight + ySpacing) * (i - 1)) or ((profileHeight + ySpacing) * (columnIndex - 1)) container:SetPoint("TOPLEFT", self.container, "TOPLEFT", x, -y - headerHeight) end end local largestRow = math.max(numColumns, orientation == "Vertical" and profile.MinUnitsX or profile.MinUnitsY) --largestRow = math.max(largestRow, 1) --largestColumn = math.max(largestColumn, 1) local width = orientation == "Vertical" and (profileWidth * largestRow + (xSpacing * (largestRow - 1))) or (profileWidth * largestColumn + (xSpacing * (largestColumn - 1))) width = math.max(width, profileWidth) -- Prevent width from being 0 local height = orientation == "Vertical" and (profileHeight * largestColumn + (ySpacing * (largestColumn - 1))) or (profileHeight * largestRow + (ySpacing * (largestRow - 1))) height = height + headerHeight self.container:SetWidth(width) self.container:SetHeight(height) local header = self.header if headerEnabled then header:Show() header:SetWidth(width) header:SetHeight(headerHeight) else header:Hide() end local borderPadding = 0 if profile.BorderStyle == "Tooltip" then borderPadding = 10 elseif profile.BorderStyle == "Dialog Box" then borderPadding = 18 end self.borderFrame:SetWidth(width + borderPadding) self.borderFrame:SetHeight(height + borderPadding) local label = self.label label:SetPoint("CENTER", header, "CENTER", 0, 0) self:UpdateManaLabels() end -- Comparators are defined once instead of creating new closures on every sort local function compareByName(a, b) local aName = UnitName(a:GetUnit()) or a.fakeStats.name or a:GetUnit() local bName = UnitName(b:GetUnit()) or b.fakeStats.name or b:GetUnit() return aName < bName end local function compareByClass(a, b) local aName = util.GetClass(a:GetUnit()) or a.fakeStats.class local bName = util.GetClass(b:GetUnit()) or b.fakeStats.class return aName < bName end local rolePriority = { ["Tank"] = 1, ["Healer"] = 2, ["Damage"] = 3 } -- Key: Unit frame | Value: Sort rank. Ranks are computed once per frame before sorting, rather than inside the -- comparator (which previously also needed a copy of the group to look up each frame's original position). local roleRanks = {} local function compareByRoleRank(a, b) return roleRanks[a] < roleRanks[b] end -- Returns an array with the index being the group number, and the value being an array of units, -- along with the number of groups in the array. The returned tables are reused by the next call. function PTUnitFrameGroup:GetSortedUIs() local profile = self:GetProfile() local uis = self.uis local scratch = self.sortScratch local groups = scratch.groups local numGroups if self.environment == "raid" and profile.SplitRaidIntoGroups and not self.petGroup then numGroups = 8 for i = 1, 8 do getScratchArray(groups, i) end local raidUnits = PTUtil.RaidUnits local numRaidMembers = GetNumRaidMembers() for i = 1, numRaidMembers do local _, _, subgroup = GetRaidRosterInfo(i) local group = groups[subgroup] table.insert(group, uis[raidUnits[i]]) end -- If testing, fill empty slots with fake players if Puppeteer.TestUI then local maxFake = Puppeteer.TestUICount for groupNumber = 1, 8 do local group = groups[groupNumber] for frameNumber = 1, 5 do if not group[frameNumber] then numRaidMembers = numRaidMembers + 1 if numRaidMembers <= maxFake then table.insert(group, uis[raidUnits[numRaidMembers]]) end end end end end else numGroups = 1 local group = getScratchArray(groups, 1) for _, ui in pairs(uis) do if ui:IsShown() then table.insert(group, ui) end end end local sortedGroups = scratch.sorted clearArray(sortedGroups) local numSorted = 0 local sortUnitsBy = profile.SortUnitsBy if sortUnitsBy == "ID" then local groupSet = scratch.groupSet for groupNumber = 1, numGroups do local group = groups[groupNumber] if getn(group) > 0 then if self.environment == "raid" and not self.petGroup then -- Should already be sorted if we're not dealing with the pets numSorted = numSorted + 1 sortedGroups[numSorted] = group else -- Sort by the order of the unit IDs this UI group can handle for _, ui in ipairs(group) do -- Convert group UI array to a set with the key as the Unit ID groupSet[ui:GetUnit()] = ui end local sortedGroup = getScratchArray(scratch.idSorted, groupNumber) for _, unit in ipairs(self.units) do if groupSet[unit] then table.insert(sortedGroup, groupSet[unit]) end end for unit in pairs(groupSet) do groupSet[unit] = nil end numSorted = numSorted + 1 sortedGroups[numSorted] = sortedGroup end end end elseif sortUnitsBy == "Name" or sortUnitsBy == "Class Name" then local comparator = sortUnitsBy == "Name" and compareByName or compareByClass for groupNumber = 1, numGroups do local group = groups[groupNumber] if getn(group) > 0 then table.sort(group, comparator) numSorted = numSorted + 1 sortedGroups[numSorted] = group end end end setn(sortedGroups, numSorted) local sortByRole = self.sortByRole for groupIndex = 1, numSorted do local group = sortedGroups[groupIndex] if sortByRole then for i, ui in ipairs(group) do local unit = ui:GetUnit() roleRanks[ui] = ((rolePriority[ui:GetRole()] or 4) * 1000) + i + (UnitIsUnit("player", unit) and 0 or 100) + (UnitIsUnit("pet", unit) and 0 or 99) end table.sort(group, compareByRoleRank) for _, ui in ipairs(group) do roleRanks[ui] = nil end end for _, ui in ipairs(group) do ui:UpdateRole() end end return sortedGroups, numSorted end function PTUnitFrameGroup:GetProfile() return self.profile end