--[[------------------------------------------------------------------------- Copyright (c) 2006, Jim Whitehead II <cladhaire@gmail.com> All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of PerfectRaid nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------]] local frames = {} local unavail = {} local aggro = {} assert(DongleStub, string.format("PerfectRaid requires DongleStub.")) local L = PerfectRaidLocals local utils PerfectRaid = DongleStub("Dongle-1.0"):New("PerfectRaid") PerfectRaid.rev = tonumber(string.match("$Revision$", "(%d+)") or 1) -- This needs to be visible elsewhere local OnAttributeChanged function PerfectRaid:Initialize() self.frames = frames self.aggro = aggro self.defaults = { profile = { headers = { ["*"] = { manaheight = 0, }, }, positions = {}, hideparty = true, showmanaonly = true, }, } if not ClickCastFrames then ClickCastFrames = {} end utils = self.utils -- This is a small procedure to update the saved variables PerfectRaidDB = PerfectRaidDB or {} local sv = PerfectRaidDB if not sv.global then sv.global = {} end if sv and not sv.global.sv_converted then if sv.char then local converted = {} for key,entry in pairs(sv.char) do local char,server = key:match("^(.+) of (.+)$") if char then local new_key = ("%s - %s"):format(char, server) converted[new_key] = entry else converted[key] = entry end end sv.char = converted end if sv.profileKeys then local converted = {} for ukey,pkey in pairs(sv.profileKeys) do -- Update each user key local char,server = ukey:match("^(.+) of (.+)$") if char then local new_key = ("%s - %s"):format(char, server) converted[new_key] = pkey else converted[key] = entry end end sv.profileKeys = converted end sv.global.sv_converted = true end end function PerfectRaid:Enable() self.db = self:InitializeDB("PerfectRaidDB", self.defaults) self:RegisterEvent("RAID_ROSTER_UPDATE") self:RegisterEvent("PARTY_MEMBERS_CHANGED") self:RegisterEvent("UNIT_DISPLAYPOWER") self:RegisterEvent("UNIT_HEALTH") self:RegisterEvent("UNIT_MAXHEALTH") self:RegisterEvent("UNIT_MANA", "UNIT_MANA") self:RegisterEvent("UNIT_RAGE", "UNIT_MANA") self:RegisterEvent("UNIT_ENERGY", "UNIT_MANA") self:RegisterEvent("UNIT_FOCUS", "UNIT_MANA") self:RegisterEvent("UNIT_MAXMANA") self:RegisterEvent("UNIT_MAXRAGE", "UNIT_MAXMANA") self:RegisterEvent("UNIT_MAXENERGY", "UNIT_MAXMANA") self:RegisterEvent("UNIT_MAXFOCUS", "UNIT_MAXMANA") self:RegisterEvent("CHAT_MSG_SYSTEM") self:UpdateRaidFrames() self:PARTY_MEMBERS_CHANGED() end function PerfectRaid:UpdateRaidFrames() local list = self.db.profile.headers self.hasparty = nil for idx=1,40 do local name = "PRHeader"..idx local frame = getglobal(name) if not frame then break end if not list[idx] then for i=1,frame:GetNumChildren() do local button = frame:GetAttribute("child"..i) button:SetAttribute("unit", nil) button:Hide() end end frame:Hide() frame.bg:Hide() end --name, title, filter, sortOrder, strict, group, order, columns, coloffset, colanchor) for idx,entry in ipairs(list) do self:CreateRaidFrame(idx) end -- Call RAID_ROSTER_UPDATE to update subgroup, etc. self:RAID_ROSTER_UPDATE() end function PerfectRaid:SavePosition(name) local f = getglobal(name) local x,y = f:GetLeft(), f:GetTop() local s = f:GetEffectiveScale() x,y = x*s,y*s local opt = self.db.profile.positions[name] if not opt then self.db.profile.positions[name] = {} opt = self.db.profile.positions[name] end opt.PosX = x opt.PosY = y end function PerfectRaid:RestorePosition(name) local f = getglobal(name) local opt = self.db.profile.positions[name] if not opt then self.db.profile.positions[name] = {} opt = self.db.profile.positions[name] end local x = opt.PosX local y = opt.PosY local s = f:GetEffectiveScale() if not x or not y then f:ClearAllPoints() f:SetPoint("CENTER", UIParent, "CENTER", 0, 0) return end x,y = x/s,y/s f:ClearAllPoints() f:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", x, y) end local function OnDragStart(frame) local parent = frame.dragparent if not PerfectRaid.db.profile.Locked then parent:StartMoving() PerfectRaid.moving = parent end end local function OnDragStop(frame) local parent = frame.dragparent parent:StopMovingOrSizing() PerfectRaid:SavePosition(parent:GetName()) PerfectRaid.moving = nil end function PerfectRaid:CreateRaidFrame(idx) local options = self.db.profile.headers[idx] local name = "PRHeader"..idx local template = "SecureRaidGroupHeaderTemplate" if options.partyFrame then name = name .. "Party" template = "SecurePartyHeaderTemplate" end local frame = getglobal(name) if not frame then local bg = CreateFrame("Frame", nil, UIParent) frame = CreateFrame("Frame", name, bg, template) frame.title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") local offset = options.partyFrame and 17 or 3 local bgtopoffset = 5 bgtopoffset = bgtopoffset + (options.partyFrame and 21 or 0) bgtopoffset = bgtopoffset + (options.title and 20 or 0) frame.bg = bg bg:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, bgtopoffset) bg:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, -5) frame.title:SetPoint("BOTTOM", frame, "TOP", 0, offset) frame.backdrop = { bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\AddOns\\PerfectRaid\\images\\backdrop", tile = true, tileSize = 16, edgeSize = 4, insets = {left = 4, right = 4, top = 4, bottom = 4}, } end frame.idx = idx frame.title:SetText(options.title or "") if options.disabled then frame:Hide() frame.bg:Hide() return end if options.partyFrame and not frame.player then frame.player = CreateFrame("Button", name.."Player", frame, "SecureUnitButtonTemplate") frame.player:SetPoint("BOTTOMLEFT", frame, "TOPLEFT", 0, 0) frame.player:SetAttribute("unit", "player") self.ConfigureButton(frame.player) OnAttributeChanged(frame.player, "unit", "player") frame.player:Show() frame.player:SetWidth(224) frame.player:SetHeight(14) frame.player.dragparent = frame end if options.partyFrame then self.hasparty = true if self.inparty then frame.player:Show() else frame.player:Hide() end end if self.db.profile.hideparty and self.hasparty then -- Disable the party frames self.hidingparty = true for i=1,4 do local f = getglobal("PartyMemberFrame"..i) f:UnregisterEvent("RAID_ROSTER_UPDATE") f:UnregisterEvent("PARTY_MEMBERS_CHANGED") end HidePartyFrame() elseif self.hidingparty then self.hidingparty = false for i=1,4 do local f = getglobal("PartyMemberFrame"..i) f:RegisterEvent("RAID_ROSTER_UPDATE") f:RegisterEvent("PARTY_MEMBERS_CHANGED") end ShowPartyFrame() end if options.hBackdrop then frame.bg:SetBackdrop(frame.backdrop) frame.bg:SetBackdropColor(0,0,0,.7) else frame.bg:SetBackdrop(nil) end local yoffset = options.alignbottom and 1 or -1 frame:SetAttribute("point", options.alignbottom and "BOTTOM" or "TOP") frame:SetAttribute("groupFilter", options.filter or "") frame:SetAttribute("template", "SecureUnitButtonTemplate") frame:SetAttribute("templateType", "Button") frame:SetAttribute("yOffset", yoffset) frame:SetAttribute("sortMethod", options.sortType) frame:SetAttribute("strictFiltering", options.strict) frame:SetAttribute("groupBy", options.groupBy) local groupOrder if options.groupBy == "CLASS" then groupOrder="WARRIOR,PRIEST,DRUID,SHAMAN,PALADIN,MAGE,ROGUE,WARLOCK,HUNTER" elseif options.groupBy == "GROUP" then groupOrder="1,2,3,4,5,6,7,8" end frame:SetAttribute("groupingOrder", groupOrder) frame:SetAttribute("maxColumns", options.numColumns) frame:SetAttribute("unitsPerColumn", options.maxUnits) frame:SetAttribute("columnSpacing", options.colSpacing) frame:SetAttribute("columnAnchorPoint", options.colAnchor) frame.dragparent = dragparent or frame frame.initialConfigFunction = PerfectRaid.ConfigureButton frame:SetMovable(true) frame:SetClampedToScreen(true) frame:SetScale(options.scale) frame:Show() frame.bg:Show() self:RestorePosition(name) return frame end function PerfectRaid:GetColoredName(unit, idx) local class = select(2, UnitClass(unit)) or "WARRIOR" local color = RAID_CLASS_COLORS[class] local group = "" if idx then group = string.format("%s-", select(3, GetRaidRosterInfo(idx))) end return string.format("%s|cFF%02x%02x%02x%s|r", group, color.r*255, color.g*255, color.b*255, UnitName(unit) or "Unknown") end function PerfectRaid:CHAT_MSG_SYSTEM(event, msg) if msg == ERR_RAID_YOU_LEFT then for unit,tbl in pairs(frames) do for frame in pairs(tbl) do frame.unit = nil frame.unitname = nil end end end end function PerfectRaid:UNIT_HEALTH(event, unit) if not frames[unit] then return end local health = UnitHealth(unit) local max = UnitHealthMax(unit) -- Hack to fix api issue if max < health then max = health end local deficit = max - health local perc = UnitHealthMax(unit) / health local class = select(2, UnitClass(unit)) local perc = (health/max) or 0 if deficit > 999 then deficit = string.format("%.1fk", deficit / 1000) elseif deficit == 0 then deficit = "" end --TODO: Seperate this out properly into the logical module if aggro[unit] and aggro[unit] >= 15 then if deficit == "" then deficit = "|cFFFF1111"..L["Aggro"] else deficit = "|cFFFF1111"..deficit.."|r" end end if UnitIsDead(unit) or UnitIsGhost(unit) or not UnitIsConnected(unit) then local status unavail[unit] = true if UnitIsDead(unit) then status = L["Dead"] elseif UnitIsGhost(unit) then status = L["Ghost"] elseif not UnitIsConnected(unit) then status = L["Offline"] end for frame in pairs(frames[unit]) do frame.healthbar:SetValue(max) frame.manabar:SetValue(max) frame.status:SetText(status) frame.healthbar:SetStatusBarColor(0.3, 0.3, 0.3) frame.manabar:SetStatusBarColor(0.3, 0.3, 0.3) frame:SetBackdropBorderColor(0.3, 0.3, 0.3) end else if unavail[unit] then for frame in pairs(frames[unit]) do local options = self.db.profile.headers[frame.header.idx] if options.colorclass then local c = frame.classcolor frame.healthbar:SetStatusBarColor(c.r,c.g,c.b) else frame.healthbar:SetStatusBarColor(utils.GetHPSeverity(perc)) end local value = health if options.reverse then value = max - health end frame.healthbar:SetMinMaxValues(0, max) frame.healthbar:SetValue(value) frame.status:SetText(nil) end unavail[unit] = nil end for frame in pairs(frames[unit]) do local options = self.db.profile.headers[frame.header.idx] if options.colorclass then local c = frame.classcolor frame.healthbar:SetStatusBarColor(c.r,c.g,c.b) else frame.healthbar:SetStatusBarColor(utils.GetHPSeverity(perc)) end local value = health if options.reverse then value = max - health end frame.healthbar:SetValue(value) if options.deficit then frame.status:SetText(deficit) else frame.status:SetText(nil) end end end end function PerfectRaid:UNIT_MANA(event, unit) if not frames[unit] then return end local mana = UnitMana(unit) for frame in pairs(frames[unit]) do frame.manabar:SetValue(mana) end end function PerfectRaid:UNIT_MAXHEALTH(event, unit) if not frames[unit] then return end local max = UnitHealthMax(unit) local health = UnitHealth(unit) for frame in pairs(frames[unit]) do frame.healthbar:SetMinMaxValues(0, max) self:UNIT_HEALTH(nil, unit) end end function PerfectRaid:UNIT_MAXMANA(event, unit) if not frames[unit] then return end local max = UnitManaMax(unit) local mana = UnitMana(unit) for frame in pairs(frames[unit]) do frame.manabar:SetMinMaxValues(0, max) self:UNIT_MANA(nil, unit) end end local function OnShow(frame) local self = PerfectRaid local unit = frame:GetAttribute("unit") if unit == nil then PerfectRaid:Print(debugstack()) end local unitname = UnitName(unit) if not frame.unitname then frame.unitname = unitname else if frame.unitname == unitname then return end end end local function OnHide(frame) local unit = frame:GetAttribute("unit") frames[unit][frame] = nil end function OnAttributeChanged(frame, name, value) if name ~= "unit" then return end local self = PerfectRaid for unit,tbl in pairs(frames) do if tbl[frame] and frame:GetAttribute("unit") ~= unit then tbl[frame] = nil end end if value then local unitname = UnitName(value) if not frame.unit then frame.unit = value frame.unitname = unitname else if frame.unit == value and frame.unitname == unitname then return end end frame.unit = value frame.unitname = unitname -- Do Visual Configuration here local unit = value frame.idx = value:match("^raid(%d+)$") local nametext = self:GetColoredName(unit, frame.options.showgroupnum and frame.idx) frame.name:SetText(nametext) -- frame.name:SetText(UnitName(unit))G local class = select(2, UnitClass(unit)) or "WARRIOR" frame.class = class frame.classcolor = RAID_CLASS_COLORS[class] frame.manacolor = ManaBarColor[UnitPowerType(unit)] local color = frame.classcolor frame.healthbar:SetStatusBarColor(color.r, color.g, color.b) frame.healthbar:SetMinMaxValues(0, UnitHealthMax(unit)) frame.healthbar:SetValue(UnitHealth(unit)) -- frame:SetBackdropBorderColor(color.r, color.g, color.b) local color = frame.manacolor frame.manabar:SetStatusBarColor(color.r, color.g, color.b) frame.manabar:SetMinMaxValues(0, UnitManaMax(unit)) frame.manabar:SetValue(UnitMana(unit)) -- Show/Hide the mana bar depending on unit power type local manaheight = frame.options.manaheight or 0 if UnitPowerType(unit) == 0 and manaheight > 0 then frame.manabar:Show() elseif self.db.profile.showmanaonly then frame.manabar:Hide() end frame.status:SetText(nil) frames[unit] = frames[unit] or {} frames[unit][frame] = true for name,module in self:IterateModules() do if type(module.ShowButton) == "function" then module:ShowButton(frame) end end self:UNIT_HEALTH(nil, unit) self:UNIT_MANA(nil, unit) end -- Hide any empty parents local parent = frame:GetParent() if value == nil then local num = parent:GetNumChildren() local visible = nil for i=1,num do local button = parent:GetAttribute("child"..i) if button and button:IsShown() then visible = true end end local method if visible then method = "Show" else method = "Hide" end if parent.title then parent.title[method](parent.title) end if parent.bg then parent.bg[method](parent.bg) end else parent.title:Show() end end function PerfectRaid.ConfigureButton(button) local self = PerfectRaid button:SetAttribute("initial-width", 225) button:SetAttribute("initial-height", 14) ClickCastFrames[button] = true button:SetAttribute("*type1", "target") local bg = button:CreateTexture(nil, "BACKGROUND") bg:SetAllPoints() bg:SetTexture("Interface\\AddOns\\PerfectRaid\\images\\Highlight") bg:Hide() button.bg = bg --button:SetScript("OnEnter", function(frame) bg:Show() end) --button:SetScript("OnLeave", function(frame) bg:Hide() end) local parent = button:GetParent() button.dragparent = parent.dragparent button.header = parent button.headerName = parent:GetName() local options = self.db.profile.headers[parent.idx] button.options = options local leftbox = CreateFrame("Frame", nil, button) button.leftbox = leftbox local rightbox = CreateFrame("Frame", nil, button) button.rightbox = rightbox local bar = CreateFrame("StatusBar", nil, button) button.healthbar = bar local bar = CreateFrame("StatusBar", nil, button.healthbar) button.manabar = bar local font = button.healthbar:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") button.name = font local font = button.healthbar:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") button.status = font button:SetClampedToScreen(true) button:RegisterForDrag("LeftButton") for name,module in self:IterateModules() do if type(module.ConfigureButton) == "function" then module:ConfigureButton(button) end end self:UpdateButtonLayout(button) button:SetScript("OnShow", OnShow) button:SetScript("OnDragStart", OnDragStart) button:SetScript("OnDragStop", OnDragStop) button:SetScript("OnAttributeChanged", OnAttributeChanged) end function PerfectRaid:UpdateButtonLayout(button) if not button then self:Print(debugstack()) end local parent = button:GetParent() local options = self.db.profile.headers[parent.idx] button.leftbox:ClearAllPoints() button.leftbox:SetPoint("TOPLEFT", 0, 0) button.leftbox:SetPoint("BOTTOMRIGHT", button, "BOTTOMLEFT", 70, 0) button.rightbox:ClearAllPoints() button.rightbox:SetPoint("TOPRIGHT", 0, 0) button.rightbox:SetPoint("BOTTOMLEFT", button, "BOTTOMRIGHT", -70, 0) button.healthbar:ClearAllPoints() button.healthbar:SetPoint("TOPLEFT", button.leftbox, "TOPRIGHT", 0, -1) button.healthbar:SetPoint("BOTTOMRIGHT", button.rightbox, "BOTTOMLEFT", 0, 1) button.healthbar:SetStatusBarTexture("Interface\\AddOns\\PerfectRaid\\images\\smooth") button.manabar:ClearAllPoints() button.manabar:SetPoint("BOTTOMLEFT", 0, 0) button.manabar:SetPoint("BOTTOMRIGHT", 0, 0) if options.manaheight == 0 then button.manabar:Hide() else button.manabar:Show() button.manabar:SetHeight(options.manaheight or 0) end button.manabar:SetStatusBarTexture("Interface\\AddOns\\PerfectRaid\\images\\smooth") button.status:ClearAllPoints() button.status:SetPoint("RIGHT", -2, 0) if options.alignright then button.name:ClearAllPoints() button.name:SetPoint("RIGHT", button.rightbox, "RIGHT", 0, 0) button.name:SetPoint("LEFT", button.rightbox, "LEFT", 2, 0) button.name:SetJustifyH("LEFT") else button.name:ClearAllPoints() button.name:SetPoint("RIGHT", button.leftbox, "RIGHT", -2, 0) button.name:SetPoint("LEFT", button.leftbox, "LEFT", 0, 0) button.name:SetJustifyH("RIGHT") end for name,module in self:IterateModules() do if type(module.UpdateButtonLayout) == "function" then module:UpdateButtonLayout(button, options) end end end function PerfectRaid:RAID_ROSTER_UPDATE() if self.moving then self.moving:StopMovingOrSizing() end -- Update group numbers here for unit,list in pairs(self.frames) do for frame in pairs(list) do if frame.idx then local nametext = self:GetColoredName(unit, frame.options.showgroupnum and frame.idx) frame.name:SetText(nametext) end end end end function PerfectRaid:PARTY_MEMBERS_CHANGED() self.inparty = GetNumPartyMembers() > 0 self:UpdateRaidFrames() end function PerfectRaid:UNIT_DISPLAYPOWER(event, unit) if not frames[unit] then return end local color = ManaBarColor[UnitPowerType(unit)] local show = UnitPowerType(unit) == 0 local max = UnitManaMax(unit) local mana = UnitMana(unit) local manaonly = self.db.profile.showmanaonly for frame in pairs(frames[unit]) do frame.manacolor = color local bar = frame.manabar bar:SetStatusBarColor(color.r, color.g, color.b) bar:SetMinMaxValues(0, max) bar:SetValue(mana) if show and frame.options.manaheight > 0 then bar:Show() elseif manaonly then bar:Hide() end end end