Files
WeirdUtils/src/dpslog/WeirdDPSMate/DPSMate.lua
T
MarcelineVQ 86da3bcc03 dpslog: DPSMate CLEU adapter with per-combat A/B profiling
Adds DPSMate_CLEUAdapter.lua -- replaces DPSMate's string-parsing
CHAT_MSG_* system with structured COMBAT_LOG_EVENT data from our DLL.

Adapter maps CLEU subevents directly to DPSMate.DB API calls:
DamageDone, DamageTaken, EnemyDamage, Healing, HealingTaken,
DeathHistory, Kick, Dispels, BuildBuffs, CCBreaker, etc.
Eliminates all strfind pattern matching from the combat log path.

Toggle: /dpscleu (on/off/status)
Benchmark: /dpsbench -- always-on per-combat A/B profiling.
  Resets at combat start, reports at combat end, flips mode for next
  combat. Reports events, total ms, us/event, GC delta, and percentage
  comparison between CLEU and original modes.

Starts in CLEU mode by default.
2026-03-18 01:12:04 -07:00

970 lines
30 KiB
Lua

-- Global Variables
DPSMate = {}
DPSMate.VERSION = 130
DPSMate.LOCALE = GetLocale()
DPSMate.SYNCVERSION = DPSMate.VERSION..DPSMate.LOCALE
DPSMate.Parser = CreateFrame("Frame", nil, UIParent)
DPSMate.L = {}
DPSMate.DB = CreateFrame("Frame", nil, UIParent)
DPSMate.Options = CreateFrame("Frame", nil, UIParent)
DPSMate.Sync = CreateFrame("Frame", nil, UIParent)
DPSMate.Modules = {}
DPSMate.Events = {
--"CHAT_MSG_ADDON",
"PLAYER_AURAS_CHANGED",
-- Damage
"CHAT_MSG_COMBAT_SELF_HITS",
"CHAT_MSG_COMBAT_SELF_MISSES",
"CHAT_MSG_SPELL_SELF_DAMAGE",
"CHAT_MSG_COMBAT_PARTY_HITS",
"CHAT_MSG_SPELL_PARTY_DAMAGE",
"CHAT_MSG_COMBAT_PARTY_MISSES",
"CHAT_MSG_COMBAT_FRIENDLYPLAYER_HITS",
"CHAT_MSG_COMBAT_FRIENDLYPLAYER_MISSES",
"CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE",
-- Pet Damage
"CHAT_MSG_COMBAT_PET_HITS",
"CHAT_MSG_COMBAT_PET_MISSES",
--"CHAT_MSG_SPELL_PET_BUFF",
"CHAT_MSG_SPELL_PET_DAMAGE",
-- EDD (Enemy player) / DeathHistory
"CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS",
"CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES",
"CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE",
"CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE",
-- Damage taken (Also EDD) / DeathHistory
"CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS",
"CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES",
"CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE",
"CHAT_MSG_COMBAT_CREATURE_VS_PARTY_HITS",
"CHAT_MSG_COMBAT_CREATURE_VS_PARTY_MISSES",
"CHAT_MSG_SPELL_CREATURE_VS_PARTY_DAMAGE",
"CHAT_MSG_COMBAT_CREATURE_VS_CREATURE_HITS",
"CHAT_MSG_COMBAT_CREATURE_VS_CREATURE_MISSES",
"CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE",
"CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE",
"CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE",
"CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE",
"CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE",
-- Healing/Absorbs/Fail/DeathHistory/Dispels
"CHAT_MSG_SPELL_SELF_BUFF",
"CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS",
"CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF",
"CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS",
"CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF",
"CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS",
"CHAT_MSG_SPELL_PARTY_BUFF",
"CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS",
-- Absorbs/Auras
"CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF",
"CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS",
"CHAT_MSG_SPELL_BREAK_AURA",
"CHAT_MSG_SPELL_AURA_GONE_SELF",
"CHAT_MSG_SPELL_AURA_GONE_OTHER",
"CHAT_MSG_SPELL_AURA_GONE_PARTY",
-- Death/DeathHistory
"CHAT_MSG_COMBAT_FRIENDLY_DEATH",
"CHAT_MSG_COMBAT_HOSTILE_DEATH",
}
DPSMate.Registered = true
DPSMate.RegistredModules = {}
DPSMate.ModuleNames = {}
DPSMate.UserId = nil
DPSMate.AbilityId = nil
DPSMate.Key = 1
DPSMate.DelayMsg = {}
DPSMate.BabbleSpell = BabbleSpell
DPSMate.BabbleBoss = BabbleBoss
-- Knife was wrong - 11.06.2018
-- Local Variables
local _G = getglobal
local classcolor = {
rogue = {r=1.0, g=0.96, b=0.41},
priest = {r=1,g=1,b=1},
druid = {r=1,g=0.49,b=0.04},
warrior = {r=0.78,g=0.61,b=0.43},
warlock = {r=0.58,g=0.51,b=0.79},
mage = {r=0.41,g=0.8,b=0.94},
hunter = {r=0.67,g=0.83,b=0.45},
paladin = {r=0.96,g=0.55,b=0.73},
shaman = {r=0,g=0.44,b=0.87},
}
local t = {}
local tinsert = table.insert
local strgsub = string.gsub
local func = function(c) tinsert(t, c) end
local strformat = string.format
local strgfind = string.gfind
local pairs = pairs
local strlen = strlen
local strsub = strsub
local tonumber = tonumber
local getn = getn
local tconcat = table.concat
local SendChatMessage = function(prio, prefix, text, chattype, language, destination)
ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination)
end
local DPSSettings = {}
local DPSHist = {}
local fontRefreshAt = nil
local savedFontSettings = nil
local GT = GetTime
-- Begin functions
function DPSMate:OnLoad()
SLASH_DPSMate1 = "/dps"
SlashCmdList["DPSMate"] = function(msg) DPSMate:SlashCMDHandler(msg) end
DPSMate:UpdatePointer()
-- Snapshot font settings before skinning addons can overwrite them
savedFontSettings = {}
for k, w in pairs(DPSSettings["windows"]) do
savedFontSettings[k] = {
barfont = w["barfont"],
barfontsize = w["barfontsize"],
barfontflag = w["barfontflag"],
titlebarfont = w["titlebarfont"],
titlebarfontsize = w["titlebarfontsize"],
titlebarfontflag = w["titlebarfontflag"],
}
end
DPSMate:InitializeFrames()
DPSMate.Options:InitializeConfigMenu()
if DPSMate.L.UpdateFrameTexts then
DPSMate.L.UpdateFrameTexts()
end
-- Schedule a deferred font refresh so that font settings survive
-- skinning addons (e.g. pfUI) that override fonts after load.
fontRefreshAt = GT() + 1
end
function DPSMate:UpdatePointer()
DPSSettings = DPSMateSettings
DPSHist = DPSMateHistory
end
function DPSMate:SlashCMDHandler(msg)
if (msg) then
local cmd = msg
if cmd == "lock" then
DPSMate.Options:Lock()
elseif cmd == "unlock" then
DPSMate.Options:Unlock()
elseif cmd == "config" then
DPSMate_ConfigMenu:Show()
elseif cmd == "reset" then
DPSMate:SendMessage(DPSMate.L["resetconfirm"])
elseif cmd == "resetconfirm" then
DPSMateUser = {}
DPSMateAbility = {}
DPSMate.DB.abilitylen = 1
DPSMate.DB.userlen = 1
DPSMate.UserId = {}
DPSMate.Options:PopUpAccept(true, true)
DPSMate:SendMessage(DPSMate.L["resetdone"])
elseif cmd == "showAll" then
for _, val in DPSSettings["windows"] do DPSMate.Options:Show(getglobal("DPSMate_"..val["name"])) end
elseif cmd == "hideAll" then
for _, val in DPSSettings["windows"] do DPSMate.Options:Hide(getglobal("DPSMate_"..val["name"])) end
elseif strsub(cmd, 1, 4) == "show" then
local frame = _G("DPSMate_"..strsub(cmd, 6))
if frame then
DPSMate.Options:Show(frame)
else
DPSMate:SendMessage(DPSMate.L["framesavailable"])
for _, val in pairs(DPSSettings["windows"]) do
DPSMate:SendMessage("|c3ffddd80- "..val["name"].."|r")
end
end
elseif strsub(cmd, 1, 4) == "hide" then
local frame = _G("DPSMate_"..strsub(cmd, 6))
if frame then
DPSMate.Options:Hide(frame)
else
DPSMate:SendMessage(DPSMate.L["framesavailable"])
for _, val in pairs(DPSSettings["windows"]) do
DPSMate:SendMessage("|c3ffddd80- "..val["name"].."|r")
end
end
else
DPSMate:SendMessage(DPSMate.L["slashabout"])
DPSMate:SendMessage(DPSMate.L["slashusage"])
DPSMate:SendMessage(DPSMate.L["slashlock"])
DPSMate:SendMessage(DPSMate.L["slashunlock"])
DPSMate:SendMessage(DPSMate.L["slashshowAll"])
DPSMate:SendMessage(DPSMate.L["slashhideAll"])
DPSMate:SendMessage(DPSMate.L["slashshow"])
DPSMate:SendMessage(DPSMate.L["slashhide"])
DPSMate:SendMessage(DPSMate.L["slashconfig"])
DPSMate:SendMessage(DPSMate.L["slashreset"])
end
end
end
function DPSMate:InitializeFrames()
if not DPSSettings["windows"][1] then return end
for k, val in pairs(DPSSettings["windows"]) do
if not _G("DPSMate_"..val["name"]) then
local f=CreateFrame("Frame", "DPSMate_"..val["name"], UIParent, "DPSMate_Statusframe")
f.Key=k
end
local frame = _G("DPSMate_"..val["name"])
frame.fborder = _G("DPSMate_"..val["name"].."_Border")
frame:SetToplevel(true)
_G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total_Name"):SetText(self.L["total"])
if (val["position"] and val["position"][1]) then
frame:ClearAllPoints()
frame:SetPoint(val["position"][1], UIParent, val["position"][1], val["position"][2], val["position"][3])
end
if (val["savsize"] and val["savsize"][1]) then
frame:SetWidth(val["savsize"][1])
frame:SetHeight(val["savsize"][2])
end
DPSMate.Options:ToggleDrewDrop(1, DPSMate.DB:GetOptionsTrue(1, k), frame)
DPSMate.Options:ToggleDrewDrop(2, DPSMate.DB:GetOptionsTrue(2, k), frame)
frame.fborder:SetAlpha(val["borderopacity"] or 0)
frame.fborder:SetFrameStrata(DPSMate.Options.stratas[val["borderstrata"] or 1])
frame.fborder:SetBackdrop({
bgFile = "",
edgeFile = DPSMate.Options.bordertextures[val["bordertexture"] or "UI-Tooltip-Border"], tile = true, tileSize = 12, edgeSize = 10,
insets = { left = 5, right = 5, top = 3, bottom = 1 }
})
frame.fborder:SetBackdropBorderColor(val["contentbordercolor"][1], val["contentbordercolor"][2], val["contentbordercolor"][3])
local head = _G("DPSMate_"..val["name"].."_Head")
head.font = _G("DPSMate_"..val["name"].."_Head_Font")
head.bg = _G("DPSMate_"..val["name"].."_Head_Background")
head.sync = _G("DPSMate_"..val["name"].."_Head_Sync")
if DPSSettings["sync"] then
head.sync:GetNormalTexture():SetVertexColor(0.67,0.83,0.45,1)
else
head.sync:GetNormalTexture():SetVertexColor(1,0,0,1)
end
if DPSSettings["lock"] then
_G("DPSMate_"..val["name"].."_Resize"):Hide()
end
if not val["titlebar"] then
head:Hide()
end
frame:SetAlpha(val["opacity"])
head.font:SetTextColor(val["titlebarfontcolor"][1],val["titlebarfontcolor"][2],val["titlebarfontcolor"][3])
head.bg:SetTexture(DPSMate.Options.statusbars[val["titlebartexture"]])
head.bg:SetVertexColor(val["titlebarbgcolor"][1], val["titlebarbgcolor"][2], val["titlebarbgcolor"][3])
head.bg:SetAlpha(val["titlebaropacity"] or 1)
head.font:SetFont(DPSMate.Options.fonts[val["titlebarfont"]], val["titlebarfontsize"], DPSMate.Options.fontflags[val["titlebarfontflag"]])
head:SetHeight(val["titlebarheight"])
_G("DPSMate_"..val["name"].."_ScrollFrame_Background"):SetTexture(DPSMate.Options.bgtexture[val["contentbgtexture"]])
_G("DPSMate_"..val["name"].."_ScrollFrame_Background"):SetVertexColor(val["contentbgcolor"][1], val["contentbgcolor"][2], val["contentbgcolor"][3])
_G("DPSMate_"..val["name"].."_ScrollFrame_Background"):SetAlpha(val["bgopacity"] or 1)
frame:SetScale(val["scale"])
_G("DPSMate_"..val["name"].."_Head_Enable"):SetChecked(DPSSettings["enable"])
-- Styles // Bars
local child = _G("DPSMate_"..val["name"].."_ScrollFrame_Child")
_G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total"):SetPoint("TOPLEFT", child, "TOPLEFT")
_G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total"):SetPoint("TOPRIGHT", child, "TOPRIGHT")
if DPSSettings["showtotals"] then
_G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total"):SetHeight(val["barheight"])
else
_G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total"):SetHeight(0.00001)
end
_G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total"):SetStatusBarTexture(DPSMate.Options.statusbars[val["bartexture"]])
_G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total"):SetStatusBarColor(1,1,1,val["totopacity"] or 1)
_G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total_BG"):SetTexture(DPSMate.Options.statusbars[val["bartexture"]])
_G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total_BG"):SetAlpha(val["totopacity"] or 1)
_G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total_Name"):SetFont(DPSMate.Options.fonts[val["barfont"]], val["barfontsize"], DPSMate.Options.fontflags[val["barfontflag"]])
_G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total_Value"):SetFont(DPSMate.Options.fonts[val["barfont"]], val["barfontsize"], DPSMate.Options.fontflags[val["barfontflag"]])
for i=1, 40 do
local bar = _G("DPSMate_"..val["name"].."_ScrollFrame_Child_StatusBar"..i)
bar.name = _G("DPSMate_"..val["name"].."_ScrollFrame_Child_StatusBar"..i.."_Name")
bar.value = _G("DPSMate_"..val["name"].."_ScrollFrame_Child_StatusBar"..i.."_Value")
bar.icon = _G("DPSMate_"..val["name"].."_ScrollFrame_Child_StatusBar"..i.."_Icon")
bar.bg = _G("DPSMate_"..val["name"].."_ScrollFrame_Child_StatusBar"..i.."_BG")
-- Postition
bar:SetPoint("TOPLEFT", child, "TOPLEFT")
bar:SetPoint("TOPRIGHT", child, "TOPRIGHT")
if i>1 then
bar:SetPoint("TOPLEFT", _G("DPSMate_"..val["name"].."_ScrollFrame_Child_StatusBar"..(i-1)), "BOTTOMLEFT", 0, -1*val["barspacing"])
else
if DPSSettings["showtotals"] then
bar:SetPoint("TOPLEFT", _G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total"), "BOTTOMLEFT", 0, -1*val["barspacing"])
else
bar:SetPoint("TOPLEFT", _G("DPSMate_"..val["name"].."_ScrollFrame_Child_Total"), "BOTTOMLEFT", 0, -1)
end
end
if val["classicons"] then
bar.name:ClearAllPoints()
bar.name:SetPoint("TOPLEFT", bar, "TOPLEFT", val["barheight"], 0)
bar.name:SetPoint("BOTTOMRIGHT", bar, "BOTTOMRIGHT")
bar.icon:SetWidth(val["barheight"])
bar.icon:SetHeight(val["barheight"])
bar.icon:Show()
end
-- Styles
bar.name:SetFont(DPSMate.Options.fonts[val["barfont"]], val["barfontsize"], DPSMate.Options.fontflags[val["barfontflag"]])
bar.name:SetTextColor(val["barfontcolor"][1],val["barfontcolor"][2],val["barfontcolor"][3])
bar.value:SetFont(DPSMate.Options.fonts[val["barfont"]], val["barfontsize"], DPSMate.Options.fontflags[val["barfontflag"]])
bar.value:SetTextColor(val["barfontcolor"][1],val["barfontcolor"][2],val["barfontcolor"][3])
bar:SetStatusBarTexture(DPSMate.Options.statusbars[val["bartexture"]])
bar.bg:SetTexture(DPSMate.Options.statusbars[val["bartexture"]])
bar:SetHeight(val["barheight"])
if val["barbg"] then
bar.bg:SetVertexColor(val["bgbarcolor"][1],val["bgbarcolor"][2],val["bgbarcolor"][3], 0)
else
bar.bg:SetVertexColor(val["bgbarcolor"][1],val["bgbarcolor"][2],val["bgbarcolor"][3], 0.5)
end
end
DPSMate.Options:SelectRealtime(frame, val["realtime"])
if (val["hidden"] == true) then
frame:Hide()
end
end
DPSMate.Options:ToggleTitleBarButtonState()
DPSMate.Options:HideWhenSolo()
if not DPSSettings["enable"] then
self:Disable()
else
self:Enable()
end
-- Report delay button
DPSMate_Report_Delay:SetChecked(DPSSettings["reportdelay"])
DPSMate_MiniMap:SetToplevel(true)
DPSMate_PopUp:SetToplevel(true)
DPSMate_Vote:SetToplevel(true)
DPSMate_Logout:SetToplevel(true)
DPSMate_Report:SetToplevel(true)
DPSMate_ConfigMenu:SetToplevel(true)
end
function DPSMate:ProbZero(val)
if (val==0) then
return 1;
end
return val;
end
function DPSMate:TMax(t)
local max = 0
for _,val in pairs(t) do
if val>max then
max=val
end
end
return max
end
function DPSMate:TableLength(t)
local count = 0
if (t) then
count = getn(t)
if count<=1 then
count = 0
for _,_ in pairs(t) do
count = count + 1
end
end
end
return count
end
function DPSMate:TContains(t, value)
if (t) then
for cat, val in pairs(t) do
if val == value or cat==value then
return true
end
end
end
return false
end
function DPSMate:GetKeyByVal(t, value)
for cat, val in pairs(t) do
if val == value then
return cat
end
end
end
function DPSMate:GetKeyByValInTT(t, x, y)
for cat, val in pairs(t) do
if (type(val) == "table") then
if (x==val[y]) then
return cat
end
end
end
end
function DPSMate:InvertTable(t)
local s={}
for cat, val in pairs(t) do
s[val]=cat
end
return s
end
function DPSMate:CopyTable(t)
local s={}
for cat, val in pairs(t) do
if type(val) == "table" then
s[cat] = self:CopyTable(val)
else
s[cat] = val
end
end
return s
end
function DPSMate:CopyTableStripInstant(t, depth)
local s={}
depth = depth or 0
for cat, val in pairs(t) do
if depth >= 2 and cat == "i" then
-- Skip ["i"] instant-damage dictionaries at ability level
elseif type(val) == "table" then
s[cat] = self:CopyTableStripInstant(val, depth + 1)
else
s[cat] = val
end
end
return s
end
function DPSMate:PruneStaleUsers()
-- Collect all user IDs referenced in active metric data and history
local activeIds = {}
local metrics = {DPSMateDamageDone, DPSMateDamageTaken, DPSMateEDD, DPSMateEDT, DPSMateTHealing, DPSMateEHealing, DPSMateOverhealing, DPSMateHealingTaken, DPSMateEHealingTaken, DPSMateOverhealingTaken, DPSMateAbsorbs, DPSMateDispels, DPSMateDeaths, DPSMateInterrupts, DPSMateAurasGained, DPSMateThreat, DPSMateFails, DPSMateCCBreaker}
-- Check active data (mode [1] and [2])
for _, metric in pairs(metrics) do
if metric then
for mode = 1, 2 do
if metric[mode] then
for uid, _ in pairs(metric[mode]) do
activeIds[uid] = true
end
end
end
end
end
-- Check history segments
for cat, segments in pairs(DPSMateHistory) do
if cat ~= "names" and type(segments) == "table" then
for _, segment in pairs(segments) do
if type(segment) == "table" then
for uid, _ in pairs(segment) do
activeIds[uid] = true
end
end
end
end
end
-- Remove users whose IDs aren't referenced anywhere
local removed = 0
for name, data in pairs(DPSMateUser) do
if data[1] and not activeIds[data[1]] then
DPSMateUser[name] = nil
removed = removed + 1
end
end
if removed > 0 then
self.UserId = nil
end
end
-- Strip ["i"] sub-tables from all modes of all metrics that use them.
-- Covers damage/healing/threat (per-second time buckets) as well as dispels,
-- interrupts, and absorbs (event log arrays stored under ["i"]).
-- Called on login and on logout to keep SavedVariables compact.
--
-- A recursive walk is used: wherever ["i"] is a table it is cleared to {}.
-- Numbers stored under ["i"] are left untouched.
-- Using {} rather than nil keeps combat event writers safe.
local function stripInstantRecursive(t)
for k, v in pairs(t) do
if k == "i" and type(v) == "table" then
t[k] = {}
elseif k ~= "i" and type(v) == "table" then
stripInstantRecursive(v)
end
end
end
function DPSMate:StripInstantFromMode1()
local metrics = {DPSMateDamageDone, DPSMateDamageTaken, DPSMateEDD, DPSMateEDT,
DPSMateTHealing, DPSMateEHealing, DPSMateOverhealing, DPSMateHealingTaken,
DPSMateEHealingTaken, DPSMateOverhealingTaken, DPSMateThreat,
DPSMateDispels, DPSMateInterrupts, DPSMateAbsorbs}
for _, metric in pairs(metrics) do
if metric then
if metric[1] then stripInstantRecursive(metric[1]) end
if metric[2] then stripInstantRecursive(metric[2]) end
end
end
end
-- Collapse AurasGained timestamp arrays in Mode [1] and [2] into compact uptime totals.
-- path[1] (gain times) and path[2] (loss times) grow unboundedly with each buff
-- application. The pre-computed total is stored in path[7] so EvalTable can still
-- display accurate uptime percentages across sessions.
function DPSMate:CollapseAuraTimestamps()
if not DPSMateAurasGained then return end
for mode = 1, 2 do
local modeData = DPSMateAurasGained[mode]
if modeData then
for uid, abilities in pairs(modeData) do
for abilityId, path in pairs(abilities) do
if type(path) == "table" and type(path[1]) == "table" then
local total = path[7] or 0
for i, gainTime in pairs(path[1]) do
if path[2] and path[2][i] then
total = total + (path[2][i] - gainTime)
end
end
path[7] = total
path[1] = {}
path[2] = {}
end
end
end
end
end
end
function DPSMate:GetUserById(id)
if not self.UserId or not self.UserId[id] then
self.UserId = {}
for cat, val in DPSMateUser do
self.UserId[val[1]] = cat
end
end
return self.UserId[id]
end
function DPSMate:GetPetsByOwner(arr)
local lookup = {}
for cat, _ in pairs(arr) do
local name = self:GetUserById(cat)
if name and DPSMateUser[name] and DPSMateUser[name][4] and DPSMateUser[name][6] then
local oid = DPSMateUser[name][6]
if not lookup[oid] then
lookup[oid] = {}
end
table.insert(lookup[oid], cat)
end
end
return lookup
end
function DPSMate:GetAbilityById(id)
if not self.AbilityId or not self.AbilityId[id] then
self.AbilityId = {}
for cat, val in DPSMateAbility do
self.AbilityId[val[1]] = cat
end
end
return self.AbilityId[id]
end
function DPSMate:GetMaxValue(arr, key)
local max = 0
for _, val in arr do
if val[key]>max then
max=val[key]
end
end
return max
end
function DPSMate:GetMinValue(arr, key)
local min
for _, val in arr do
if not min or val[key]<min then
min = val[key]
end
end
return min or 0
end
function DPSMate:ScaleDown(arr, start)
local t = {}
for cat, val in arr do
t[cat] = {(val[1]-start+1), val[2]}
end
return t
end
function DPSMate:RefreshFonts()
if not DPSSettings["windows"][1] then return end
if not savedFontSettings then return end
local fonts = self.Options.fonts
local flags = self.Options.fontflags
for k, c in pairs(DPSSettings["windows"]) do
local saved = savedFontSettings[k]
if saved then
-- Restore user's saved font settings into the live table
c["barfont"] = saved.barfont
c["barfontsize"] = saved.barfontsize
c["barfontflag"] = saved.barfontflag
c["titlebarfont"] = saved.titlebarfont
c["titlebarfontsize"] = saved.titlebarfontsize
c["titlebarfontflag"] = saved.titlebarfontflag
end
local barFont = fonts[c["barfont"]]
local barSize = c["barfontsize"]
local barFlag = flags[c["barfontflag"]]
local titleFont = fonts[c["titlebarfont"]]
local titleSize = c["titlebarfontsize"]
local titleFlag = flags[c["titlebarfontflag"]]
local name = c["name"]
_G("DPSMate_"..name.."_Head_Font"):SetFont(titleFont, titleSize, titleFlag)
_G("DPSMate_"..name.."_ScrollFrame_Child_Total_Name"):SetFont(barFont, barSize, barFlag)
_G("DPSMate_"..name.."_ScrollFrame_Child_Total_Value"):SetFont(barFont, barSize, barFlag)
for i=1, 40 do
_G("DPSMate_"..name.."_ScrollFrame_Child_StatusBar"..i.."_Name"):SetFont(barFont, barSize, barFlag)
_G("DPSMate_"..name.."_ScrollFrame_Child_StatusBar"..i.."_Value"):SetFont(barFont, barSize, barFlag)
end
end
savedFontSettings = nil
end
local framePointerCache = {}
function DPSMate:InvalidateFrameCache()
framePointerCache = {}
end
function DPSMate:SetStatusBarValue()
if not DPSSettings["windows"][1] or self.Options.TestMode then return end
-- Deferred font refresh: wait ~1s after load for skinning addons to finish,
-- then re-apply saved font settings once.
if fontRefreshAt and GT() >= fontRefreshAt then
fontRefreshAt = nil
self:RefreshFonts()
end
local arr, cbt, ecbt, user, val, perc, strt, statusbar, r, g, b, img, len
for k,c in pairs(DPSSettings.windows) do
arr, cbt, ecbt = self:GetMode(k)
user, val, perc, strt = self:GetSettingValues(arr,cbt,k,ecbt)
-- Caching the global lookups
if not framePointerCache[k] then
framePointerCache[k] = {}
framePointerCache[k][1] = _G("DPSMate_"..c["name"].."_ScrollFrame_Child_Total_Value")
framePointerCache[k][2] = _G("DPSMate_"..c["name"].."_Head_Font")
framePointerCache[k][3] = _G("DPSMate_"..c["name"].."_ScrollFrame_Child")
framePointerCache[k][4] = _G("DPSMate_"..c["name"].."_ScrollFrame_Child_Total")
for i=1, 40 do
framePointerCache[k][4+i] = {}
framePointerCache[k][4+i][1] = _G("DPSMate_"..c["name"].."_ScrollFrame_Child_StatusBar"..i)
framePointerCache[k][4+i][2] = _G("DPSMate_"..c["name"].."_ScrollFrame_Child_StatusBar"..i.."_Name")
framePointerCache[k][4+i][3] = _G("DPSMate_"..c["name"].."_ScrollFrame_Child_StatusBar"..i.."_Value")
framePointerCache[k][4+i][4] = _G("DPSMate_"..c["name"].."_ScrollFrame_Child_StatusBar"..i.."_Icon")
end
end
local FPC = framePointerCache[k];
if DPSSettings["showtotals"] then
FPC[1]:SetText(strt[1]..strt[2])
end
if not c["cbtdisplay"] then
FPC[2]:SetText(self.Options.Options[1]["args"][c["CurMode"]].name.." ["..self.Options:FormatTime(cbt).."]")
end
len = 0
for i=1, 40 do
statusbar = FPC[4+i][1]
if user[i] then
r,g,b,img = self:GetClassColor(user[i])
statusbar:SetStatusBarColor(r,g,b, 1)
local displayName = user[i]
if not DPSMateSettings["mergepets"] and DPSMateUser[user[i]] and DPSMateUser[user[i]][4] then
local ownerID = DPSMateUser[user[i]][6]
if ownerID then
local ownerName = self:GetUserById(ownerID)
if ownerName then displayName = user[i].." ("..ownerName..")" end
end
end
if c["ranks"] then
FPC[4+i][2]:SetText(i..". "..displayName)
else
FPC[4+i][2]:SetText(displayName)
end
FPC[4+i][3]:SetText(val[i])
FPC[4+i][4]:SetTexture("Interface\\AddOns\\DPSMate\\images\\class\\"..img)
statusbar:SetValue(perc[i])
statusbar.user = user[i]
statusbar:Show()
len = len + 1
else
statusbar:Hide()
end
end
FPC[3]:SetHeight((len+1)*(c["barheight"]+c["barspacing"]))
FPC[4]:Show()
if len == 0 then
FPC[4]:Hide()
end
end
end
function DPSMate:strrev(str)
local res, len = {}, strlen(str)
for i=1, len do
res[i] = strsub(str, len-i+1, len-i+1)
end
return tconcat(res);
end
function DPSMate:Commas(n,k)
if DPSSettings["windows"][k]["numberformat"] == 3 then
n = strformat("%.0f", n)
for left, num, right in strgfind(n, '([^%d]*%d)(%d+)') do
return left and left..self:strrev(strgsub(self:strrev(num), '(%d%d%d)','%1,'))
end
end
return n;
end
function DPSMate:FormatNumbers(dmg,total,sort,k)
local oldd, oldt, olds = dmg, total, sort
if DPSSettings["windows"][k]["numberformat"] == 2 then
if dmg>10000 then
dmg = strformat("%.0f", (dmg/1000))
end
if total>10000 then
total = strformat("%.0f", (total/1000))
end
if sort>10000 then
sort = strformat("%.0f", (sort/1000))
end
elseif DPSSettings["windows"][k]["numberformat"] == 4 then
if dmg>10000 then
dmg = strformat("%.1f", (dmg/1000))
end
if total>10000 then
total = strformat("%.1f", (total/1000))
end
if sort>10000 then
sort = strformat("%.1f", (sort/1000))
end
end
return dmg, total, sort, oldd, oldt, olds
end
function DPSMate:ApplyFilter(key, name)
if not name or not DPSMateUser[name] then return false end
if not key then return true end
local class = DPSMateUser[name][2] or "warrior"
local path = DPSSettings["windows"][key]
if path["grouponly"] then
if not DPSMate.Parser.TargetParty[name] and next(DPSMate.Parser.TargetParty) ~= nil then
return false
end
end
if path["filterpeople"] and path["filterpeople"] ~= "" then
-- Certain people
t = {}
strgsub(path["filterpeople"]..",", "(.-),", func)
for cat, val in pairs(t) do
if name == val then
return true
end
end
end
-- classes
for cat, val in pairs(path["filterclasses"]) do
if not val then
if cat == class then
return false
end
end
end
return true
end
function DPSMate:GetSettingValues(arr, cbt, k, ecbt)
k = k or 1
return self.RegistredModules[DPSSettings["windows"][k]["CurMode"]]:GetSettingValues(arr, cbt, k, ecbt)
end
function DPSMate:EvalTable(k)
k = k or 1
return self.RegistredModules[DPSSettings["windows"][k]["CurMode"]]:EvalTable(DPSMateUser[UnitName("player")], k)
end
function DPSMate:GetClassColor(class)
if not class then
class = "warrior"
end
if DPSMateUser[class] then
class = DPSMateUser[class][2] or "warrior"
end
if classcolor[class] then
return classcolor[class].r, classcolor[class].g, classcolor[class].b, class
end
return classcolor["warrior"].r, classcolor["warrior"].g, classcolor["warrior"].b, "warrior"
end
function DPSMate:GetMode(k)
k = k or 1
local Handler = DPSMate.RegistredModules[DPSSettings["windows"][k]["CurMode"]]
local opts = DPSSettings["windows"][k]["options"][2]
-- Check in deterministic order: total first, then currentfight, then segments
if opts["total"] then
return Handler.DB[1], DPSMateCombatTime["total"], DPSMateCombatTime["effective"][1]
end
if opts["currentfight"] then
return Handler.DB[2], DPSMateCombatTime["current"], DPSMateCombatTime["effective"][2]
end
local num
for cat, val in pairs(opts) do
if val and strfind(cat, "segment") then
num = tonumber(strsub(cat, 8))
if (Handler.Hist) and DPSHist[Handler.Hist] and self:TableLength(DPSHist[Handler.Hist]) >= num then
return DPSHist[Handler.Hist][num], DPSMateCombatTime["segments"][num][1], DPSMateCombatTime["segments"][num][2]
else
return {}, 0, 0
end
end
end
-- Fallback: default to total
return Handler.DB[1], DPSMateCombatTime["total"], DPSMateCombatTime["effective"][1]
end
function DPSMate:GetModeByArr(arr, k, Hist)
local opts = DPSSettings["windows"][k]["options"][2]
-- Check in deterministic order: total first, then currentfight, then segments
if opts["total"] then
return arr[1], DPSMateCombatTime["total"], DPSMateCombatTime["effective"][1]
end
if opts["currentfight"] then
return arr[2], DPSMateCombatTime["current"], DPSMateCombatTime["effective"][2]
end
local num
for cat, val in pairs(opts) do
if val and strfind(cat, "segment") then
num = tonumber(strsub(cat, 8))
if (Hist or arr.Hist) and DPSHist[Hist or arr.Hist] and self:TableLength(DPSHist[Hist or arr.Hist]) >= num then
return DPSHist[Hist or arr.Hist][num], DPSMateCombatTime["segments"][num][1], DPSMateCombatTime["segments"][num][2]
else
return {}, 0, 0
end
end
end
-- Fallback: default to total
return arr[1], DPSMateCombatTime["total"], DPSMateCombatTime["effective"][1]
end
function DPSMate:GetModeName(k)
k = k or 1
local opts = DPSSettings["windows"][k]["options"][2]
if opts["total"] then return "Total" end
if opts["currentfight"] then return "Current fight" end
local num
for cat, val in pairs(opts) do
if val and strfind(cat, "segment") then
num = tonumber(strsub(cat, 8))
return DPSHist["names"][num]
end
end
return "Total"
end
function DPSMate:Disable()
if self.Registered then
self.Sync:UnregisterEvent("CHAT_MSG_ADDON")
for _, event in pairs(self.Events) do
self.Parser:UnregisterEvent(event)
end
self.Registered = false
end
end
function DPSMate:Enable()
if not self.Registered then
self.Sync:RegisterEvent("CHAT_MSG_ADDON")
for _, val in pairs(self.RegistredModules) do
if val.Events then
for _, event in pairs(val.Events) do
self.Parser:RegisterEvent(event)
end
end
end
self.Registered = true
end
end
function DPSMate:Broadcast(type, who, what, with, value, failtype)
if DPSSettings["broadcasting"] then
if IsRaidLeader() or IsRaidOfficer() then
local ch = "RAID"
if DPSSettings["bcrw"] then
ch = "RAID_WARNING"
end
if DPSSettings["bccd"] and type == 1 then
SendChatMessage("BULK", "DPSMateChat", self.L["bccdo"](who, what), ch, nil, nil)
return
elseif DPSSettings["bccd"] and type == 6 then
SendChatMessage("BULK", "DPSMateChat", self.L["bccdt"](who, what), ch, nil, nil)
return
elseif DPSSettings["bcress"] and type == 2 then
SendChatMessage("BULK", "DPSMateChat", self.L["bcress"](who, what), ch, nil, nil)
return
elseif DPSSettings["bckb"] and type == 4 then
SendChatMessage("BULK", "DPSMateChat", self.L["bckb"](who, what, with, value), ch, nil, nil)
return
elseif DPSSettings["bcfail"] and type == 3 then
if failtype == 1 then
SendChatMessage("BULK", "DPSMateChat", self.L["bcfailo"](what, who, value, with), ch, nil, nil)
elseif failtype == 3 then
SendChatMessage("BULK", "DPSMateChat", self.L["bcfailt"](who, with), ch, nil, nil)
else
SendChatMessage("BULK", "DPSMateChat", self.L["bcfailth"](who, value, with, what), ch, nil, nil)
end
return
end
end
end
end
function DPSMate:SendMessage(msg)
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF8080"..self.L["name"].."|r: "..msg)
end
function DPSMate:Register(prefix, table, name)
self.ModuleNames[name] = prefix
self.RegistredModules[prefix] = table
end