--[[------------------------------------------------------------------------- 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 = {} 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 local class = select(2, UnitClass("player")) self.defaults = { profile = { headers = { ["*"] = { manaheight = 0, showRaid = true, }, }, positions = {}, hideparty = true, showmanaonly = true, clickcast = true, locked = false, highlight = { mouseover = true, tooltip = false, curse = (class == "DRUID" or class == "MAGE"), disease = (class == "PRIEST" or class == "PALADIN" or class == "SHAMAN"), magic = (class == "PRIEST" or class == "PALADIN"), poison = (class == "DRUID" or class == "PALADIN" or class == "SHAMAN"), }, }, } if not ClickCastFrames then ClickCastFrames = {} end utils = self.utils if PerfectRaidDB then if PerfectRaidDB.global then if PerfectRaidDB.global.sv_converted then PerfectRaidDB.global.sv_converted = nil PerfectRaidDB.global.sv_version = self.rev elseif PerfectRaidDB.global.sv_version > self.rev then -- Got the bad rev number, revert PerfectRaidDB.global.sv_version = self.rev elseif PerfectRaidDB.global.sv_version < 112 then -- We do this once, to make sure we catch everyone from -- the last version of PerfectRaid self:Print("You're upgrading from an old version of PerfectRaid") PerfectRaidDB = {} self:Print("All settings have been set to default") end end end end function PerfectRaid:Enable() self.db = self:InitializeDB("PerfectRaidDB", self.defaults) self.db.global.sv_version = self.rev self:RegisterEvent("RAID_ROSTER_UPDATE") 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:RegisterEvent("PLAYER_ALIVE", function() self:UNIT_HEALTH("UNIT_HEALTH", "player") end) -- Create our raid frames self:UpdateRaidFrames() end function PerfectRaid:UpdateRaidFrames() -- Hide all existing raid frames, in case we deleted one if not InCombatLockdown() then local idx = 1 repeat local name = "PRHeader"..idx local header = getglobal(name) if header then header:Hide() end idx = idx + 1 until not header end -- Create our raid frames for idx,entry in ipairs(self.db.profile.headers) do self:CreateRaidFrame(idx) end 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 = "SecureGroupHeaderTemplate" local frame = getglobal(name) if not frame then frame = CreateFrame("Frame", name, UIParent, template) frame.title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") frame.title:SetPoint("BOTTOM", frame, "TOP", 0, 3) frame.backdrop = { bgFile = "Interface\\AddOns\\PerfectRaid\\images\\bgFile", tile = true, tileSize = 32, edgeFile = "Interface\\AddOns\\PerfectRaid\\images\\edgeFile", edgeSize = 16, insets = { left = 6, right = 5, top = 6, bottom = 5 }, } end -- Position backdrop frame.idx = idx frame.title:SetText(options.title or "") if options.disabled then frame:Hide() return end if options.hBackdrop then frame:SetBackdrop(frame.backdrop) frame:SetBackdropColor(0,0,0,.7) frame:SetBackdropBorderColor(0,0,0,.7) else frame:SetBackdrop(nil) end local yoffset = options.alignbottom and 1 or -1 -- Set the frame's scale frame:SetScale(options.scale) 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:SetAttribute("showParty", options.showParty) frame:SetAttribute("showRaid", options.showRaid) frame:SetAttribute("showPlayer", options.showPlayer) frame:SetAttribute("showSolo", options.showSolo) frame.dragparent = dragparent or frame frame.initialConfigFunction = PerfectRaid.ConfigureButton frame:SetMovable(true) frame:SetClampedToScreen(true) frame: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 color = frame.manacolor frame.manabar:SetStatusBarColor(color.r, color.g, color.b) 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 -- This code finds all instances of the current frame -- and clears them from the unit table. This is exhaustive -- to ensure nothing is left referencing this frame. for unit,tbl in pairs(frames) do if tbl[frame] and frame:GetAttribute("unit") ~= unit then tbl[frame] = nil end end -- If we've been given a unit, instead of nil if value then -- Register us for frame updates under the correct unit ID. frames[value] = frames[value] or {} frames[value][frame] = true -- Use the unitname to check if we need to update config -- even if the unitID changed. 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 -- Set the variables we use to check/clear 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 = PowerBarColor[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) 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 else parent.title:Show() end end function PerfectRaid.ConfigureButton(button) local self = PerfectRaid button:SetAttribute("initial-width", 225) button:SetAttribute("initial-height", 14) if self.db.profile.clickcast then ClickCastFrames[button] = true end 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 raise = CreateFrame("Frame", nil, button.healthbar) raise:SetAllPoints() button.raise = raise --raise:Show() local font = raise:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") button.status = font local bar = CreateFrame("StatusBar", nil, button.healthbar) button.manabar = bar local font = button.healthbar:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") button.name = 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:UNIT_DISPLAYPOWER(event, unit) if not frames[unit] then return end local color = PowerBarColor[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 PerfectRaid = DongleStub("Dongle-1.1"):New("PerfectRaid", PerfectRaid)