-- check for expansion local tbc = ShaguDPS.expansion() == "tbc" and true or nil -- load public variables into local local window = ShaguDPS.window local parser = ShaguDPS.parser local data = ShaguDPS.data local config = ShaguDPS.config local internals = ShaguDPS.internals local textures = ShaguDPS.textures local spairs = ShaguDPS.spairs local round = ShaguDPS.round local skin = ShaguDPS.skin -- time used for "per second" values: each unit's own active time, or with the -- "Fight Duration" time mode the time the whole segment lasted (set by Refresh) local fight_time = nil local function ctime(unitdata) return fight_time or unitdata["_ctime"] end -- title row: two icons left, three right, so the two text buttons sit a bit -- left of the center and the window needs this much width local MIN_WIDTH = 186 local TITLE_SHIFT = -8.5 local function SetFightTime(segment, wconfig) if config.time_mode ~= 1 then fight_time = nil elseif segment == 2 then -- a fight of the history: the time it lasted local fight = ShaguDPS.fights and wconfig and ShaguDPS.fights:Get(wconfig.fight) fight_time = fight and math.max(1, fight.duration) or nil else fight_time = parser:GetFightTime(segment or 1) end end -- caption of a window's segment: Current, Overall or the fight's name local function SegmentName(wconfig) if wconfig.segment == 2 then local fight = ShaguDPS.fights and ShaguDPS.fights:Get(wconfig.fight) if fight then return fight.name end end return wconfig.segment == 0 and "Overall" or "Current" end -- all known classes local classes = { WARRIOR = true, MAGE = true, ROGUE = true, DRUID = true, HUNTER = true, SHAMAN = true, PRIEST = true, WARLOCK = true, PALADIN = true, } -- Modern class palette for the retail bar preset, independent of other addons. local modern_colors = { WARRIOR = {.78, .61, .43}, MAGE = {.25, .78, .92}, ROGUE = {1, .96, .41}, DRUID = {1, .49, .04}, HUNTER = {.67, .83, .45}, SHAMAN = {0, .44, .87}, PRIEST = {1, 1, 1}, WARLOCK = {.53, .53, .93}, PALADIN = {.96, .55, .73}, } -- Format display values explicitly to avoid Lua exposing float precision noise. window.FormatThreatPercent = function(value) if type(value) ~= "number" then return value or "0" end return (string.gsub(string.format("%.1f", value), "%.0$", "")) end window.FormatRate = function(value) return string.format("%.0f", value or 0) end window.FormatTotal = function(value) value = value or 0 local divisor, suffix, precision = 1, "", 0 if value >= 1000000 then divisor, suffix, precision = 1000000, "M", 2 elseif value >= 10000 then divisor, suffix, precision = 1000, "K", 1 end local text = string.format("%." .. precision .. "f", value / divisor) if precision > 0 then text = string.gsub(text, "0+$", "") text = string.gsub(text, "%.$", "") end return text .. suffix end -- The built-in class atlas is also used by GreedMeter; no extra assets needed. local class_icons = { WARRIOR = {0, .25, 0, .25}, MAGE = {.25, .5, 0, .25}, ROGUE = {.5, .75, 0, .25}, DRUID = {.75, 1, 0, .25}, HUNTER = {0, .25, .25, .5}, SHAMAN = {.25, .5, .25, .5}, PRIEST = {.5, .75, .25, .5}, WARLOCK = {.75, 1, .25, .5}, PALADIN = {0, .25, .5, .75}, } window.UpdateClassIcon = function(bar, class) local coords = config.class_icons == 1 and class_icons[class] local offset = 5 if coords then bar.classIcon = bar.classIcon or bar:CreateTexture(nil, "OVERLAY") local size = math.max(1, config.height - config.spacing - 2) bar.classIcon:SetTexture("Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes") bar.classIcon:SetTexCoord(unpack(coords)) bar.classIcon:SetWidth(size) bar.classIcon:SetHeight(size) bar.classIcon:SetPoint("LEFT", bar, "LEFT", 2, 0) bar.classIcon:Show() offset = size + 5 elseif bar.classIcon then bar.classIcon:Hide() end bar.textLeft:SetPoint("TOPLEFT", bar, "TOPLEFT", offset, 1) end -- Share measured column widths across the visible rows of one window. window.AlignValueColumns = function(bars, count, widths) for i = 1, count do local bar = bars[i] if bar and bar:IsShown() then local offset = 5 local columns = { bar.textRight, bar.textRate, bar.textPercent } for column = 3, 1, -1 do local text = columns[column] text:ClearAllPoints() text:SetPoint("TOPRIGHT", bar, "TOPRIGHT", -offset, 1) text:SetPoint("BOTTOMRIGHT", bar, "BOTTOMRIGHT", -offset, 0) text:SetWidth(widths[column]) offset = offset + widths[column] + 6 end bar.textLeft:SetPoint("BOTTOMRIGHT", bar, "BOTTOMRIGHT", -offset, 0) end end end local empty_dispels = {} window.GetDispelSegment = function(wconfig, fight) local key = wconfig.dispels_received == 1 and "dispels_received" or "dispels_done" if fight then return fight[key] or empty_dispels end return data[key][wconfig.segment or 0] end window.DispelStatus = function(wconfig, fight, available) local key = wconfig.dispels_received == 1 and "dispels_received" or "dispels_done" if fight and (fight[key] == nil or fight.dispels_recorded == false) then return "Dispel data was not recorded for this fight." elseif not fight and not available then return "Dispel tracking requires ClassicAPI with combat logging." end end -- default backdrops local backdrop = { bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 8, insets = { left = 2, right = 2, top = 2, bottom = 2 } } local backdrop_window = { bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 3, right = 3, top = 3, bottom = 3 } } local backdrop_border = { edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 3, right = 3, top = 3, bottom = 3 } } -- templates describing the window contents local view_templates = { [6] = { name = "Dispels", sort = "normal", bar_max = "best", bar_val = "value", chat_string = "%s [%s%%]", bar_string = "%s [%s%%]", bar_string_params = { "value_short", "percent_display", "percent_display" }, chat_string_params = { "value_short", "percent_display", "percent_display" }, }, [1] = { -- damage: total, per second and share in one bar name = "Damage", sort = "normal", bar_max = "best", bar_val = "value", bar_lower_max = nil, bar_lower_val = nil, chat_string = "%s (%s DPS) [%s%%]", bar_string = "%s (%s DPS) [%s%%]", bar_string_params = { "value_short", "persecond_short", "percent_display" }, chat_string_params = { "value_short", "persecond_short", "percent_display" }, }, [3] = { -- heal: effective total, per second and share; overheal as the light bar name = "Heal", sort = "normal", bar_max = "best", bar_val = "effective_value", bar_lower_max = "best", bar_lower_val = "value", chat_string = "%s (%s HPS) [%s%%] [+%s overheal]", bar_string = "%s (%s HPS) [%s%%]", bar_string_params = { "effective_short", "effective_persecond_short", "effective_percent_display" }, chat_string_params = { "effective_short", "effective_persecond_short", "effective_percent_display", "uneffective_short" }, }, [5] = { -- threat on the current target name = "Threat", sort = "normal", bar_max = "best", bar_val = "value", bar_lower_max = nil, bar_lower_val = nil, chat_string = "%s (%s TPS) [%s%%]", bar_string = "%s (%s TPS) [%s%%]", bar_string_params = { "value_short", "threat_tps_short", "threat_perc" }, chat_string_params = { "value_short", "threat_tps_short", "threat_perc" }, }, } -- views of older versions: DPS (2) and HPS (4) are part of Damage and Heal now local view_merged = { [2] = 1, [4] = 3 } local short = window.FormatTotal -- panel button templates local menubuttons = { ["Dispels"] = {3, 6, 25.5, "Dispels", "|cffffffffSuccessful removals; select Done or Received", "view"}, ["Done"] = {4, 0, 25.5, "Dispels Done", "|cffffffffShow players removing auras", "dispels_received"}, ["Received"] = {5, 1, 25.5, "Dispels Received", "|cffffffffShow players whose auras were removed", "dispels_received"}, -- segments ["Current"] = { 0, 1, -25.5, "Current Segment", "|cffffffffShow current fight", "segment" }, ["Fights"] = { 1, 2, -25.5, "Boss Fights", "|cffffffffPick a past boss fight", "segment" }, ["Overall"] = { 2, 0, -25.5, "Overall Segment", "|cffffffffShow all fights", "segment" }, -- modes ["Damage"] = { 0, 1, 25.5, "Damage View", "|cffffffffDamage done and damage per second", "view" }, ["Heal"] = { 1, 3, 25.5, "Heal View", "|cffffffffHealing done and heal per second", "view" }, ["Threat"] = { 2, 5, 25.5, "Threat View", "|cffffffffThreat on your target", "view" }, } -- default colors of chat types local chatcolors = { ["SAY"] = "|cffFFFFFF", ["EMOTE"] = "|cffFF7E40", ["YELL"] = "|cffFF3F40", ["PARTY"] = "|cffAAABFE", ["GUILD"] = "|cff3CE13F", ["OFFICER"] = "|cff40BC40", ["RAID"] = "|cffFF7D01", ["RAID_WARNING"] = "|cffFF4700", ["BATTLEGROUND"] = "|cffFF7D01", ["WHISPER"] = "|cffFF7EFF", ["CHANNEL"] = "|cffFEC1C0" } local sort_algorithms = { normal = function(t,a,b) if t[a]["_esum"] and t[b]["_esum"] and t[a]["_esum"] ~= t[b]["_esum"] then return t[b]["_esum"] < t[a]["_esum"] else return t[b]["_sum"] < t[a]["_sum"] end end, per_second = function(t,a,b) if t[a]["_esum"] and t[b]["_esum"] and t[a]["_esum"] ~= t[b]["_esum"] then return t[b]["_esum"] / ctime(t[b]) < t[a]["_esum"] / ctime(t[a]) else return t[b]["_sum"] / ctime(t[b]) < t[a]["_sum"] / ctime(t[a]) end end, single_spell = function(t,a,b) if t["_effective"] and t["_effective"][a] and t["_effective"][b] and t["_effective"][a] ~= t["_effective"][b] then return t["_effective"][b] < t["_effective"][a] else if tonumber(t[b]) and tonumber(t[a]) then return t[b] < t[a] end end end } local rgbcache = {} local function str2rgb(text) if not text then return 1, 1, 1 end if rgbcache[text] then return unpack(rgbcache[text]) end local counter = 1 local l = string.len(text) for i = 1, l, 3 do counter = mod(counter*8161, 4294967279) + (string.byte(text,i)*16776193) + ((string.byte(text,i+1) or (l-i+256))*8372226) + ((string.byte(text,i+2) or (l-i+256))*3932164) end local hash = mod(mod(counter, 4294967291),16777216) local r = (hash - (mod(hash,65536))) / 65536 local g = ((hash - r*65536) - ( mod((hash - r*65536),256)) ) / 256 local b = hash - r*65536 - g*256 rgbcache[text] = { r / 255, g / 255, b / 255 } return unpack(rgbcache[text]) end local function spairs(t, order) -- collect the keys local keys = {} for k in pairs(t) do keys[table.getn(keys)+1] = k end -- if order function given, sort by it by passing the table and keys a, b, -- otherwise just sort the keys if order then table.sort(keys, function(a,b) return order(t, a, b) end) else table.sort(keys) end -- return the iterator function local i = 0 return function() i = i + 1 if keys[i] then return keys[i], t[keys[i]] end end end -- Shared, display-only caches. Weak keys let discarded fight tables expire. local displayCaches = setmetatable({}, {__mode = "k"}) local paintOptions = {"height", "spacing", "texture", "class_icons", "pastel", "merge_pets", "time_mode", "backdrop", "theme"} window.DisplaySnapshot = function(t, order) local modes = displayCaches[t] if not modes then modes = {}; displayCaches[t] = modes end local c = modes[order] if not c then c = {rows = {}, keys = {}, revision = 0, sorts = 0} c.compare = function(a, b) if order(c.rows, a, b) then return true end if order(c.rows, b, a) then return false end return a < b end modes[order] = c end local changed, ranking = false, false local count = 0 for name, entry in pairs(t) do count = count + 1 local row = c.rows[name] if not row then row = {}; c.rows[name] = row; changed, ranking = true, true end if row._sum ~= entry._sum or row._esum ~= entry._esum or row._ctime ~= entry._ctime then ranking = true; changed = true end row._sum, row._esum, row._ctime = entry._sum, entry._esum, entry._ctime local class = data.classes[name] local ownerClass = class and data.classes[class] local color = RAID_CLASS_COLORS and RAID_CLASS_COLORS[ownerClass or class] local r, g, b = color and color.r, color and color.g, color and color.b if row.class ~= class or row.ownerClass ~= ownerClass or row.r ~= r or row.g ~= g or row.b ~= b then changed = true end row.class, row.ownerClass, row.r, row.g, row.b = class, ownerClass, r, g, b end for name in pairs(c.rows) do if not t[name] then c.rows[name] = nil; changed, ranking = true, true end end if ranking then local n = 0 for name in pairs(c.rows) do n = n + 1; c.keys[n] = name end for i = n + 1, table.getn(c.keys) do c.keys[i] = nil end table.sort(c.keys, c.compare) c.sorts = c.sorts + 1 end if changed then c.revision = c.revision + 1 end c.count = count return c end window.PaintUnchanged = function(self, cache, duration, force, report) local state = self.paintState local wc = config[self:GetID()] local same = not force and not report and wc.view ~= 5 and state and state.cache == cache and state.revision == cache.revision and state.duration == duration and state.scroll == self.scroll and state.view == wc.view and state.bars == wc.bars and state.direction == wc.dispels_received and state.width == self:GetWidth() and state.height == self:GetHeight() state = state or {}; self.paintState = state for _, key in pairs(paintOptions) do if state["option_" .. key] ~= config[key] then same = false end state["option_" .. key] = config[key] end state.cache, state.revision, state.duration = cache, cache.revision, duration state.scroll, state.view, state.bars = self.scroll, wc.view, wc.bars state.direction, state.width, state.height = wc.dispels_received, self:GetWidth(), self:GetHeight() return same and true or false end local function barTooltipShow() GameTooltip:SetOwner(this, "ANCHOR_RIGHT") local segment = this.parent.segment local wid = this.parent:GetID() if not segment[this.unit] then return end if this.glow then this.glow:Show() end if config[wid].view == 6 then local entry = segment[this.unit] GameTooltip:AddLine(this.title .. ": " .. entry._sum .. " dispels") local function Breakdown(title, counts) GameTooltip:AddLine(title) local count = 0 for name, value in spairs(counts or {}, sort_algorithms.single_spell) do count = count + 1 if count > 10 then GameTooltip:AddLine("..."); break end GameTooltip:AddDoubleLine(name, tostring(value)) end end Breakdown("Removed auras", entry._auras) if entry._abilities then Breakdown("Dispel abilities", entry._abilities) end Breakdown(config[wid].dispels_received == 1 and "Dispelled by" or "Dispelled from", entry._players) GameTooltip:Show() return end -- threat: the server's numbers for the current target if config[wid].view == 5 then local entry = segment[this.unit] GameTooltip:AddLine(this.title .. ":") if entry._pull then local threshold = entry._minimum and (entry._minimum .. "-" .. entry._sum) or entry._sum GameTooltip:AddDoubleLine("|cffffffffYou pull aggro at", "|cffff4040" .. threshold) if entry._minimum then GameTooltip:AddLine("|cffaaaaaaYour range is unknown: 110% melee, 130% ranged.") GameTooltip:AddLine("|cffaaaaaaThe bar marks the upper end of this range.") end GameTooltip:AddDoubleLine("|cffaaaaaaOf pull threshold", "|cffaaaaaa" .. window.FormatThreatPercent(entry._perc) .. "%") else GameTooltip:AddDoubleLine("|cffffffffThreat", "|cffffffff" .. entry._sum) GameTooltip:AddDoubleLine("|cffffffffOf pull threshold", "|cffffffff" .. window.FormatThreatPercent(entry._perc) .. "%") GameTooltip:AddDoubleLine("|cffffffffThreat Per Second", "|cffffffff" .. window.FormatRate(entry._tps)) if entry._tank then GameTooltip:AddLine("|cffffd100Has aggro (server displays 100%)") end GameTooltip:AddLine(entry._melee and "|cffaaaaaaMelee range: pulls at 110%" or "|cffaaaaaaRanged: pulls at 130%") end GameTooltip:Show() return end SetFightTime(config[wid].segment, config[wid]) local time = ctime(segment[this.unit]) local value = segment[this.unit]["_sum"] local persec = round(value / time, 0) if this.glow then this.glow:Show() end GameTooltip:AddLine(this.title .. ":") if config[wid].view == 1 then GameTooltip:AddDoubleLine("|cffffffffDamage", "|cffffffff" .. value) GameTooltip:AddDoubleLine("|cffffffffDamage Per Second", "|cffffffff" .. persec) elseif config[wid].view == 3 then local evalue = segment[this.unit]["_esum"] local epersec = round(evalue / time, 0) GameTooltip:AddDoubleLine("|cffffffffHealing", "|cffffffff" .. evalue) GameTooltip:AddDoubleLine("|cffaaaaaaOverheal", "|cffcc8888+" .. value - evalue) GameTooltip:AddLine(" ") GameTooltip:AddDoubleLine("|cffffffffHealing Per Second", "|cffffffff" .. epersec) GameTooltip:AddDoubleLine("|cffaaaaaaOverheal Per Second", "|cffcc8888+" .. persec - epersec) end -- active time vs. fight duration local active = segment[this.unit]["_ctime"] or 1 if config.time_mode == 1 then GameTooltip:AddDoubleLine("|cffaaaaaaFight Duration", "|cffaaaaaa" .. SecondsToTime(math.floor(time))) GameTooltip:AddDoubleLine("|cffaaaaaaActive Time", "|cffaaaaaa" .. SecondsToTime(math.floor(active)) .. string.format(" (%d%%)", math.min(100, active / time * 100))) else GameTooltip:AddDoubleLine("|cffaaaaaaActive Time", "|cffaaaaaa" .. SecondsToTime(math.floor(active))) end GameTooltip:AddLine(" ") GameTooltip:AddLine("Details:") for attack, damage in spairs(segment[this.unit], sort_algorithms.single_spell) do if attack and not internals[attack] then local percent = damage == 0 and 0 or round(damage / segment[this.unit]["_sum"] * 100,1) if segment[this.unit]["_effective"] and segment[this.unit]["_effective"][attack] then -- heal / effective heal local effective = segment[this.unit]["_effective"][attack] local epercent = effective == 0 and 0 or round(effective / segment[this.unit]["_esum"] * 100,1) local str = string.format("|cffcc8888+%s|cffffffff %s (%.1f%%)", damage - effective, effective, epercent) GameTooltip:AddDoubleLine("|cffffffff" .. attack, str) else -- damage local str = string.format("|cffffffff %s (%.1f%%)", damage, percent) GameTooltip:AddDoubleLine("|cffffffff" .. attack, str) end end end GameTooltip:Show() end local function barTooltipHide() if this.glow then this.glow:Hide() end GameTooltip:Hide() end local function barScrollWheel() this.scroll = arg1 > 0 and this.scroll - 1 or this.scroll this.scroll = arg1 < 0 and this.scroll + 1 or this.scroll local count = 0 for k,v in pairs(this.segment) do count = count + 1 end this.scroll = math.min(this.scroll, count + 1 - config[this:GetID()].bars) this.scroll = math.max(this.scroll, 0) this:Refresh() end local function ResetData() data.dispels_done[0], data.dispels_done[1] = {}, {} data.dispels_received[0], data.dispels_received[1] = {}, {} -- clear overall damage data for k, v in pairs(data.damage[0]) do data.damage[0][k] = nil end -- clear current damage data for k, v in pairs(data.damage[1]) do data.damage[1][k] = nil end -- clear overall heal data for k, v in pairs(data.heal[0]) do data.heal[0][k] = nil end -- clear current heal data for k, v in pairs(data.heal[1]) do data.heal[1][k] = nil end for i=1,10 do if window[i] then window[i].scroll = 0 end end -- reset the fight timers too parser:ResetFight(true) if ShaguDPS.fights then ShaguDPS.fights:Reset() end -- reload all windows window.Refresh() end local function CreateBar(parent, i, background) local prefix = "ShaguDPS" .. (parent:GetID() == 1 and "" or parent:GetID()) parent.bars[i] = parent.bars[i] or CreateFrame("StatusBar", prefix .. "Bar" .. i, parent) parent.bars[i].parent = parent parent.bars[i]:SetStatusBarTexture(textures[config.texture] or textures[1]) parent.bars[i]:SetPoint("TOPLEFT", parent, "TOPLEFT", 2, -config.height * (i-1) - 22) parent.bars[i]:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -2, -config.height * (i-1) - 22) parent.bars[i]:SetHeight(config.height - config.spacing) parent.bars[i]:SetFrameLevel(4) parent.bars[i].lowerBar = parent.bars[i].lowerBar or CreateFrame("StatusBar", prefix .. "LowerBar" .. i, parent) parent.bars[i].lowerBar:SetStatusBarTexture(textures[config.texture] or textures[1]) parent.bars[i].lowerBar:SetPoint("TOPLEFT", parent, "TOPLEFT", 2, -config.height * (i-1) - 22) parent.bars[i].lowerBar:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -2, -config.height * (i-1) - 22) parent.bars[i].lowerBar:SetStatusBarColor(1, 1, 1, .4) parent.bars[i].lowerBar:SetHeight(config.height - config.spacing) parent.bars[i].lowerBar:SetFrameLevel(2) parent.bars[i].textLeft = parent.bars[i].textLeft or parent.bars[i]:CreateFontString("Status", "OVERLAY", "GameFontNormal") parent.bars[i].textLeft:SetFont(STANDARD_TEXT_FONT, 10, "THINOUTLINE") parent.bars[i].textLeft:SetJustifyH("LEFT") parent.bars[i].textLeft:SetFontObject(GameFontWhite) parent.bars[i].textLeft:SetParent(parent.bars[i]) parent.bars[i].textLeft:ClearAllPoints() parent.bars[i].textLeft:SetPoint("TOPLEFT", parent.bars[i], "TOPLEFT", 5, 1) parent.bars[i].textLeft:SetPoint("BOTTOMRIGHT", parent.bars[i], "BOTTOMRIGHT", -5, 0) parent.bars[i].textRight = parent.bars[i].textRight or parent.bars[i]:CreateFontString("Status", "OVERLAY", "GameFontNormal") parent.bars[i].textRight:SetFont(STANDARD_TEXT_FONT, 10, "THINOUTLINE") parent.bars[i].textRight:SetJustifyH("RIGHT") parent.bars[i].textRight:SetFontObject(GameFontWhite) parent.bars[i].textRight:SetParent(parent.bars[i]) parent.bars[i].textRight:ClearAllPoints() parent.bars[i].textRight:SetPoint("TOPLEFT", parent.bars[i], "TOPLEFT", 5, 1) parent.bars[i].textRight:SetPoint("BOTTOMRIGHT", parent.bars[i], "BOTTOMRIGHT", -5, 0) for _, field in pairs({"textRate", "textPercent"}) do local bar = parent.bars[i] bar[field] = bar[field] or bar:CreateFontString(nil, "OVERLAY", "GameFontNormal") bar[field]:SetFont(STANDARD_TEXT_FONT, 10, "THINOUTLINE") bar[field]:SetFontObject(GameFontWhite) bar[field]:SetJustifyH("RIGHT") if ShaguDPS.classic then skin.Font(bar[field], math.max(8, math.min(13, config.height - 4))) bar[field]:SetTextColor(1, 1, 1) end end -- classic: shadowed text, a dark track behind the bar and a hover glow local bar = parent.bars[i] if ShaguDPS.classic then local size = math.max(8, math.min(13, config.height - 4)) skin.Font(bar.textLeft, size) skin.Font(bar.textRight, size) bar.textLeft:SetTextColor(1, 1, 1) bar.textRight:SetTextColor(1, 1, 1) -- the track sits below the overheal bar, and follows the bar's visibility if not bar.track then bar.track = CreateFrame("Frame", nil, parent) bar.track:SetFrameLevel(1) bar.track.tex = bar.track:CreateTexture(nil, "BACKGROUND") bar.track.tex:SetAllPoints(bar.track) bar:SetScript("OnShow", function() this.track:Show() end) bar:SetScript("OnHide", function() this.track:Hide() end) end bar.track:SetAllPoints(bar) bar.track.tex:SetTexture(textures[config.texture] or textures[1]) bar.track.tex:SetVertexColor(0.2, 0.2, 0.2, 0.35) if not bar:IsShown() then bar.track:Hide() end bar.glow = bar.glow or skin.Glow(bar, "OVERLAY", 0.35) bar.glow:Hide() end parent.bars[i]:EnableMouse(true) parent.bars[i]:SetScript("OnEnter", barTooltipShow) parent.bars[i]:SetScript("OnLeave", barTooltipHide) return parent.bars[i] end local function btnEnter() if this.tooltip then GameTooltip_SetDefaultAnchor(GameTooltip, this) for i, data in pairs(this.tooltip) do if type(data) == "string" then GameTooltip:AddLine(data) elseif type(data) == "table" then GameTooltip:AddDoubleLine(data[1], data[2]) end end GameTooltip:Show() end if this.hoverOn then return this:hoverOn() end this:SetBackdropBorderColor(1,.8,0,1) end local function btnLeave() if this.tooltip then GameTooltip:Hide() end if this.hoverOff then return this:hoverOff() end this:SetBackdropBorderColor(.4,.4,.4,1) end local function announce(text) local type = tbc and ChatFrameEditBox:GetAttribute("chatType") or ChatFrameEditBox.chatType local language = tbc and ChatFrameEditBox:GetAttribute("language") or ChatFrameEditBox.language local channel = tbc and ChatFrameEditBox:GetAttribute("channelTarget") or ChatFrameEditBox.channelTarget local target = tbc and ChatFrameEditBox:GetAttribute("tellTarget") or ChatFrameEditBox.tellTarget if type == "WHISPER" then SendChatMessage(text, type, language, target) elseif type == "CHANNEL" then SendChatMessage(text, type, language, channel); else SendChatMessage(text, type, language); end end local function GetCaps(view, values) local values = values or {} -- reset/empty values values.best = 0 values.all = 0 values.persecond_best = 0 values.persecond_all = 0 values.effective_best = 0 values.effective_all = 0 values.effective_persecond_best = 0 values.effective_persecond_all = 0 for name, data in pairs(view) do local val = 0 -- calculate normal values if data["_sum"] and data["_ctime"] then local time = ctime(data) values.all = values.all + data["_sum"] if data["_sum"] > values.best then values.best = data["_sum"] end values.persecond_all = values.persecond_all + data["_sum"] / time if data["_sum"] / time > values.persecond_best then values.persecond_best = data["_sum"] / time end end -- calculate effective values if data["_esum"] and data["_ctime"] then local time = ctime(data) values.effective_all = values.effective_all + data["_esum"] if data["_esum"] > values.effective_best then values.effective_best = data["_esum"] end values.effective_persecond_all = values.effective_persecond_all + data["_esum"] / time if data["_esum"] / time > values.effective_persecond_best then values.effective_persecond_best = data["_esum"] / time end end end return values end local function GetData(unitdata, values) local values = values or {} -- read normal values values.value = unitdata["_sum"] values.value_persecond = round(values.value / ctime(unitdata), 1) values.percent = values.value == 0 and 0 or round(values.value / values.all * 100,1) values.percent_persecond = values.value_persecond == 0 and 0 or round(values.value_persecond / values.persecond_all * 100, 1) -- read effective values if unitdata["_esum"] then values.effective_value = unitdata["_esum"] values.effective_value_persecond = round(values.effective_value / ctime(unitdata), 1) values.effective_percent = values.effective_value == 0 and 0 or round(values.effective_value / values.effective_all * 100, 1) values.effective_percent_persecond = values.effective_value_persecond == 0 and 0 or round(values.effective_value_persecond / values.effective_persecond_all * 100,1) values.uneffective_value = values.value - values.effective_value values.uneffective_value_persecond = values.value_persecond - values.effective_value_persecond else values.effective_value = 0 values.effective_value_persecond = 0 values.effective_percent = 0 values.effective_percent_persecond = 0 values.uneffective_value = 0 values.uneffective_value_persecond = 0 end -- threat percent of the tank values.threat_perc = window.FormatThreatPercent(unitdata["_perc"]) values.threat_tps_short = window.FormatRate(unitdata["_tps"]) values.percent_display = window.FormatThreatPercent(values.percent) values.effective_percent_display = window.FormatThreatPercent(values.effective_percent) -- short numbers for the bars values.value_short = short(values.value) if unitdata._minimum then values.value_short = short(unitdata._minimum) .. "-" .. values.value_short end values.persecond_short = window.FormatRate(values.value / ctime(unitdata)) values.effective_short = short(values.effective_value) values.effective_persecond_short = window.FormatRate(values.effective_value / ctime(unitdata)) values.uneffective_short = short(values.uneffective_value) -- check pet and detect owner/unit names local pet = not classes[data["classes"][values.name]] and data["classes"][values.name] ~= "__other__" local unit = pet and data["classes"][values.name] or values.name -- merge pet/owner strings if option is set if config.merge_pets == 0 then -- "Wolf (Hunter)" becomes "Hunter - Wolf" local petname = pet and string.gsub(values.name, "%s%(.-%)$", "") or nil values.name = pet and unit .. " - " .. petname or unit else values.name = unit end values.class = not unitdata._pull and data.classes[unit] or nil -- write color into view -- default to faded name colors local r, g, b = str2rgb(values.name) values.color = values.color or {} values.color.r = r / 4 + .4 values.color.g = g / 4 + .4 values.color.b = b / 4 + .4 -- the pull aggro line is red if unitdata["_pull"] then values.color.r, values.color.g, values.color.b = .9, .15, .15 end -- replace color by class colors if possible if classes[data["classes"][unit]] then -- set color to player class colors values.color.r = RAID_CLASS_COLORS[data["classes"][unit]].r values.color.g = RAID_CLASS_COLORS[data["classes"][unit]].g values.color.b = RAID_CLASS_COLORS[data["classes"][unit]].b if config.texture == 8 then local color = modern_colors[data.classes[unit]] values.color.r, values.color.g, values.color.b = color[1], color[2], color[3] elseif config.pastel == 1 then values.color.r = (values.color.r + .5) * .5 values.color.g = (values.color.g + .5) * .5 values.color.b = (values.color.b + .5) * .5 end end return values end local function Refresh(self, force, report) -- skip refreshs on legacy calls if not self or type(self) == "boolean" then return end -- assign shortcuts local values, buttons = self.values, self.buttons local wid = self:GetID() -- DPS and HPS windows of older versions become Damage and Heal local view = config[wid].view if view_merged[view] then config[wid].view = view_merged[view] end if not view_templates[config[wid].view] then config[wid].view = 1 end -- config changes if force then if config.visible == 1 then self:Show() else self:Hide() end for _, button in pairs(buttons) do button.caption:SetTextColor(1,1,1,1) end if self.titleRule then if config.backdrop == 1 then self.titleRule:Show() else self.titleRule:Hide() end end -- update backdrop borders if config.backdrop == 1 and ShaguDPS.classic then local c = skin.colors self:SetBackdrop(skin.backdrop_window) self:SetBackdropColor(c.bg[1], c.bg[2], c.bg[3], .9) self.border:SetBackdrop(skin.backdrop_border) self.border:SetBackdropBorderColor(c.border[1], c.border[2], c.border[3], 1) elseif config.backdrop == 1 then -- window background self:SetBackdrop(backdrop_window) self:SetBackdropColor(.5,.5,.5,.5) -- window border self.border:SetBackdrop(backdrop_border) self.border:SetBackdropBorderColor(.7,.7,.7,1) else self:SetBackdrop(nil) self.border:SetBackdrop(nil) end -- update panel button appearance if config[wid].view == 1 then self.btnDamage.caption:SetTextColor(1,.9,0,1) self.btnMode.caption:SetText("Damage") elseif config[wid].view == 3 then self.btnHeal.caption:SetTextColor(1,.9,0,1) self.btnMode.caption:SetText("Heal") elseif config[wid].view == 6 then self.btnDispels.caption:SetTextColor(1,.9,0,1) self.btnMode.caption:SetText("Dispels") self[config[wid].dispels_received == 1 and "btnReceived" or "btnDone"].caption:SetTextColor(1,.9,0,1) elseif config[wid].view == 5 then self.btnThreat.caption:SetTextColor(1,.9,0,1) self.btnMode.caption:SetText("Threat") end -- a history fight that was dropped from the list falls back to current if config[wid].segment == 2 and not (ShaguDPS.fights and ShaguDPS.fights:Get(config[wid].fight)) then config[wid].segment = 1 end if config[wid].segment == 0 then self.btnOverall.caption:SetTextColor(1,.9,0,1) elseif config[wid].segment == 1 then self.btnCurrent.caption:SetTextColor(1,.9,0,1) elseif config[wid].segment == 2 then self.btnFights.caption:SetTextColor(1,.9,0,1) end self.btnSegment.caption:SetText(SegmentName(config[wid])) -- lock icon of the title row if self.btnLock then local locked = config.lock == 1 self.btnLock.tex:SetTexture("Interface\\AddOns\\ShaguDPS\\img\\" .. (locked and "lock" or "unlock")) self.btnLock.tooltip = { locked and "Unlock Windows" or "Lock Windows", locked and "|cffffffffAllow moving and resizing" or "|cffffffffPrevent moving and resizing", } end self:SetWidth(math.max(MIN_WIDTH, config[wid].width or MIN_WIDTH)) self:SetHeight(config.height * (config[wid].bars or 8) + 22 + (config[wid].bars == 0 and 2 or 3)) end -- hidden windows catch up when they are shown again if not force and not report and not self:IsVisible() then self.needs_refresh = true return end -- set view to damage or heal local fight = config[wid].segment == 2 and ShaguDPS.fights and ShaguDPS.fights:ForWindow(config[wid]) if config[wid].segment == 2 and not fight then config[wid].segment = 1 end if config[wid].view == 1 then self.segment = fight and fight.damage or data.damage[(config[wid].segment or 0)] elseif config[wid].view == 3 then self.segment = fight and fight.heal or data.heal[(config[wid].segment or 0)] elseif config[wid].view == 6 then self.segment = window.GetDispelSegment(config[wid], fight) elseif config[wid].view == 5 then self.segment = data.threat end -- threat has no segments: the segment button names the target instead if config[wid].view == 5 then local target = ShaguDPS.threat and ShaguDPS.threat.target self.btnSegment.caption:SetText(target or "No Target") -- Size to the target text and center the two buttons as one title group. -- Leave room for the icons at both ends of the title bar. local width = math.min(math.max(50, self.btnSegment.caption:GetStringWidth() + 12), math.max(50, self:GetWidth() - 170)) self.btnSegment:SetWidth(width) self.btnSegment:ClearAllPoints() self.btnSegment:SetPoint("RIGHT", self.title, "CENTER", (width - 51) / 2, 0) else self.btnSegment:SetWidth(50) self.btnSegment:ClearAllPoints() self.btnSegment:SetPoint("RIGHT", self.title, "CENTER", TITLE_SHIFT - .5, 0) if not force and self.lastview == 5 then self.btnSegment.caption:SetText(SegmentName(config[wid])) end end if config[wid].view == 6 then self.btnSegment:SetWidth(90) self.btnSegment.caption:SetText((config[wid].dispels_received == 1 and "Received: " or "Done: ") .. SegmentName(config[wid])) elseif self.lastview == 6 then self.btnSegment.caption:SetText(SegmentName(config[wid])) end self.btnMode:ClearAllPoints() self.btnMode:SetPoint("LEFT", self.btnSegment, "RIGHT", 1, 0) self.lastview = config[wid].view -- hint while the threat list is empty if (config[wid].view == 5 or config[wid].view == 6) and not next(self.segment) then self.hint = self.hint or self:CreateFontString(nil, "OVERLAY", "GameFontDisable") self.hint:SetPoint("TOPLEFT", self, "TOPLEFT", 8, -26) self.hint:SetPoint("TOPRIGHT", self, "TOPRIGHT", -8, -26) self.hint:SetJustifyH("LEFT") self.hint:SetFont(STANDARD_TEXT_FONT, 10) if config[wid].view == 6 then self.hint:SetText(window.DispelStatus(config[wid], fight, ShaguDPS.dispels.available) or "No successful dispels recorded for this segment.") else self.hint:SetText(ShaguDPS.threat:GetStatus()) end self.hint:Show() elseif self.hint then self.hint:Hide() end -- read view settings local template = view_templates[config[wid].view] local sort = sort_algorithms[template.sort] -- report to chat if flag is set if report then local name = view_templates[config[wid].view].name if config[wid].view == 6 then name = name .. (config[wid].dispels_received == 1 and " Received" or " Done") end local seg = SegmentName(config[wid]) if config[wid].segment == 2 then local f = ShaguDPS.fights:Get(config[wid].fight) seg = seg .. (f.kill and " (kill, " or " (wipe, ") .. SecondsToTime(math.floor(f.duration)) .. ")" end announce("ShaguDPS - " .. seg .. " " .. name .. ":") if config[wid].view == 6 then local status = window.DispelStatus(config[wid], fight, ShaguDPS.dispels.available) if status then announce(status) end end end -- load caps of the current view SetFightTime(config[wid].segment, config[wid]) local cache if config[wid].view ~= 5 then cache = window.DisplaySnapshot(self.segment, sort) if window.PaintUnchanged(self, cache, fight_time, force, report) then return end else self.paintState = nil end -- clear previous results for id, bar in pairs(self.bars) do bar.lowerBar:Hide() bar:Hide() end self.values = self.GetCaps(self.segment, self.values) -- Threat bypasses paint caching; its percentages/TPS can change independently. local keys, count if cache then keys, count = cache.keys, cache.count else cache = window.DisplaySnapshot(self.segment, sort) keys, count = cache.keys, cache.count end local first = self.scroll + 1 local last = math.min(count, self.scroll + (config[wid].bars or 8)) if report then first, last = 1, math.min(count, math.max(last, 10)) end local columnWidths = {1, 1, 1} -- only the units that get a bar (or a chat line) need their values read for i = first, last do local name = keys[i] local unitdata = self.segment[name] -- attach name to values self.values.name = name -- load data values of the current unit self.values = self.GetData(unitdata, self.values) local a = template.bar_string_params local bar = i - self.scroll if bar >= 1 and bar <= (config[wid].bars or 8) then self.bars[bar] = not force and self.bars[bar] or CreateBar(self, bar) -- attach unit and titles to bar self.bars[bar].title = self.values.name self.bars[bar].unit = name self.bars[bar]:SetMinMaxValues(0, self.values[template.bar_max]) self.bars[bar]:SetValue(self.values[template.bar_val]) -- enable lower bar if template requires it if template.bar_lower_max and template.bar_lower_val then self.bars[bar].lowerBar:SetMinMaxValues(0, self.values[template.bar_lower_max]) self.bars[bar].lowerBar:SetValue(self.values[template.bar_lower_val]) self.bars[bar].lowerBar:Show() else self.bars[bar].lowerBar:Hide() end self.bars[bar]:SetStatusBarColor(self.values.color.r, self.values.color.g, self.values.color.b, 1) self.bars[bar].textLeft:SetText(i .. ". " .. self.values.name) window.UpdateClassIcon(self.bars[bar], self.values.class) local row = self.bars[bar] row.textRight:SetText(self.values[a[1]]) row.textRate:SetText((unitdata._pull or config[wid].view == 6) and "" or ("(" .. self.values[a[2]] .. " " .. (config[wid].view == 1 and "DPS" or config[wid].view == 3 and "HPS" or "TPS") .. ")")) local percentage = unitdata._pull and self.values.threat_perc or self.values[a[3]] row.textPercent:SetText("[" .. percentage .. "%]") for column, field in pairs({"textRight", "textRate", "textPercent"}) do columnWidths[column] = math.max(columnWidths[column], math.ceil(row[field]:GetStringWidth()) + 1) end self.bars[bar]:Show() end -- report to chat if flag is set if report and i <= 10 then local c = template.chat_string_params or a local chat = string.format(template.chat_string, self.values[c[1]], self.values[c[2]], self.values[c[3]], self.values[c[4]], self.values[c[5]]) if config[wid].view == 5 and unitdata._pull then chat = string.format("%s [%s%%]", self.values.value_short, self.values.threat_perc) end announce(i .. ". " .. self.values.name .. " " .. chat) end end window.AlignValueColumns(self.bars, config[wid].bars or 8, columnWidths) end local function Resize(self) local wid = self:GetID() local width = self:GetWidth() local height = self:GetTop() - self:GetBottom() local bars = (height - 22) / config.height bars = math.floor(bars) config[wid].width = width if config[wid].bars ~= bars then config[wid].bars = bars self:Refresh() end end local function CreateWindow(wid) -- create default config config[wid] = config[wid] or {} config[wid].bars = config[wid].bars or 8 config[wid].width = math.max(MIN_WIDTH, config[wid].width or MIN_WIDTH) config[wid].segment = config[wid].segment or 1 config[wid].view = config[wid].view or 1 local frame = CreateFrame("Frame", "ShaguDPSWindow" .. (wid == 1 and "" or wid), UIParent) frame.scroll = 0 frame.GetCaps = GetCaps frame.GetData = GetData frame.Refresh = Refresh frame.Resize = Resize frame.LoadPosition = function() if ShaguDPS_Config and ShaguDPS_Config[frame:GetID()] and ShaguDPS_Config[frame:GetID()].pos then -- load config position if existing frame:ClearAllPoints() frame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", unpack(ShaguDPS_Config[frame:GetID()].pos)) else -- use default window position frame:ClearAllPoints() frame:SetPoint("RIGHT", UIParent, "RIGHT", -100, -100) end end frame:SetID(wid) frame:EnableMouse(true) frame:EnableMouseWheel(1) frame:SetResizable(true) frame:SetMinResize(MIN_WIDTH, 22) frame:RegisterForDrag("LeftButton") frame:SetMovable(true) frame:SetScript("OnDragStart", function() if config.lock == 0 then frame:StartMoving() end end) frame:SetScript("OnDragStop", function() frame:StopMovingOrSizing() ShaguDPS_Config[frame:GetID()].pos = { frame:GetCenter() } end) frame:SetScript("OnMouseWheel", barScrollWheel) frame:SetClampedToScreen(true) frame:SetScript("OnUpdate", function() -- update config on resize if this.sizing then this:Resize() end -- only check for updates every .2 seconds if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + .2 end -- check for resize button if config.lock == 0 and MouseIsOver(this) then this.btnResize:SetAlpha(.5) else this.btnResize:SetAlpha(0) end -- refresh window if needed if this.needs_refresh then this.needs_refresh = nil this:Refresh() end end) frame:RegisterEvent("PLAYER_LOGIN") frame:SetScript("OnEvent", frame.LoadPosition) frame.LoadPosition() frame.title = frame:CreateTexture(nil, "NORMAL") frame.title:SetTexture(0,0,0,.6) frame.title:SetHeight(20) frame.title:SetPoint("TOPLEFT", 2, -2) frame.title:SetPoint("TOPRIGHT", -2, -2) frame.btnSegment = CreateFrame("Button", "ShaguDPSDamage", frame) frame.btnSegment:SetPoint("RIGHT", frame.title, "CENTER", TITLE_SHIFT - .5, 0) frame.btnSegment:SetFrameStrata("MEDIUM") frame.btnSegment:SetHeight(16) frame.btnSegment:SetWidth(50) frame.btnSegment:SetBackdrop(backdrop) frame.btnSegment:SetBackdropColor(.2,.2,.2,1) frame.btnSegment:SetBackdropBorderColor(.4,.4,.4,1) frame.btnSegment.caption = frame.btnSegment:CreateFontString("ShaguDPSTitle", "OVERLAY", "GameFontWhite") frame.btnSegment.caption:SetFont(STANDARD_TEXT_FONT, 9) frame.btnSegment.caption:SetText("Overall") frame.btnSegment.caption:SetAllPoints() frame.btnSegment.tooltip = { "Select Segment", "|cffffffffOverall, Current" } frame.btnSegment:SetScript("OnEnter", btnEnter) frame.btnSegment:SetScript("OnLeave", btnLeave) frame.btnSegment:SetScript("OnClick", function() frame.btnDamage:Hide() frame.btnHeal:Hide() frame.btnThreat:Hide() frame.btnDispels:Hide() frame.btnDone:Hide() frame.btnReceived:Hide() -- the threat view always shows the current target if frame.btnCurrent:IsShown() or config[frame:GetID()].view == 5 then frame.btnOverall:Hide() frame.btnFights:Hide() frame.btnCurrent:Hide() else frame.btnOverall:Show() frame.btnFights:Show() frame.btnCurrent:Show() end end) frame.btnMode = CreateFrame("Button", "ShaguDPSDamage", frame) frame.btnMode:SetPoint("LEFT", frame.title, "CENTER", TITLE_SHIFT + .5, 0) frame.btnMode:SetFrameStrata("MEDIUM") frame.btnMode:SetHeight(16) frame.btnMode:SetWidth(50) frame.btnMode:SetBackdrop(backdrop) frame.btnMode:SetBackdropColor(.2,.2,.2,1) frame.btnMode:SetBackdropBorderColor(.4,.4,.4,1) frame.btnMode.caption = frame.btnMode:CreateFontString("ShaguDPSTitle", "OVERLAY", "GameFontWhite") frame.btnMode.caption:SetFont(STANDARD_TEXT_FONT, 9) frame.btnMode.caption:SetText("Mode: Damage") frame.btnMode.caption:SetAllPoints() frame.btnMode.tooltip = { "Select Mode", "|cffffffffDamage, Heal, Threat, Dispels" } frame.btnMode:SetScript("OnEnter", btnEnter) frame.btnMode:SetScript("OnLeave", btnLeave) frame.btnMode:SetScript("OnClick", function() if frame.btnDamage:IsShown() then frame.btnDamage:Hide() frame.btnHeal:Hide() frame.btnThreat:Hide() frame.btnDispels:Hide() frame.btnDone:Hide() frame.btnReceived:Hide() frame.btnOverall:Hide() frame.btnFights:Hide() frame.btnCurrent:Hide() else frame.btnDamage:Show() frame.btnHeal:Show() frame.btnThreat:Show() frame.btnDispels:Show() if config[frame:GetID()].view == 6 then frame.btnDone:Show() frame.btnReceived:Show() end if frame.menuView then frame.menuView:SetPoint("BOTTOMRIGHT", config[frame:GetID()].view == 6 and frame.btnReceived or frame.btnDispels, "BOTTOMRIGHT", 6, -6) end frame.btnOverall:Hide() frame.btnFights:Hide() frame.btnCurrent:Hide() end end) for name, template in pairs(menubuttons) do frame["btn"..name] = CreateFrame("Button", "ShaguDPS" .. name, frame) local button = frame["btn"..name] local template = template local name = name if template[6] == "view" or template[6] == "dispels_received" then button:SetPoint("CENTER", frame.btnMode, "CENTER", 0, -17-template[1]*14) else button:SetPoint("CENTER", frame.title, "CENTER", template[3] + TITLE_SHIFT, -17-template[1]*14) end button:SetFrameStrata("HIGH") button:SetHeight(16) button:SetWidth(50) button:SetBackdrop(backdrop) button:SetBackdropColor(.2,.2,.2,1) button:SetBackdropBorderColor(.4,.4,.4,1) button:Hide() button.caption = button:CreateFontString("ShaguDPS"..name.."Title", "OVERLAY", "GameFontWhite") button.caption:SetFont(STANDARD_TEXT_FONT, 9) button.caption:SetText(name) button.caption:SetAllPoints() button.tooltip = { template[4], template[5] } button:SetScript("OnEnter", btnEnter) button:SetScript("OnLeave", btnLeave) button:SetScript("OnClick", function() if name == "Fights" then return frame:ToggleFightList() end config[frame:GetID()][template[6]] = template[2] if template[6] == "dispels_received" then config[frame:GetID()].view = 6 end frame.scroll = 0 frame:Refresh(true) for button in pairs(menubuttons) do frame["btn"..button]:Hide() end end) end frame.btnAnnounce = CreateFrame("Button", "ShaguDPSReset", frame) frame.btnAnnounce:SetPoint("LEFT", frame.title, "LEFT", 2, 0) frame.btnAnnounce:SetFrameStrata("MEDIUM") frame.btnAnnounce:SetHeight(16) frame.btnAnnounce:SetWidth(16) frame.btnAnnounce:SetBackdrop(backdrop) frame.btnAnnounce:SetBackdropColor(.2,.2,.2,1) frame.btnAnnounce:SetBackdropBorderColor(.4,.4,.4,1) frame.btnAnnounce.tooltip = { "Send to Chat", { "|cffffffffClick", "|cffaaaaaaAsk to anounce all data."}, { "|cffffffffShift-Click", "|cffaaaaaaAnnounce all data."}, } frame.btnAnnounce.tex = frame.btnAnnounce:CreateTexture() frame.btnAnnounce.tex:SetWidth(10) frame.btnAnnounce.tex:SetHeight(10) frame.btnAnnounce.tex:SetPoint("CENTER", 0, 0) frame.btnAnnounce.tex:SetTexture("Interface\\AddOns\\ShaguDPS" .. (tbc and "-tbc" or "") .. "\\img\\announce") frame.btnAnnounce:SetScript("OnEnter", btnEnter) frame.btnAnnounce:SetScript("OnLeave", btnLeave) frame.btnAnnounce:SetScript("OnClick", function() if IsShiftKeyDown() then -- reload / anounce frame:Refresh(nil, true) else local ctype = tbc and ChatFrameEditBox:GetAttribute("chatType") or ChatFrameEditBox.chatType local color = chatcolors[ctype] if not color then color = "|cff00FAF6" end local name = view_templates[config[frame:GetID()].view].name local text = "Post |cffffdd00" .. name .. "|r data into /" .. color..string.lower(ctype) .. "|r?" local dialog = StaticPopupDialogs["SHAGUMETER_QUESTION"] dialog.text = text dialog.OnAccept = function() frame:Refresh(nil, true) end StaticPopup_Show("SHAGUMETER_QUESTION") end end) frame.btnSettings = CreateFrame("Button", "ShaguDPSReset", frame) frame.btnSettings:SetPoint("LEFT", frame.btnAnnounce, "RIGHT", 1, 0) frame.btnSettings:SetFrameStrata("MEDIUM") frame.btnSettings:SetHeight(16) frame.btnSettings:SetWidth(16) frame.btnSettings:SetBackdrop(backdrop) frame.btnSettings:SetBackdropColor(.2,.2,.2,1) frame.btnSettings:SetBackdropBorderColor(.4,.4,.4,1) frame.btnSettings.tooltip = { "Settings", "|cffffffffShow Configuration Window" } frame.btnSettings.tex = frame.btnSettings:CreateTexture() frame.btnSettings.tex:SetWidth(10) frame.btnSettings.tex:SetHeight(10) frame.btnSettings.tex:SetPoint("CENTER", 0, 0) frame.btnSettings.tex:SetTexture("Interface\\AddOns\\ShaguDPS" .. (tbc and "-tbc" or "") .. "\\img\\settings") frame.btnSettings:SetScript("OnEnter", btnEnter) frame.btnSettings:SetScript("OnLeave", btnLeave) frame.btnSettings:SetScript("OnClick", function() if ShaguDPS.settings:IsShown() then ShaguDPS.settings:Hide() else ShaguDPS.settings:Show() end end) frame.btnReset = CreateFrame("Button", "ShaguDPSReset", frame) frame.btnReset:SetPoint("RIGHT", frame.title, "RIGHT", -2, 0) frame.btnReset:SetFrameStrata("MEDIUM") frame.btnReset:SetHeight(16) frame.btnReset:SetWidth(16) frame.btnReset:SetBackdrop(backdrop) frame.btnReset:SetBackdropColor(.2,.2,.2,1) frame.btnReset:SetBackdropBorderColor(.4,.4,.4,1) frame.btnReset.tooltip = { "Reset Data", { "|cffffffffClick", "|cffaaaaaaAsk to reset all data."}, { "|cffffffffShift-Click", "|cffaaaaaaReset all data."}, } frame.btnReset.tex = frame.btnReset:CreateTexture() frame.btnReset.tex:SetWidth(10) frame.btnReset.tex:SetHeight(10) frame.btnReset.tex:SetPoint("CENTER", 0, 0) frame.btnReset.tex:SetTexture("Interface\\AddOns\\ShaguDPS" .. (tbc and "-tbc" or "") .. "\\img\\reset") frame.btnReset:SetScript("OnEnter", btnEnter) frame.btnReset:SetScript("OnLeave", btnLeave) frame.btnReset:SetScript("OnClick", function() if IsShiftKeyDown() then ResetData() else local dialog = StaticPopupDialogs["SHAGUMETER_QUESTION"] dialog.text = "Do you wish to reset the data?" dialog.OnAccept = ResetData StaticPopup_Show("SHAGUMETER_QUESTION") end end) frame.btnWindow = CreateFrame("Button", "ShaguDPSReset", frame) frame.btnWindow:SetPoint("RIGHT", frame.btnReset, "LEFT", -1, 0) frame.btnWindow:SetFrameStrata("MEDIUM") frame.btnWindow:SetHeight(16) frame.btnWindow:SetWidth(16) frame.btnWindow:SetBackdrop(backdrop) frame.btnWindow:SetBackdropColor(.2,.2,.2,1) frame.btnWindow:SetBackdropBorderColor(.4,.4,.4,1) frame.btnWindow.tex = frame.btnWindow:CreateTexture() frame.btnWindow.tex:SetWidth(10) frame.btnWindow.tex:SetHeight(10) frame.btnWindow.tex:SetPoint("CENTER", 0, 0) if frame:GetID() == 1 then frame.btnWindow.tex:SetTexture("Interface\\AddOns\\ShaguDPS" .. (tbc and "-tbc" or "") .. "\\img\\plus") frame.btnWindow.tooltip = { "New Window", "|cffffffffCreate a new window" } frame.btnWindow:SetScript("OnClick", function() for i=1,10 do if not ShaguDPS.window[i] then -- new windows start with the size and view of the first one config[i] = { width = config[1].width, bars = config[1].bars, view = config[1].view, segment = config[1].segment, dispels_received = config[1].dispels_received, } ShaguDPS.window[i] = CreateWindow(i) ShaguDPS.window.Refresh(true) return end end end) else frame.btnWindow.tex:SetTexture("Interface\\AddOns\\ShaguDPS" .. (tbc and "-tbc" or "") .. "\\img\\minus") frame.btnWindow.tooltip = { "Remove Window", "|cffffffffDelete this window" } frame.btnWindow:SetScript("OnClick", function() local wid = frame:GetID() window[wid]:Hide() window[wid] = nil config[wid] = nil window.Refresh(true) end) end frame.btnWindow:SetScript("OnEnter", btnEnter) frame.btnWindow:SetScript("OnLeave", btnLeave) -- lock / unlock all windows frame.btnLock = CreateFrame("Button", nil, frame) frame.btnLock:SetPoint("RIGHT", frame.btnWindow, "LEFT", -1, 0) frame.btnLock:SetFrameStrata("MEDIUM") frame.btnLock:SetHeight(16) frame.btnLock:SetWidth(16) frame.btnLock:SetBackdrop(backdrop) frame.btnLock:SetBackdropColor(.2,.2,.2,1) frame.btnLock:SetBackdropBorderColor(.4,.4,.4,1) frame.btnLock.tex = frame.btnLock:CreateTexture() frame.btnLock.tex:SetWidth(10) frame.btnLock.tex:SetHeight(10) frame.btnLock.tex:SetPoint("CENTER", 0, 0) frame.btnLock.tex:SetTexture("Interface\\AddOns\\ShaguDPS\\img\\unlock") frame.btnLock:SetScript("OnEnter", btnEnter) frame.btnLock:SetScript("OnLeave", btnLeave) frame.btnLock:SetScript("OnClick", function() config.lock = config.lock == 1 and 0 or 1 window.Refresh(true) local check = ShaguDPS.settings.checks and ShaguDPS.settings.checks.lock if check then check:SetChecked(config.lock == 1) end -- show the new state right away while the mouse is still on it GameTooltip:Hide() btnEnter() end) -- list of past boss fights, opened from the "Fights" entry frame.fightList = CreateFrame("Frame", nil, frame) frame.fightList:SetFrameStrata("HIGH") frame.fightList:SetPoint("TOPLEFT", frame.btnFights, "TOPRIGHT", 4, 2) frame.fightList:SetWidth(150) frame.fightList:Hide() frame.fightList.entries = {} frame.btnFights:SetScript("OnHide", function() frame.fightList:Hide() end) frame.ToggleFightList = function(self) local list = self.fightList if list:IsShown() then return list:Hide() end local fights = ShaguDPS.fights and ShaguDPS.fights.list or {} local count = math.max(1, table.getn(fights)) for i = 1, math.max(count, table.getn(list.entries)) do local entry = list.entries[i] if not entry and i <= count then entry = CreateFrame("Button", nil, list) entry:SetHeight(14) entry:SetPoint("TOPLEFT", list, "TOPLEFT", 6, -6 - (i - 1) * 14) entry:SetPoint("TOPRIGHT", list, "TOPRIGHT", -6, -6 - (i - 1) * 14) entry.caption = entry:CreateFontString(nil, "OVERLAY", "GameFontWhite") entry.caption:SetFont(STANDARD_TEXT_FONT, 9) entry.caption:SetPoint("LEFT", entry, "LEFT", 2, 0) entry.caption:SetPoint("RIGHT", entry, "RIGHT", -34, 0) entry.caption:SetJustifyH("LEFT") entry.time = entry:CreateFontString(nil, "OVERLAY", "GameFontWhite") entry.time:SetFont(STANDARD_TEXT_FONT, 9) entry.time:SetPoint("RIGHT", entry, "RIGHT", -2, 0) entry.time:SetJustifyH("RIGHT") if ShaguDPS.classic then skin.Font(entry.caption, 9) skin.Font(entry.time, 9) skin.MenuEntry(entry) end entry:SetScript("OnEnter", btnEnter) entry:SetScript("OnLeave", btnLeave) entry:SetScript("OnClick", function() if not this.id then return end config[self:GetID()].segment = 2 config[self:GetID()].fight = this.id self.scroll = 0 for b in pairs(menubuttons) do self["btn" .. b]:Hide() end self:Refresh(true) end) list.entries[i] = entry end if entry then local fight = fights[i] if i > count then entry:Hide() elseif not fight then entry.id = nil entry.tooltip = nil entry.caption:SetText("|cff888888No boss fights yet") entry.time:SetText("") entry:Show() else local selected = config[self:GetID()].segment == 2 and config[self:GetID()].fight == fight.id local color = selected and "|cffffd100" or (fight.kill and "|cffffffff" or "|cffaaaaaa") entry.id = fight.id entry.caption:SetText(color .. fight.name .. (fight.kill and "" or " |cff888888(wipe)")) entry.time:SetText("|cffaaaaaa" .. string.format("%d:%02d", math.floor(fight.duration / 60), math.mod(math.floor(fight.duration), 60))) entry.tooltip = { fight.name, { "|cffffffff" .. (fight.kill and "Kill" or "Wipe"), "|cffaaaaaaended " .. (fight.ended or "") } } entry:Show() end end end list:SetHeight(count * 14 + 12) if ShaguDPS.classic then skin.Panel(list) else list:SetBackdrop(backdrop) list:SetBackdropColor(.2,.2,.2,1) list:SetBackdropBorderColor(.4,.4,.4,1) end list:Show() end frame.btnResize = CreateFrame("Frame", nil, frame) frame.btnResize:SetPoint("BOTTOMRIGHT", -3, 3) frame.btnResize:SetWidth(12) frame.btnResize:SetHeight(12) frame.btnResize:EnableMouse(1) frame.btnResize.tex = frame.btnResize:CreateTexture(nil, "BACKGROUND") frame.btnResize.tex:SetAllPoints() frame.btnResize.tex:SetTexture("Interface\\AddOns\\ShaguDPS" .. (tbc and "-tbc" or "") .. "\\img\\resize") frame.btnResize:SetFrameLevel(50) frame.btnResize:SetScript("OnMouseDown", function() if not this:GetParent().sizing and config.lock == 0 then this:GetParent().sizing = true this:GetParent():StartSizing() end end) frame.btnResize:SetScript("OnMouseUp", function() this:GetParent().sizing = nil this:GetParent():StopMovingOrSizing() this:GetParent():Refresh(true) end) frame.border = CreateFrame("Frame", "ShaguDPSBorder", frame) frame.border:ClearAllPoints() frame.border:SetPoint("TOPLEFT", frame, "TOPLEFT", -1,1) frame.border:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 1,-1) frame.border:SetFrameLevel(100) frame.bars = {} frame.values = {} frame.buttons = { frame.btnDamage, frame.btnHeal, frame.btnThreat, frame.btnDispels, frame.btnDone, frame.btnReceived, frame.btnOverall, frame.btnFights, frame.btnCurrent } table.insert(parser.callbacks.refresh, function() frame.needs_refresh = true end) if ShaguDPS.classic then window.ApplyClassic(frame) end return frame end -- classic theme: no boxes around the title buttons, gold text and icons, -- a silver rule under the title and the dropdown lists as tooltip panels window.ApplyClassic = function(frame) if frame.classic then return end frame.classic = true frame.title:SetTexture(0, 0, 0, 0) frame.titleRule = skin.Rule(frame) frame.titleRule:SetPoint("TOPLEFT", frame, "TOPLEFT", 6, -21) frame.titleRule:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -6, -21) frame.titleRule:SetFrameLevel(frame:GetFrameLevel() + 3) skin.IconButton(frame.btnAnnounce) skin.IconButton(frame.btnSettings) skin.IconButton(frame.btnReset) skin.IconButton(frame.btnWindow) skin.IconButton(frame.btnLock) skin.TextButton(frame.btnSegment, 10) skin.TextButton(frame.btnMode, 10) -- one tooltip panel behind each dropdown list local function MenuPanel(first, last) local panel = CreateFrame("Frame", nil, frame) panel:SetFrameStrata("HIGH") panel:SetFrameLevel(first:GetFrameLevel() > 1 and first:GetFrameLevel() - 1 or 1) panel:SetPoint("TOPLEFT", first, "TOPLEFT", -6, 6) panel:SetPoint("BOTTOMRIGHT", last, "BOTTOMRIGHT", 6, -6) skin.Panel(panel) panel:Hide() first:SetScript("OnShow", function() panel:Show() end) first:SetScript("OnHide", function() panel:Hide() end) return panel end frame.menuSegment = MenuPanel(frame.btnCurrent, frame.btnOverall) frame.menuView = MenuPanel(frame.btnDamage, frame.btnDispels) for name in pairs(menubuttons) do local button = frame["btn" .. name] button:SetFrameLevel(frame.menuView:GetFrameLevel() + 2) skin.MenuEntry(button) end end -- the theme is known once the saved variables are loaded window.ApplyTheme = function() if not ShaguDPS.classic then return end for i=1,10 do if window[i] then window.ApplyClassic(window[i]) end end end -- make sure to always create first window window[1] = window[1] or CreateWindow(1) -- update all available windows window.Refresh = function(force, report) for i=1,10 do if config[i] then window[i] = window[i] or CreateWindow(i) window[i]:Refresh(force, report) end end end window.Refresh(true)