更新发送到功能
更新仇恨计算方式 还在开发 更新其他细节
This commit is contained in:
132
BarDisplay.lua
Normal file
132
BarDisplay.lua
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local BarDisplay = {}
|
||||||
|
NanamiDPS.BarDisplay = BarDisplay
|
||||||
|
|
||||||
|
local BAR_POOL = {}
|
||||||
|
|
||||||
|
function BarDisplay:CreateBar(parent, index)
|
||||||
|
local cfg = NanamiDPS.config or {}
|
||||||
|
local barHeight = cfg.barHeight or 16
|
||||||
|
local barSpacing = cfg.barSpacing or 1
|
||||||
|
local fontSize = cfg.fontSize or 10
|
||||||
|
local A = SFrames.ActiveTheme
|
||||||
|
|
||||||
|
local bar = CreateFrame("StatusBar", "NanamiDPSBar" .. parent:GetID() .. "_" .. index, parent)
|
||||||
|
bar:SetStatusBarTexture(SFrames:GetTexture())
|
||||||
|
bar:SetHeight(barHeight)
|
||||||
|
bar:SetMinMaxValues(0, 1)
|
||||||
|
bar:SetValue(0)
|
||||||
|
bar:SetFrameLevel(parent:GetFrameLevel() + 3)
|
||||||
|
|
||||||
|
bar.bg = bar:CreateTexture(nil, "BACKGROUND")
|
||||||
|
bar.bg:SetTexture(SFrames:GetTexture())
|
||||||
|
bar.bg:SetAllPoints(bar)
|
||||||
|
if A and A.barBg then
|
||||||
|
bar.bg:SetVertexColor(A.barBg[1], A.barBg[2], A.barBg[3], A.barBg[4] or 0.6)
|
||||||
|
else
|
||||||
|
bar.bg:SetVertexColor(0.15, 0.15, 0.15, 0.6)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfg.showClassIcons and SFrames.CreateClassIcon then
|
||||||
|
bar.classIcon = SFrames:CreateClassIcon(bar, barHeight - 2)
|
||||||
|
bar.classIcon.overlay:SetPoint("LEFT", bar, "LEFT", 2, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
local textOffset = (cfg.showClassIcons and barHeight + 2) or 4
|
||||||
|
|
||||||
|
bar.textLeft = SFrames:CreateFontString(bar, fontSize, "LEFT")
|
||||||
|
bar.textLeft:SetPoint("LEFT", bar, "LEFT", textOffset, 0)
|
||||||
|
bar.textLeft:SetPoint("RIGHT", bar, "RIGHT", -130, 0)
|
||||||
|
|
||||||
|
bar.textRight = SFrames:CreateFontString(bar, fontSize, "RIGHT")
|
||||||
|
bar.textRight:SetPoint("RIGHT", bar, "RIGHT", -4, 0)
|
||||||
|
bar.textRight:SetWidth(126)
|
||||||
|
|
||||||
|
bar:EnableMouse(true)
|
||||||
|
bar:SetScript("OnEnter", function()
|
||||||
|
if this.onEnter then this.onEnter(this) end
|
||||||
|
end)
|
||||||
|
bar:SetScript("OnLeave", function()
|
||||||
|
if this.onLeave then this.onLeave(this) end
|
||||||
|
end)
|
||||||
|
bar:SetScript("OnMouseUp", function()
|
||||||
|
if this.onClick then this.onClick(this, arg1) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
bar.index = index
|
||||||
|
return bar
|
||||||
|
end
|
||||||
|
|
||||||
|
function BarDisplay:LayoutBars(window)
|
||||||
|
local cfg = NanamiDPS.config or {}
|
||||||
|
local barHeight = cfg.barHeight or 16
|
||||||
|
local barSpacing = cfg.barSpacing or 1
|
||||||
|
local headerHeight = 22
|
||||||
|
|
||||||
|
for i, bar in ipairs(window.bars) do
|
||||||
|
bar:ClearAllPoints()
|
||||||
|
bar:SetPoint("TOPLEFT", window.content, "TOPLEFT", 1, -((i - 1) * (barHeight + barSpacing)))
|
||||||
|
bar:SetPoint("TOPRIGHT", window.content, "TOPRIGHT", -1, -((i - 1) * (barHeight + barSpacing)))
|
||||||
|
bar:SetHeight(barHeight)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BarDisplay:UpdateBar(bar, data, maxVal, rank)
|
||||||
|
if not data then
|
||||||
|
bar:Hide()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
bar:Show()
|
||||||
|
bar:SetMinMaxValues(0, maxVal > 0 and maxVal or 1)
|
||||||
|
bar:SetValue(data.value or 0)
|
||||||
|
|
||||||
|
local r, g, b = data.r or 0.6, data.g or 0.6, data.b or 0.6
|
||||||
|
bar:SetStatusBarColor(r, g, b, 1)
|
||||||
|
bar.bg:SetVertexColor(r * 0.25, g * 0.25, b * 0.25, 0.6)
|
||||||
|
|
||||||
|
local nameStr = rank .. ". " .. (data.name or L["Unknown"])
|
||||||
|
bar.textLeft:SetText(nameStr)
|
||||||
|
|
||||||
|
local valStr
|
||||||
|
if data.valueText then
|
||||||
|
valStr = data.valueText
|
||||||
|
else
|
||||||
|
valStr = NanamiDPS.formatNumber(data.value or 0)
|
||||||
|
if data.percent then
|
||||||
|
valStr = valStr .. " (" .. NanamiDPS.round(data.percent, 1) .. "%)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
bar.textRight:SetText(valStr)
|
||||||
|
|
||||||
|
if bar.classIcon and data.class and NanamiDPS.validClasses[data.class] then
|
||||||
|
SFrames:SetClassIcon(bar.classIcon, data.class)
|
||||||
|
elseif bar.classIcon then
|
||||||
|
bar.classIcon:Hide()
|
||||||
|
if bar.classIcon.overlay then bar.classIcon.overlay:Hide() end
|
||||||
|
end
|
||||||
|
|
||||||
|
bar.barData = data
|
||||||
|
end
|
||||||
|
|
||||||
|
function BarDisplay:SetBarCallbacks(bar, onEnter, onLeave, onClick)
|
||||||
|
bar.onEnter = onEnter
|
||||||
|
bar.onLeave = onLeave
|
||||||
|
bar.onClick = onClick
|
||||||
|
end
|
||||||
|
|
||||||
|
function BarDisplay:GetMaxBars(window)
|
||||||
|
local cfg = NanamiDPS.config or {}
|
||||||
|
local barHeight = cfg.barHeight or 16
|
||||||
|
local barSpacing = cfg.barSpacing or 1
|
||||||
|
local contentHeight = 200
|
||||||
|
if window.content then
|
||||||
|
local h = window.content:GetHeight()
|
||||||
|
if h and h > 10 then contentHeight = h end
|
||||||
|
end
|
||||||
|
local maxBars = math.floor(contentHeight / (barHeight + barSpacing))
|
||||||
|
return math.max(maxBars, 1)
|
||||||
|
end
|
||||||
78
Core.lua
Normal file
78
Core.lua
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
NanamiDPS = {}
|
||||||
|
|
||||||
|
NanamiDPS.version = "1.0.0"
|
||||||
|
|
||||||
|
NanamiDPS.modules = {}
|
||||||
|
NanamiDPS.moduleOrder = {}
|
||||||
|
NanamiDPS.windows = {}
|
||||||
|
|
||||||
|
NanamiDPS.callbacks = {
|
||||||
|
refresh = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
local defaultConfig = {
|
||||||
|
barHeight = 16,
|
||||||
|
barSpacing = 1,
|
||||||
|
fontSize = 10,
|
||||||
|
trackAllUnits = false,
|
||||||
|
mergePets = true,
|
||||||
|
visible = true,
|
||||||
|
locked = false,
|
||||||
|
maxSegments = 10,
|
||||||
|
backdropAlpha = 0.92,
|
||||||
|
showClassIcons = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
function NanamiDPS:RegisterModule(name, mod)
|
||||||
|
if self.modules[name] then return end
|
||||||
|
self.modules[name] = mod
|
||||||
|
table.insert(self.moduleOrder, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
function NanamiDPS:GetModule(name)
|
||||||
|
return self.modules[name]
|
||||||
|
end
|
||||||
|
|
||||||
|
function NanamiDPS:FireCallback(name, a1, a2, a3, a4, a5)
|
||||||
|
if self.callbacks[name] then
|
||||||
|
for _, fn in pairs(self.callbacks[name]) do
|
||||||
|
fn(a1, a2, a3, a4, a5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function NanamiDPS:RegisterCallback(name, id, fn)
|
||||||
|
if not self.callbacks[name] then
|
||||||
|
self.callbacks[name] = {}
|
||||||
|
end
|
||||||
|
self.callbacks[name][id] = fn
|
||||||
|
end
|
||||||
|
|
||||||
|
StaticPopupDialogs["NANAMI_DPS_CONFIRM"] = {
|
||||||
|
button1 = YES,
|
||||||
|
button2 = NO,
|
||||||
|
timeout = 0,
|
||||||
|
whileDead = 1,
|
||||||
|
hideOnEscape = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
local initFrame = CreateFrame("Frame")
|
||||||
|
initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||||
|
initFrame:SetScript("OnEvent", function()
|
||||||
|
if not NanamiDPS_DB then
|
||||||
|
NanamiDPS_DB = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS_DB.config = NanamiDPS_DB.config or {}
|
||||||
|
for k, v in pairs(defaultConfig) do
|
||||||
|
if NanamiDPS_DB.config[k] == nil then
|
||||||
|
NanamiDPS_DB.config[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
NanamiDPS.config = NanamiDPS_DB.config
|
||||||
|
|
||||||
|
NanamiDPS_DB.windows = NanamiDPS_DB.windows or {}
|
||||||
|
NanamiDPS_DB.windowPositions = NanamiDPS_DB.windowPositions or {}
|
||||||
|
|
||||||
|
NanamiDPS:FireCallback("INIT")
|
||||||
|
end)
|
||||||
249
DataStore.lua
Normal file
249
DataStore.lua
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
|
||||||
|
local DataStore = {}
|
||||||
|
NanamiDPS.DataStore = DataStore
|
||||||
|
|
||||||
|
local function CreateEmptySegmentData()
|
||||||
|
return {
|
||||||
|
damage = {},
|
||||||
|
healing = {},
|
||||||
|
damageTaken = {},
|
||||||
|
deaths = {},
|
||||||
|
dispels = {},
|
||||||
|
interrupts = {},
|
||||||
|
threat = {},
|
||||||
|
activity = {},
|
||||||
|
classes = {},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CreateSegment(name)
|
||||||
|
return {
|
||||||
|
name = name or "Unknown",
|
||||||
|
startTime = GetTime(),
|
||||||
|
endTime = 0,
|
||||||
|
duration = 0,
|
||||||
|
data = CreateEmptySegmentData(),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
DataStore.total = CreateSegment("Total")
|
||||||
|
DataStore.current = CreateSegment("Current")
|
||||||
|
DataStore.history = {}
|
||||||
|
|
||||||
|
DataStore.inCombat = false
|
||||||
|
|
||||||
|
function DataStore:GetSegment(index)
|
||||||
|
if index == 0 then
|
||||||
|
return self.total
|
||||||
|
elseif index == 1 then
|
||||||
|
return self.current
|
||||||
|
elseif index and self.history[index - 1] then
|
||||||
|
return self.history[index - 1]
|
||||||
|
end
|
||||||
|
return self.current
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:GetSegmentList()
|
||||||
|
local list = {}
|
||||||
|
table.insert(list, { index = 0, name = NanamiDPS.L["Total"] })
|
||||||
|
table.insert(list, { index = 1, name = NanamiDPS.L["Current"] })
|
||||||
|
for i, seg in ipairs(self.history) do
|
||||||
|
table.insert(list, { index = i + 1, name = seg.name })
|
||||||
|
end
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:StartCombat()
|
||||||
|
if self.inCombat then return end
|
||||||
|
self.inCombat = true
|
||||||
|
|
||||||
|
self.current = CreateSegment("Current")
|
||||||
|
self.current.startTime = GetTime()
|
||||||
|
|
||||||
|
NanamiDPS:FireCallback("COMBAT_START")
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:StopCombat()
|
||||||
|
if not self.inCombat then return end
|
||||||
|
self.inCombat = false
|
||||||
|
|
||||||
|
self.current.endTime = GetTime()
|
||||||
|
self.current.duration = self.current.endTime - self.current.startTime
|
||||||
|
|
||||||
|
local hasData = false
|
||||||
|
for _ in pairs(self.current.data.damage) do hasData = true; break end
|
||||||
|
if not hasData then
|
||||||
|
for _ in pairs(self.current.data.healing) do hasData = true; break end
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasData and self.current.duration >= 2 then
|
||||||
|
local segName = date("%H:%M:%S")
|
||||||
|
self.current.name = segName
|
||||||
|
|
||||||
|
table.insert(self.history, 1, self.current)
|
||||||
|
|
||||||
|
local maxSegs = (NanamiDPS.config and NanamiDPS.config.maxSegments) or 10
|
||||||
|
while table.getn(self.history) > maxSegs do
|
||||||
|
table.remove(self.history)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.current = CreateSegment("Current")
|
||||||
|
|
||||||
|
NanamiDPS:FireCallback("COMBAT_STOP")
|
||||||
|
NanamiDPS:FireCallback("refresh")
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:AddDamage(source, spell, target, amount, school)
|
||||||
|
if not source or not amount then return end
|
||||||
|
amount = tonumber(amount)
|
||||||
|
if not amount or amount <= 0 then return end
|
||||||
|
|
||||||
|
local segs = { self.current, self.total }
|
||||||
|
for _, seg in ipairs(segs) do
|
||||||
|
local d = seg.data.damage
|
||||||
|
if not d[source] then
|
||||||
|
d[source] = { _sum = 0, _ctime = 1, _tick = 0, spells = {} }
|
||||||
|
end
|
||||||
|
d[source]._sum = d[source]._sum + amount
|
||||||
|
d[source].spells[spell] = (d[source].spells[spell] or 0) + amount
|
||||||
|
self:UpdateCombatTime(d[source])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:AddDamageTaken(target, spell, source, amount, school)
|
||||||
|
if not target or not amount then return end
|
||||||
|
amount = tonumber(amount)
|
||||||
|
if not amount or amount <= 0 then return end
|
||||||
|
|
||||||
|
local segs = { self.current, self.total }
|
||||||
|
for _, seg in ipairs(segs) do
|
||||||
|
local d = seg.data.damageTaken
|
||||||
|
if not d[target] then
|
||||||
|
d[target] = { _sum = 0, _ctime = 1, _tick = 0, spells = {} }
|
||||||
|
end
|
||||||
|
d[target]._sum = d[target]._sum + amount
|
||||||
|
d[target].spells[spell] = (d[target].spells[spell] or 0) + amount
|
||||||
|
self:UpdateCombatTime(d[target])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:AddHealing(source, spell, target, amount, effective)
|
||||||
|
if not source or not amount then return end
|
||||||
|
amount = tonumber(amount)
|
||||||
|
effective = tonumber(effective) or amount
|
||||||
|
if not amount or amount <= 0 then return end
|
||||||
|
|
||||||
|
local segs = { self.current, self.total }
|
||||||
|
for _, seg in ipairs(segs) do
|
||||||
|
local d = seg.data.healing
|
||||||
|
if not d[source] then
|
||||||
|
d[source] = { _sum = 0, _esum = 0, _ctime = 1, _tick = 0, spells = {}, effective = {} }
|
||||||
|
end
|
||||||
|
d[source]._sum = d[source]._sum + amount
|
||||||
|
d[source]._esum = d[source]._esum + effective
|
||||||
|
d[source].spells[spell] = (d[source].spells[spell] or 0) + amount
|
||||||
|
d[source].effective[spell] = (d[source].effective[spell] or 0) + effective
|
||||||
|
self:UpdateCombatTime(d[source])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:AddDeath(playerName, deathLog)
|
||||||
|
local segs = { self.current, self.total }
|
||||||
|
for _, seg in ipairs(segs) do
|
||||||
|
local d = seg.data.deaths
|
||||||
|
if not d[playerName] then
|
||||||
|
d[playerName] = { _sum = 0, events = {} }
|
||||||
|
end
|
||||||
|
d[playerName]._sum = d[playerName]._sum + 1
|
||||||
|
table.insert(d[playerName].events, deathLog)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:AddDispel(source, spell, target, aura)
|
||||||
|
if not source then return end
|
||||||
|
local segs = { self.current, self.total }
|
||||||
|
for _, seg in ipairs(segs) do
|
||||||
|
local d = seg.data.dispels
|
||||||
|
if not d[source] then
|
||||||
|
d[source] = { _sum = 0, spells = {} }
|
||||||
|
end
|
||||||
|
d[source]._sum = d[source]._sum + 1
|
||||||
|
d[source].spells[spell] = (d[source].spells[spell] or 0) + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:AddInterrupt(source, spell, target, interrupted)
|
||||||
|
if not source then return end
|
||||||
|
local segs = { self.current, self.total }
|
||||||
|
for _, seg in ipairs(segs) do
|
||||||
|
local d = seg.data.interrupts
|
||||||
|
if not d[source] then
|
||||||
|
d[source] = { _sum = 0, spells = {} }
|
||||||
|
end
|
||||||
|
d[source]._sum = d[source]._sum + 1
|
||||||
|
d[source].spells[spell] = (d[source].spells[spell] or 0) + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:AddThreat(source, amount)
|
||||||
|
if not source or not amount then return end
|
||||||
|
local segs = { self.current, self.total }
|
||||||
|
for _, seg in ipairs(segs) do
|
||||||
|
local d = seg.data.threat
|
||||||
|
if not d[source] then
|
||||||
|
d[source] = { _sum = 0 }
|
||||||
|
end
|
||||||
|
d[source]._sum = d[source]._sum + amount
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:UpdateActivity(source, timestamp)
|
||||||
|
if not source then return end
|
||||||
|
local segs = { self.current, self.total }
|
||||||
|
for _, seg in ipairs(segs) do
|
||||||
|
local d = seg.data.activity
|
||||||
|
if not d[source] then
|
||||||
|
d[source] = { _firstAction = timestamp, _lastAction = timestamp, _activeTime = 0, _tick = 0 }
|
||||||
|
end
|
||||||
|
local entry = d[source]
|
||||||
|
if entry._tick > 0 and (timestamp - entry._tick) < 5 then
|
||||||
|
entry._activeTime = entry._activeTime + (timestamp - entry._tick)
|
||||||
|
end
|
||||||
|
entry._tick = timestamp
|
||||||
|
entry._lastAction = timestamp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:SetClass(name, class)
|
||||||
|
self.current.data.classes[name] = class
|
||||||
|
self.total.data.classes[name] = class
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:GetClass(name)
|
||||||
|
return self.current.data.classes[name] or self.total.data.classes[name]
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:UpdateCombatTime(entry)
|
||||||
|
if not entry._tick then entry._tick = 0 end
|
||||||
|
local now = GetTime()
|
||||||
|
if entry._tick == 0 then
|
||||||
|
entry._tick = now
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local diff = now - entry._tick
|
||||||
|
if diff >= 5 then
|
||||||
|
entry._ctime = entry._ctime + 5
|
||||||
|
else
|
||||||
|
entry._ctime = entry._ctime + diff
|
||||||
|
end
|
||||||
|
entry._tick = now
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataStore:ResetAll()
|
||||||
|
self.total = CreateSegment("Total")
|
||||||
|
self.current = CreateSegment("Current")
|
||||||
|
self.history = {}
|
||||||
|
NanamiDPS:FireCallback("refresh")
|
||||||
|
end
|
||||||
1045
DetailView.lua
Normal file
1045
DetailView.lua
Normal file
File diff suppressed because it is too large
Load Diff
216
Locale.lua
Normal file
216
Locale.lua
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
NanamiDPS.L = {}
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local locale = GetLocale and GetLocale() or "enUS"
|
||||||
|
|
||||||
|
L["Damage Done"] = "Damage Done"
|
||||||
|
L["DPS"] = "DPS"
|
||||||
|
L["Damage Taken"] = "Damage Taken"
|
||||||
|
L["Healing Done"] = "Healing Done"
|
||||||
|
L["HPS"] = "HPS"
|
||||||
|
L["Overhealing"] = "Overhealing"
|
||||||
|
L["Deaths"] = "Deaths"
|
||||||
|
L["Dispels"] = "Dispels"
|
||||||
|
L["Interrupts"] = "Interrupts"
|
||||||
|
L["Threat (Est.)"] = "Threat (Est.)"
|
||||||
|
L["Activity"] = "Activity"
|
||||||
|
L["Enemy Damage Done"] = "Enemy Damage Done"
|
||||||
|
L["Damage by Spell"] = "Damage by Spell"
|
||||||
|
L["Healing by Spell"] = "Healing by Spell"
|
||||||
|
L["Current"] = "Current"
|
||||||
|
L["Total"] = "Total"
|
||||||
|
L["Reset"] = "Reset"
|
||||||
|
L["Settings"] = "Settings"
|
||||||
|
L["Report"] = "Report"
|
||||||
|
L["Lock"] = "Lock"
|
||||||
|
L["Unlock"] = "Unlock"
|
||||||
|
L["Segment"] = "Segment"
|
||||||
|
L["Mode"] = "Mode"
|
||||||
|
L["No Data"] = "No Data"
|
||||||
|
L["Pet"] = "Pet"
|
||||||
|
L["Auto Hit"] = "Auto Hit"
|
||||||
|
L["Details"] = "Details"
|
||||||
|
L["Overheal"] = "Overheal"
|
||||||
|
L["Per Second"] = "Per Second"
|
||||||
|
L["Total Amount"] = "Total Amount"
|
||||||
|
L["Active Time"] = "Active Time"
|
||||||
|
L["Percent"] = "Percent"
|
||||||
|
L["Died at"] = "Died at"
|
||||||
|
L["Killing Blow"] = "Killing Blow"
|
||||||
|
L["Last Events"] = "Last Events"
|
||||||
|
L["Report to Chat"] = "Report to Chat"
|
||||||
|
L["Reset Data?"] = "Do you wish to reset all data?"
|
||||||
|
L["Bar Height"] = "Bar Height"
|
||||||
|
L["Bar Spacing"] = "Bar Spacing"
|
||||||
|
L["Font Size"] = "Font Size"
|
||||||
|
L["Track All Units"] = "Track All Units"
|
||||||
|
L["Merge Pets"] = "Merge Pets with Owner"
|
||||||
|
L["Show Class Icons"] = "Show Class Icons"
|
||||||
|
L["Lock Windows"] = "Lock Windows"
|
||||||
|
L["Backdrop Alpha"] = "Backdrop Alpha"
|
||||||
|
L["Max Segments"] = "Max History Segments"
|
||||||
|
L["Hide Window"] = "Hide Window"
|
||||||
|
L["Shift-click to reset"] = "Shift-click to reset instantly"
|
||||||
|
L["Fight Duration"] = "Fight Duration"
|
||||||
|
L["Effective"] = "Effective"
|
||||||
|
L["Unknown"] = "Unknown"
|
||||||
|
L["Display Settings"] = "Display Settings"
|
||||||
|
L["Data Settings"] = "Data Settings"
|
||||||
|
L["Window Settings"] = "Window Settings"
|
||||||
|
L["Players"] = "Players"
|
||||||
|
L["Targets"] = "Targets"
|
||||||
|
L["Data Reset"] = "Data reset."
|
||||||
|
L["Windows Locked"] = "Windows locked"
|
||||||
|
L["Windows Unlocked"] = "Windows unlocked"
|
||||||
|
L["Commands"] = "Commands"
|
||||||
|
L["Show/Hide"] = "Show/Hide"
|
||||||
|
L["Reset All Data"] = "Reset all data"
|
||||||
|
L["Open Settings"] = "Open settings"
|
||||||
|
L["Lock/Unlock"] = "Lock/Unlock windows"
|
||||||
|
L["Report to chat"] = "Report to chat"
|
||||||
|
L["Create New Window"] = "Create new window"
|
||||||
|
L["Back"] = "Back"
|
||||||
|
L["Detail View"] = "Detail View"
|
||||||
|
L["Spell Breakdown"] = "Spell Breakdown"
|
||||||
|
L["Summary"] = "Summary"
|
||||||
|
L["Comparison"] = "Comparison"
|
||||||
|
L["Top Spells"] = "Top Spells"
|
||||||
|
L["Click to view details"] = "Click to view details"
|
||||||
|
L["Combat Time"] = "Combat Time"
|
||||||
|
L["Max Hit"] = "Max Hit"
|
||||||
|
L["Avg Hit"] = "Avg Hit"
|
||||||
|
L["Overheal %"] = "Overheal %"
|
||||||
|
L["Total Healing"] = "Total Healing"
|
||||||
|
L["Effective Healing"] = "Effective Healing"
|
||||||
|
L["Total Damage"] = "Total Damage"
|
||||||
|
L["Rank"] = "Rank"
|
||||||
|
L["of total"] = "of total"
|
||||||
|
L["Spell Chart"] = "Spell Chart"
|
||||||
|
L["Player Comparison"] = "Player Comparison"
|
||||||
|
L["Death Log"] = "Death Log"
|
||||||
|
L["Threat Breakdown"] = "Threat Breakdown"
|
||||||
|
L["Damage Contribution"] = "Damage Contribution"
|
||||||
|
L["Healing Contribution"] = "Healing Contribution"
|
||||||
|
L["Say"] = "Say"
|
||||||
|
L["Yell"] = "Yell"
|
||||||
|
L["Party"] = "Party"
|
||||||
|
L["Raid"] = "Raid"
|
||||||
|
L["Guild"] = "Guild"
|
||||||
|
L["Officer"] = "Officer"
|
||||||
|
L["Whisper"] = "Whisper"
|
||||||
|
L["Channel"] = "Channel"
|
||||||
|
L["Send Report"] = "Send Report"
|
||||||
|
L["Report Lines"] = "Lines"
|
||||||
|
L["Report Sent"] = "Report sent!"
|
||||||
|
L["No Report Data"] = "No data to report."
|
||||||
|
L["Enter Whisper Target"] = "Enter player name"
|
||||||
|
L["Owner"] = "Owner"
|
||||||
|
L["Threat Note"] = "Includes spell-specific threat modifiers"
|
||||||
|
L["Drag to Resize"] = "Drag to resize"
|
||||||
|
|
||||||
|
if locale == "zhCN" or locale == "zhTW" then
|
||||||
|
L["Damage Done"] = "造成伤害"
|
||||||
|
L["DPS"] = "每秒伤害"
|
||||||
|
L["Damage Taken"] = "受到伤害"
|
||||||
|
L["Healing Done"] = "治疗量"
|
||||||
|
L["HPS"] = "每秒治疗"
|
||||||
|
L["Overhealing"] = "过量治疗"
|
||||||
|
L["Deaths"] = "死亡"
|
||||||
|
L["Dispels"] = "驱散"
|
||||||
|
L["Interrupts"] = "打断"
|
||||||
|
L["Threat (Est.)"] = "仇恨(估算)"
|
||||||
|
L["Activity"] = "活跃时间"
|
||||||
|
L["Enemy Damage Done"] = "敌方伤害"
|
||||||
|
L["Damage by Spell"] = "技能伤害"
|
||||||
|
L["Healing by Spell"] = "技能治疗"
|
||||||
|
L["Current"] = "当前"
|
||||||
|
L["Total"] = "总计"
|
||||||
|
L["Reset"] = "重置"
|
||||||
|
L["Settings"] = "设置"
|
||||||
|
L["Report"] = "汇报"
|
||||||
|
L["Lock"] = "锁定"
|
||||||
|
L["Unlock"] = "解锁"
|
||||||
|
L["Segment"] = "战斗段"
|
||||||
|
L["Mode"] = "模式"
|
||||||
|
L["No Data"] = "暂无数据"
|
||||||
|
L["Pet"] = "宠物"
|
||||||
|
L["Auto Hit"] = "自动攻击"
|
||||||
|
L["Details"] = "详情"
|
||||||
|
L["Overheal"] = "过量治疗"
|
||||||
|
L["Per Second"] = "每秒"
|
||||||
|
L["Total Amount"] = "总量"
|
||||||
|
L["Active Time"] = "活跃时间"
|
||||||
|
L["Percent"] = "百分比"
|
||||||
|
L["Died at"] = "死亡于"
|
||||||
|
L["Killing Blow"] = "致死一击"
|
||||||
|
L["Last Events"] = "最后事件"
|
||||||
|
L["Report to Chat"] = "汇报到聊天"
|
||||||
|
L["Reset Data?"] = "确定重置所有数据吗?"
|
||||||
|
L["Bar Height"] = "条高度"
|
||||||
|
L["Bar Spacing"] = "条间距"
|
||||||
|
L["Font Size"] = "字体大小"
|
||||||
|
L["Track All Units"] = "追踪所有单位"
|
||||||
|
L["Merge Pets"] = "合并宠物数据"
|
||||||
|
L["Show Class Icons"] = "显示职业图标"
|
||||||
|
L["Lock Windows"] = "锁定窗口"
|
||||||
|
L["Backdrop Alpha"] = "背景透明度"
|
||||||
|
L["Max Segments"] = "最大历史段数"
|
||||||
|
L["Hide Window"] = "隐藏窗口"
|
||||||
|
L["Shift-click to reset"] = "Shift-点击立即重置"
|
||||||
|
L["Fight Duration"] = "战斗时长"
|
||||||
|
L["Effective"] = "有效值"
|
||||||
|
L["Unknown"] = "未知"
|
||||||
|
L["Display Settings"] = "显示设置"
|
||||||
|
L["Data Settings"] = "数据设置"
|
||||||
|
L["Window Settings"] = "窗口设置"
|
||||||
|
L["Players"] = "玩家"
|
||||||
|
L["Targets"] = "目标"
|
||||||
|
L["Data Reset"] = "数据已重置。"
|
||||||
|
L["Windows Locked"] = "窗口已锁定"
|
||||||
|
L["Windows Unlocked"] = "窗口已解锁"
|
||||||
|
L["Commands"] = "命令列表"
|
||||||
|
L["Show/Hide"] = "显示/隐藏"
|
||||||
|
L["Reset All Data"] = "重置所有数据"
|
||||||
|
L["Open Settings"] = "打开设置"
|
||||||
|
L["Lock/Unlock"] = "锁定/解锁窗口"
|
||||||
|
L["Report to chat"] = "汇报到聊天"
|
||||||
|
L["Create New Window"] = "创建新窗口"
|
||||||
|
L["Back"] = "返回"
|
||||||
|
L["Detail View"] = "详细信息"
|
||||||
|
L["Spell Breakdown"] = "技能分解"
|
||||||
|
L["Summary"] = "概要"
|
||||||
|
L["Comparison"] = "对比"
|
||||||
|
L["Top Spells"] = "技能排名"
|
||||||
|
L["Click to view details"] = "点击查看详情"
|
||||||
|
L["Combat Time"] = "战斗时间"
|
||||||
|
L["Max Hit"] = "最高一击"
|
||||||
|
L["Avg Hit"] = "平均一击"
|
||||||
|
L["Overheal %"] = "过量治疗%"
|
||||||
|
L["Total Healing"] = "总治疗量"
|
||||||
|
L["Effective Healing"] = "有效治疗"
|
||||||
|
L["Total Damage"] = "总伤害"
|
||||||
|
L["Rank"] = "排名"
|
||||||
|
L["of total"] = "占总量"
|
||||||
|
L["Spell Chart"] = "技能图表"
|
||||||
|
L["Player Comparison"] = "玩家对比"
|
||||||
|
L["Death Log"] = "死亡记录"
|
||||||
|
L["Threat Breakdown"] = "仇恨分解"
|
||||||
|
L["Damage Contribution"] = "伤害贡献"
|
||||||
|
L["Healing Contribution"] = "治疗贡献"
|
||||||
|
L["Say"] = "说"
|
||||||
|
L["Yell"] = "喊叫"
|
||||||
|
L["Party"] = "小队"
|
||||||
|
L["Raid"] = "团队"
|
||||||
|
L["Guild"] = "公会"
|
||||||
|
L["Officer"] = "军官"
|
||||||
|
L["Whisper"] = "密语"
|
||||||
|
L["Channel"] = "频道"
|
||||||
|
L["Send Report"] = "发送汇报"
|
||||||
|
L["Report Lines"] = "行数"
|
||||||
|
L["Report Sent"] = "汇报已发送!"
|
||||||
|
L["No Report Data"] = "没有可汇报的数据。"
|
||||||
|
L["Enter Whisper Target"] = "输入玩家名称"
|
||||||
|
L["Owner"] = "主人"
|
||||||
|
L["Threat Note"] = "已计入技能仇恨系数"
|
||||||
|
L["Drag to Resize"] = "拖拽调整大小"
|
||||||
|
end
|
||||||
81
Modules/Activity.lua
Normal file
81
Modules/Activity.lua
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local Activity = {}
|
||||||
|
|
||||||
|
function Activity:GetName()
|
||||||
|
return L["Activity"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Activity:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.activity then return {} end
|
||||||
|
|
||||||
|
local segDuration = segment.duration
|
||||||
|
if segDuration <= 0 then
|
||||||
|
segDuration = GetTime() - (segment.startTime or GetTime())
|
||||||
|
end
|
||||||
|
if segDuration <= 0 then segDuration = 1 end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for name, entry in pairs(segment.data.activity) do
|
||||||
|
local class = DataStore:GetClass(name)
|
||||||
|
local r, g, b = NanamiDPS.GetClassColor(class)
|
||||||
|
if not NanamiDPS.validClasses[class] then
|
||||||
|
r, g, b = NanamiDPS.str2rgb(name)
|
||||||
|
r = r * 0.6 + 0.4
|
||||||
|
g = g * 0.6 + 0.4
|
||||||
|
b = b * 0.6 + 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
local activeTime = entry._activeTime or 0
|
||||||
|
local pct = segDuration > 0 and (activeTime / segDuration * 100) or 0
|
||||||
|
pct = math.min(pct, 100)
|
||||||
|
|
||||||
|
table.insert(bars, {
|
||||||
|
id = name,
|
||||||
|
name = name,
|
||||||
|
value = pct,
|
||||||
|
class = class,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
valueText = NanamiDPS.round(pct, 1) .. "% (" .. NanamiDPS.formatTime(activeTime) .. ")",
|
||||||
|
activeTime = activeTime,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function Activity:GetTooltip(playerName, segment, tooltip)
|
||||||
|
if not segment or not segment.data.activity[playerName] then return end
|
||||||
|
local entry = segment.data.activity[playerName]
|
||||||
|
|
||||||
|
local segDuration = segment.duration
|
||||||
|
if segDuration <= 0 then
|
||||||
|
segDuration = GetTime() - (segment.startTime or GetTime())
|
||||||
|
end
|
||||||
|
if segDuration <= 0 then segDuration = 1 end
|
||||||
|
|
||||||
|
local activeTime = entry._activeTime or 0
|
||||||
|
local pct = math.min(activeTime / segDuration * 100, 100)
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffffd100" .. playerName)
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Activity"], "|cffffffff" .. NanamiDPS.round(pct, 1) .. "%")
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Active Time"], "|cffffffff" .. NanamiDPS.formatTime(activeTime))
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Fight Duration"], "|cffffffff" .. NanamiDPS.formatTime(segDuration))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Activity:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %.1f%% active", i, d.name, d.value))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("Activity", Activity)
|
||||||
74
Modules/DPS.lua
Normal file
74
Modules/DPS.lua
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local DPS = {}
|
||||||
|
|
||||||
|
function DPS:GetName()
|
||||||
|
return L["DPS"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function DPS:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.damage then return {} end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for name, entry in pairs(segment.data.damage) do
|
||||||
|
local class = DataStore:GetClass(name)
|
||||||
|
local r, g, b = NanamiDPS.GetClassColor(class)
|
||||||
|
if not NanamiDPS.validClasses[class] then
|
||||||
|
r, g, b = NanamiDPS.str2rgb(name)
|
||||||
|
r = r * 0.6 + 0.4
|
||||||
|
g = g * 0.6 + 0.4
|
||||||
|
b = b * 0.6 + 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
local dps = entry._sum / math.max(entry._ctime, 1)
|
||||||
|
table.insert(bars, {
|
||||||
|
id = name,
|
||||||
|
name = name,
|
||||||
|
value = dps,
|
||||||
|
class = class,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
valueText = NanamiDPS.round(dps, 1) .. " DPS",
|
||||||
|
totalDamage = entry._sum,
|
||||||
|
ctime = entry._ctime,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local best = bars[1] and bars[1].value or 0
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = best > 0 and (bar.value / best * 100) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function DPS:GetTooltip(playerName, segment, tooltip)
|
||||||
|
if not segment or not segment.data.damage[playerName] then return end
|
||||||
|
local entry = segment.data.damage[playerName]
|
||||||
|
local dps = entry._sum / math.max(entry._ctime, 1)
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffffd100" .. playerName)
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["DPS"], "|cffffffff" .. NanamiDPS.round(dps, 1))
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Damage Done"], "|cffffffff" .. NanamiDPS.formatNumber(entry._sum))
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Active Time"],
|
||||||
|
"|cffffffff" .. NanamiDPS.formatTime(entry._ctime))
|
||||||
|
|
||||||
|
NanamiDPS.Tooltip:ShowSpellDetail(playerName, entry.spells, nil, entry._sum, nil, tooltip)
|
||||||
|
end
|
||||||
|
|
||||||
|
function DPS:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %.1f DPS (%s)",
|
||||||
|
i, d.name, d.value, NanamiDPS.formatNumber(d.totalDamage)))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("DPS", DPS)
|
||||||
90
Modules/DamageBySpell.lua
Normal file
90
Modules/DamageBySpell.lua
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local DamageBySpell = {}
|
||||||
|
|
||||||
|
function DamageBySpell:GetName()
|
||||||
|
return L["Damage by Spell"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function DamageBySpell:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.damage then return {} end
|
||||||
|
|
||||||
|
-- Aggregate all spells across all players
|
||||||
|
local spellTotals = {}
|
||||||
|
for name, entry in pairs(segment.data.damage) do
|
||||||
|
if entry.spells then
|
||||||
|
for spell, amount in pairs(entry.spells) do
|
||||||
|
spellTotals[spell] = (spellTotals[spell] or 0) + amount
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for spell, total in pairs(spellTotals) do
|
||||||
|
local r, g, b = NanamiDPS.str2rgb(spell)
|
||||||
|
r = r * 0.5 + 0.4
|
||||||
|
g = g * 0.5 + 0.4
|
||||||
|
b = b * 0.5 + 0.4
|
||||||
|
|
||||||
|
table.insert(bars, {
|
||||||
|
id = spell,
|
||||||
|
name = spell,
|
||||||
|
value = total,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local grandTotal = 0
|
||||||
|
for _, bar in ipairs(bars) do grandTotal = grandTotal + bar.value end
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = grandTotal > 0 and (bar.value / grandTotal * 100) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function DamageBySpell:GetTooltip(spellName, segment, tooltip)
|
||||||
|
tooltip:AddLine("|cffffd100" .. (spellName or L["Unknown"]))
|
||||||
|
|
||||||
|
if segment and segment.data.damage then
|
||||||
|
local users = {}
|
||||||
|
for name, entry in pairs(segment.data.damage) do
|
||||||
|
if entry.spells and entry.spells[spellName] then
|
||||||
|
table.insert(users, { name = name, amount = entry.spells[spellName] })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(users, function(a, b) return a.amount > b.amount end)
|
||||||
|
|
||||||
|
if table.getn(users) > 0 then
|
||||||
|
local total = 0
|
||||||
|
for _, u in ipairs(users) do total = total + u.amount end
|
||||||
|
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Total Amount"], "|cffffffff" .. NanamiDPS.formatNumber(total))
|
||||||
|
tooltip:AddLine(" ")
|
||||||
|
tooltip:AddLine("|cffffd100" .. L["Players"] .. ":")
|
||||||
|
for _, u in ipairs(users) do
|
||||||
|
local pct = total > 0 and NanamiDPS.round(u.amount / total * 100, 1) or 0
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. u.name,
|
||||||
|
"|cffffffff" .. NanamiDPS.formatNumber(u.amount) .. " (" .. pct .. "%)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DamageBySpell:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %s (%.1f%%)",
|
||||||
|
i, d.name, NanamiDPS.formatNumber(d.value), d.percent))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("DamageBySpell", DamageBySpell)
|
||||||
79
Modules/DamageDone.lua
Normal file
79
Modules/DamageDone.lua
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local DamageDone = {}
|
||||||
|
|
||||||
|
function DamageDone:GetName()
|
||||||
|
return L["Damage Done"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function DamageDone:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.damage then return {} end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for name, entry in pairs(segment.data.damage) do
|
||||||
|
local class = DataStore:GetClass(name)
|
||||||
|
local r, g, b = NanamiDPS.GetClassColor(class)
|
||||||
|
if not NanamiDPS.validClasses[class] then
|
||||||
|
r, g, b = NanamiDPS.str2rgb(name)
|
||||||
|
r = r * 0.6 + 0.4
|
||||||
|
g = g * 0.6 + 0.4
|
||||||
|
b = b * 0.6 + 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
local dmg = entry._sum or 0
|
||||||
|
local dps = dmg / math.max(entry._ctime or 0, 1)
|
||||||
|
|
||||||
|
table.insert(bars, {
|
||||||
|
id = name,
|
||||||
|
name = name,
|
||||||
|
value = dmg,
|
||||||
|
class = class,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
dps = dps,
|
||||||
|
ctime = entry._ctime,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local total = 0
|
||||||
|
for _, bar in ipairs(bars) do total = total + bar.value end
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = total > 0 and (bar.value / total * 100) or 0
|
||||||
|
bar.valueText = NanamiDPS.formatNumber(bar.value)
|
||||||
|
.. " " .. NanamiDPS.formatNumber(bar.dps) .. "/s"
|
||||||
|
.. " (" .. NanamiDPS.round(bar.percent, 1) .. "%)"
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function DamageDone:GetTooltip(playerName, segment, tooltip)
|
||||||
|
if not segment or not segment.data.damage[playerName] then return end
|
||||||
|
local entry = segment.data.damage[playerName]
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffffd100" .. playerName)
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Damage Done"], "|cffffffff" .. NanamiDPS.formatNumber(entry._sum))
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["DPS"],
|
||||||
|
"|cffffffff" .. NanamiDPS.round(entry._sum / math.max(entry._ctime, 1), 1))
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Active Time"],
|
||||||
|
"|cffffffff" .. NanamiDPS.formatTime(entry._ctime))
|
||||||
|
|
||||||
|
NanamiDPS.Tooltip:ShowSpellDetail(playerName, entry.spells, nil, entry._sum, nil, tooltip)
|
||||||
|
end
|
||||||
|
|
||||||
|
function DamageDone:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %s (%.1f%%)",
|
||||||
|
i, d.name, NanamiDPS.formatNumber(d.value), d.percent))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("DamageDone", DamageDone)
|
||||||
69
Modules/DamageTaken.lua
Normal file
69
Modules/DamageTaken.lua
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local DamageTaken = {}
|
||||||
|
|
||||||
|
function DamageTaken:GetName()
|
||||||
|
return L["Damage Taken"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function DamageTaken:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.damageTaken then return {} end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for name, entry in pairs(segment.data.damageTaken) do
|
||||||
|
local class = DataStore:GetClass(name)
|
||||||
|
local r, g, b = NanamiDPS.GetClassColor(class)
|
||||||
|
if not NanamiDPS.validClasses[class] then
|
||||||
|
r, g, b = NanamiDPS.str2rgb(name)
|
||||||
|
r = r * 0.6 + 0.4
|
||||||
|
g = g * 0.6 + 0.4
|
||||||
|
b = b * 0.6 + 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(bars, {
|
||||||
|
id = name,
|
||||||
|
name = name,
|
||||||
|
value = entry._sum or 0,
|
||||||
|
class = class,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local total = 0
|
||||||
|
for _, bar in ipairs(bars) do total = total + bar.value end
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = total > 0 and (bar.value / total * 100) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function DamageTaken:GetTooltip(playerName, segment, tooltip)
|
||||||
|
if not segment or not segment.data.damageTaken[playerName] then return end
|
||||||
|
local entry = segment.data.damageTaken[playerName]
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffffd100" .. playerName)
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Damage Taken"], "|cffffffff" .. NanamiDPS.formatNumber(entry._sum))
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Active Time"],
|
||||||
|
"|cffffffff" .. NanamiDPS.formatTime(entry._ctime))
|
||||||
|
|
||||||
|
NanamiDPS.Tooltip:ShowSpellDetail(playerName, entry.spells, nil, entry._sum, nil, tooltip)
|
||||||
|
end
|
||||||
|
|
||||||
|
function DamageTaken:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %s (%.1f%%)",
|
||||||
|
i, d.name, NanamiDPS.formatNumber(d.value), d.percent))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("DamageTaken", DamageTaken)
|
||||||
94
Modules/Deaths.lua
Normal file
94
Modules/Deaths.lua
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local Deaths = {}
|
||||||
|
|
||||||
|
function Deaths:GetName()
|
||||||
|
return L["Deaths"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Deaths:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.deaths then return {} end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for name, entry in pairs(segment.data.deaths) do
|
||||||
|
local class = DataStore:GetClass(name)
|
||||||
|
local r, g, b = NanamiDPS.GetClassColor(class)
|
||||||
|
if not NanamiDPS.validClasses[class] then
|
||||||
|
r, g, b = NanamiDPS.str2rgb(name)
|
||||||
|
r = r * 0.6 + 0.4
|
||||||
|
g = g * 0.6 + 0.4
|
||||||
|
b = b * 0.6 + 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(bars, {
|
||||||
|
id = name,
|
||||||
|
name = name,
|
||||||
|
value = entry._sum or 0,
|
||||||
|
class = class,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
valueText = tostring(entry._sum or 0) .. "x",
|
||||||
|
events = entry.events,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function Deaths:GetTooltip(playerName, segment, tooltip)
|
||||||
|
if not segment or not segment.data.deaths[playerName] then return end
|
||||||
|
local entry = segment.data.deaths[playerName]
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffffd100" .. playerName .. " - " .. L["Deaths"])
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Deaths"], "|cffffffff" .. (entry._sum or 0))
|
||||||
|
|
||||||
|
if entry.events and table.getn(entry.events) > 0 then
|
||||||
|
local lastDeath = entry.events[table.getn(entry.events)]
|
||||||
|
|
||||||
|
tooltip:AddLine(" ")
|
||||||
|
tooltip:AddLine("|cffffd100" .. L["Last Events"] .. " (" .. (lastDeath.timeStr or "") .. "):")
|
||||||
|
|
||||||
|
if lastDeath.events then
|
||||||
|
local startIdx = math.max(1, table.getn(lastDeath.events) - 9)
|
||||||
|
for i = startIdx, table.getn(lastDeath.events) do
|
||||||
|
local evt = lastDeath.events[i]
|
||||||
|
if evt then
|
||||||
|
local timeAgo = ""
|
||||||
|
if lastDeath.time and evt.time then
|
||||||
|
timeAgo = string.format("-%.1fs", lastDeath.time - evt.time)
|
||||||
|
end
|
||||||
|
|
||||||
|
if evt.type == "damage" then
|
||||||
|
local line = string.format("|cffff4444-%s|r %s (%s)",
|
||||||
|
NanamiDPS.formatNumber(math.abs(evt.amount or 0)),
|
||||||
|
evt.spell or "?",
|
||||||
|
evt.source or "?")
|
||||||
|
tooltip:AddDoubleLine("|cffaaaaaa" .. timeAgo, line)
|
||||||
|
elseif evt.type == "heal" then
|
||||||
|
local line = string.format("|cff44ff44+%s|r %s (%s)",
|
||||||
|
NanamiDPS.formatNumber(evt.amount or 0),
|
||||||
|
evt.spell or "?",
|
||||||
|
evt.source or "?")
|
||||||
|
tooltip:AddDoubleLine("|cffaaaaaa" .. timeAgo, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Deaths:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %dx deaths", i, d.name, d.value))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("Deaths", Deaths)
|
||||||
78
Modules/Dispels.lua
Normal file
78
Modules/Dispels.lua
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local Dispels = {}
|
||||||
|
|
||||||
|
function Dispels:GetName()
|
||||||
|
return L["Dispels"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dispels:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.dispels then return {} end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for name, entry in pairs(segment.data.dispels) do
|
||||||
|
local class = DataStore:GetClass(name)
|
||||||
|
local r, g, b = NanamiDPS.GetClassColor(class)
|
||||||
|
if not NanamiDPS.validClasses[class] then
|
||||||
|
r, g, b = NanamiDPS.str2rgb(name)
|
||||||
|
r = r * 0.6 + 0.4
|
||||||
|
g = g * 0.6 + 0.4
|
||||||
|
b = b * 0.6 + 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(bars, {
|
||||||
|
id = name,
|
||||||
|
name = name,
|
||||||
|
value = entry._sum or 0,
|
||||||
|
class = class,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
valueText = tostring(entry._sum or 0),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local total = 0
|
||||||
|
for _, bar in ipairs(bars) do total = total + bar.value end
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = total > 0 and (bar.value / total * 100) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dispels:GetTooltip(playerName, segment, tooltip)
|
||||||
|
if not segment or not segment.data.dispels[playerName] then return end
|
||||||
|
local entry = segment.data.dispels[playerName]
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffffd100" .. playerName .. " - " .. L["Dispels"])
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Dispels"], "|cffffffff" .. (entry._sum or 0))
|
||||||
|
|
||||||
|
if entry.spells then
|
||||||
|
tooltip:AddLine(" ")
|
||||||
|
tooltip:AddLine("|cffffd100" .. L["Details"] .. ":")
|
||||||
|
local sorted = {}
|
||||||
|
for spell, count in pairs(entry.spells) do
|
||||||
|
table.insert(sorted, { spell = spell, count = count })
|
||||||
|
end
|
||||||
|
table.sort(sorted, function(a, b) return a.count > b.count end)
|
||||||
|
for _, s in ipairs(sorted) do
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. s.spell, "|cffffffff" .. s.count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dispels:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %d dispels", i, d.name, d.value))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("Dispels", Dispels)
|
||||||
90
Modules/EnemyDamageDone.lua
Normal file
90
Modules/EnemyDamageDone.lua
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local EnemyDamageDone = {}
|
||||||
|
|
||||||
|
function EnemyDamageDone:GetName()
|
||||||
|
return L["Enemy Damage Done"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function EnemyDamageDone:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.damageTaken then return {} end
|
||||||
|
|
||||||
|
-- Aggregate damage by source across all targets
|
||||||
|
local bySource = {}
|
||||||
|
for targetName, entry in pairs(segment.data.damageTaken) do
|
||||||
|
if entry.spells then
|
||||||
|
for spell, amount in pairs(entry.spells) do
|
||||||
|
-- Extract real source from spell name if available
|
||||||
|
-- Otherwise group by spell
|
||||||
|
if not bySource[spell] then
|
||||||
|
bySource[spell] = 0
|
||||||
|
end
|
||||||
|
bySource[spell] = bySource[spell] + amount
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for spell, total in pairs(bySource) do
|
||||||
|
local r, g, b = NanamiDPS.str2rgb(spell)
|
||||||
|
r = r * 0.5 + 0.5
|
||||||
|
g = g * 0.3 + 0.2
|
||||||
|
b = b * 0.3 + 0.2
|
||||||
|
|
||||||
|
table.insert(bars, {
|
||||||
|
id = spell,
|
||||||
|
name = spell,
|
||||||
|
value = total,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local grandTotal = 0
|
||||||
|
for _, bar in ipairs(bars) do grandTotal = grandTotal + bar.value end
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = grandTotal > 0 and (bar.value / grandTotal * 100) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function EnemyDamageDone:GetTooltip(spellName, segment, tooltip)
|
||||||
|
tooltip:AddLine("|cffffd100" .. (spellName or L["Unknown"]))
|
||||||
|
|
||||||
|
-- Find all targets hit by this spell
|
||||||
|
if segment and segment.data.damageTaken then
|
||||||
|
local targets = {}
|
||||||
|
for targetName, entry in pairs(segment.data.damageTaken) do
|
||||||
|
if entry.spells and entry.spells[spellName] then
|
||||||
|
table.insert(targets, { name = targetName, amount = entry.spells[spellName] })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(targets, function(a, b) return a.amount > b.amount end)
|
||||||
|
|
||||||
|
if table.getn(targets) > 0 then
|
||||||
|
tooltip:AddLine(" ")
|
||||||
|
tooltip:AddLine("|cffffd100" .. L["Targets"] .. ":")
|
||||||
|
for _, t in ipairs(targets) do
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. t.name, "|cffffffff" .. NanamiDPS.formatNumber(t.amount))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function EnemyDamageDone:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %s (%.1f%%)",
|
||||||
|
i, d.name, NanamiDPS.formatNumber(d.value), d.percent))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("EnemyDamageDone", EnemyDamageDone)
|
||||||
80
Modules/HPS.lua
Normal file
80
Modules/HPS.lua
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local HPS = {}
|
||||||
|
|
||||||
|
function HPS:GetName()
|
||||||
|
return L["HPS"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function HPS:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.healing then return {} end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for name, entry in pairs(segment.data.healing) do
|
||||||
|
local class = DataStore:GetClass(name)
|
||||||
|
local r, g, b = NanamiDPS.GetClassColor(class)
|
||||||
|
if not NanamiDPS.validClasses[class] then
|
||||||
|
r, g, b = NanamiDPS.str2rgb(name)
|
||||||
|
r = r * 0.6 + 0.4
|
||||||
|
g = g * 0.6 + 0.4
|
||||||
|
b = b * 0.6 + 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
local effectiveVal = entry._esum or entry._sum
|
||||||
|
local hps = effectiveVal / math.max(entry._ctime, 1)
|
||||||
|
|
||||||
|
table.insert(bars, {
|
||||||
|
id = name,
|
||||||
|
name = name,
|
||||||
|
value = hps,
|
||||||
|
class = class,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
valueText = NanamiDPS.round(hps, 1) .. " HPS",
|
||||||
|
effectiveHeal = effectiveVal,
|
||||||
|
totalHeal = entry._sum,
|
||||||
|
ctime = entry._ctime,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local best = bars[1] and bars[1].value or 0
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = best > 0 and (bar.value / best * 100) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function HPS:GetTooltip(playerName, segment, tooltip)
|
||||||
|
if not segment or not segment.data.healing[playerName] then return end
|
||||||
|
local entry = segment.data.healing[playerName]
|
||||||
|
local effectiveVal = entry._esum or entry._sum
|
||||||
|
local hps = effectiveVal / math.max(entry._ctime, 1)
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffffd100" .. playerName)
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["HPS"], "|cffffffff" .. NanamiDPS.round(hps, 1))
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Healing Done"], "|cffffffff" .. NanamiDPS.formatNumber(effectiveVal))
|
||||||
|
tooltip:AddDoubleLine("|cffaaaaaa" .. L["Overheal"],
|
||||||
|
"|cffcc8888+" .. NanamiDPS.formatNumber(entry._sum - effectiveVal))
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Active Time"],
|
||||||
|
"|cffffffff" .. NanamiDPS.formatTime(entry._ctime))
|
||||||
|
|
||||||
|
NanamiDPS.Tooltip:ShowSpellDetail(playerName, entry.spells, entry.effective, entry._sum, effectiveVal, tooltip)
|
||||||
|
end
|
||||||
|
|
||||||
|
function HPS:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %.1f HPS (%s)",
|
||||||
|
i, d.name, d.value, NanamiDPS.formatNumber(d.effectiveHeal)))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("HPS", HPS)
|
||||||
119
Modules/HealingBySpell.lua
Normal file
119
Modules/HealingBySpell.lua
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local HealingBySpell = {}
|
||||||
|
|
||||||
|
function HealingBySpell:GetName()
|
||||||
|
return L["Healing by Spell"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function HealingBySpell:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.healing then return {} end
|
||||||
|
|
||||||
|
local spellTotals = {}
|
||||||
|
local spellEffective = {}
|
||||||
|
for name, entry in pairs(segment.data.healing) do
|
||||||
|
if entry.spells then
|
||||||
|
for spell, amount in pairs(entry.spells) do
|
||||||
|
spellTotals[spell] = (spellTotals[spell] or 0) + amount
|
||||||
|
if entry.effective and entry.effective[spell] then
|
||||||
|
spellEffective[spell] = (spellEffective[spell] or 0) + entry.effective[spell]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for spell, total in pairs(spellTotals) do
|
||||||
|
local effective = spellEffective[spell] or total
|
||||||
|
local overheal = total - effective
|
||||||
|
|
||||||
|
local r, g, b = NanamiDPS.str2rgb(spell)
|
||||||
|
r = r * 0.4 + 0.3
|
||||||
|
g = g * 0.5 + 0.4
|
||||||
|
b = b * 0.4 + 0.3
|
||||||
|
|
||||||
|
local valText = NanamiDPS.formatNumber(effective)
|
||||||
|
if overheal > 0 then
|
||||||
|
valText = valText .. " |cffcc8888+" .. NanamiDPS.formatNumber(overheal)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(bars, {
|
||||||
|
id = spell,
|
||||||
|
name = spell,
|
||||||
|
value = effective,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
valueText = valText,
|
||||||
|
totalHeal = total,
|
||||||
|
effectiveHeal = effective,
|
||||||
|
overheal = overheal,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local grandTotal = 0
|
||||||
|
for _, bar in ipairs(bars) do grandTotal = grandTotal + bar.value end
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = grandTotal > 0 and (bar.value / grandTotal * 100) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function HealingBySpell:GetTooltip(spellName, segment, tooltip)
|
||||||
|
tooltip:AddLine("|cffffd100" .. (spellName or L["Unknown"]))
|
||||||
|
|
||||||
|
if segment and segment.data.healing then
|
||||||
|
local users = {}
|
||||||
|
local totalAmount = 0
|
||||||
|
local totalEffective = 0
|
||||||
|
for name, entry in pairs(segment.data.healing) do
|
||||||
|
if entry.spells and entry.spells[spellName] then
|
||||||
|
local eff = (entry.effective and entry.effective[spellName]) or entry.spells[spellName]
|
||||||
|
table.insert(users, {
|
||||||
|
name = name,
|
||||||
|
amount = entry.spells[spellName],
|
||||||
|
effective = eff,
|
||||||
|
})
|
||||||
|
totalAmount = totalAmount + entry.spells[spellName]
|
||||||
|
totalEffective = totalEffective + eff
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(users, function(a, b) return a.effective > b.effective end)
|
||||||
|
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Healing Done"], "|cffffffff" .. NanamiDPS.formatNumber(totalEffective))
|
||||||
|
if totalAmount - totalEffective > 0 then
|
||||||
|
tooltip:AddDoubleLine("|cffaaaaaa" .. L["Overheal"],
|
||||||
|
"|cffcc8888+" .. NanamiDPS.formatNumber(totalAmount - totalEffective))
|
||||||
|
end
|
||||||
|
|
||||||
|
if table.getn(users) > 0 then
|
||||||
|
tooltip:AddLine(" ")
|
||||||
|
tooltip:AddLine("|cffffd100" .. L["Players"] .. ":")
|
||||||
|
for _, u in ipairs(users) do
|
||||||
|
local oh = u.amount - u.effective
|
||||||
|
local rightStr = NanamiDPS.formatNumber(u.effective)
|
||||||
|
if oh > 0 then
|
||||||
|
rightStr = rightStr .. " |cffcc8888+" .. NanamiDPS.formatNumber(oh)
|
||||||
|
end
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. u.name, "|cffffffff" .. rightStr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function HealingBySpell:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %s (%.1f%%)",
|
||||||
|
i, d.name, NanamiDPS.formatNumber(d.value), d.percent))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("HealingBySpell", HealingBySpell)
|
||||||
83
Modules/HealingDone.lua
Normal file
83
Modules/HealingDone.lua
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local HealingDone = {}
|
||||||
|
|
||||||
|
function HealingDone:GetName()
|
||||||
|
return L["Healing Done"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function HealingDone:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.healing then return {} end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for name, entry in pairs(segment.data.healing) do
|
||||||
|
local class = DataStore:GetClass(name)
|
||||||
|
local r, g, b = NanamiDPS.GetClassColor(class)
|
||||||
|
if not NanamiDPS.validClasses[class] then
|
||||||
|
r, g, b = NanamiDPS.str2rgb(name)
|
||||||
|
r = r * 0.6 + 0.4
|
||||||
|
g = g * 0.6 + 0.4
|
||||||
|
b = b * 0.6 + 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
local effectiveVal = entry._esum or entry._sum
|
||||||
|
table.insert(bars, {
|
||||||
|
id = name,
|
||||||
|
name = name,
|
||||||
|
value = effectiveVal,
|
||||||
|
totalHeal = entry._sum,
|
||||||
|
effectiveHeal = effectiveVal,
|
||||||
|
overheal = entry._sum - effectiveVal,
|
||||||
|
class = class,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local total = 0
|
||||||
|
for _, bar in ipairs(bars) do total = total + bar.value end
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = total > 0 and (bar.value / total * 100) or 0
|
||||||
|
bar.valueText = NanamiDPS.formatNumber(bar.effectiveHeal)
|
||||||
|
if bar.overheal > 0 then
|
||||||
|
bar.valueText = bar.valueText .. " |cffcc8888+" .. NanamiDPS.formatNumber(bar.overheal)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function HealingDone:GetTooltip(playerName, segment, tooltip)
|
||||||
|
if not segment or not segment.data.healing[playerName] then return end
|
||||||
|
local entry = segment.data.healing[playerName]
|
||||||
|
local effectiveVal = entry._esum or entry._sum
|
||||||
|
local overhealVal = entry._sum - effectiveVal
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffffd100" .. playerName)
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Healing Done"], "|cffffffff" .. NanamiDPS.formatNumber(effectiveVal))
|
||||||
|
tooltip:AddDoubleLine("|cffaaaaaa" .. L["Overheal"], "|cffcc8888+" .. NanamiDPS.formatNumber(overhealVal))
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["HPS"],
|
||||||
|
"|cffffffff" .. NanamiDPS.round(effectiveVal / math.max(entry._ctime, 1), 1))
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Active Time"],
|
||||||
|
"|cffffffff" .. NanamiDPS.formatTime(entry._ctime))
|
||||||
|
|
||||||
|
NanamiDPS.Tooltip:ShowSpellDetail(playerName, entry.spells, entry.effective, entry._sum, effectiveVal, tooltip)
|
||||||
|
end
|
||||||
|
|
||||||
|
function HealingDone:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
local oh = d.overheal > 0 and (" [+" .. NanamiDPS.formatNumber(d.overheal) .. "]") or ""
|
||||||
|
table.insert(lines, string.format("%d. %s - %s%s (%.1f%%)",
|
||||||
|
i, d.name, NanamiDPS.formatNumber(d.effectiveHeal), oh, d.percent))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("HealingDone", HealingDone)
|
||||||
78
Modules/Interrupts.lua
Normal file
78
Modules/Interrupts.lua
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local Interrupts = {}
|
||||||
|
|
||||||
|
function Interrupts:GetName()
|
||||||
|
return L["Interrupts"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Interrupts:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.interrupts then return {} end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for name, entry in pairs(segment.data.interrupts) do
|
||||||
|
local class = DataStore:GetClass(name)
|
||||||
|
local r, g, b = NanamiDPS.GetClassColor(class)
|
||||||
|
if not NanamiDPS.validClasses[class] then
|
||||||
|
r, g, b = NanamiDPS.str2rgb(name)
|
||||||
|
r = r * 0.6 + 0.4
|
||||||
|
g = g * 0.6 + 0.4
|
||||||
|
b = b * 0.6 + 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(bars, {
|
||||||
|
id = name,
|
||||||
|
name = name,
|
||||||
|
value = entry._sum or 0,
|
||||||
|
class = class,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
valueText = tostring(entry._sum or 0),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local total = 0
|
||||||
|
for _, bar in ipairs(bars) do total = total + bar.value end
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = total > 0 and (bar.value / total * 100) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function Interrupts:GetTooltip(playerName, segment, tooltip)
|
||||||
|
if not segment or not segment.data.interrupts[playerName] then return end
|
||||||
|
local entry = segment.data.interrupts[playerName]
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffffd100" .. playerName .. " - " .. L["Interrupts"])
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Interrupts"], "|cffffffff" .. (entry._sum or 0))
|
||||||
|
|
||||||
|
if entry.spells then
|
||||||
|
tooltip:AddLine(" ")
|
||||||
|
tooltip:AddLine("|cffffd100" .. L["Details"] .. ":")
|
||||||
|
local sorted = {}
|
||||||
|
for spell, count in pairs(entry.spells) do
|
||||||
|
table.insert(sorted, { spell = spell, count = count })
|
||||||
|
end
|
||||||
|
table.sort(sorted, function(a, b) return a.count > b.count end)
|
||||||
|
for _, s in ipairs(sorted) do
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. s.spell, "|cffffffff" .. s.count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Interrupts:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %d interrupts", i, d.name, d.value))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("Interrupts", Interrupts)
|
||||||
100
Modules/Overhealing.lua
Normal file
100
Modules/Overhealing.lua
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local Overhealing = {}
|
||||||
|
|
||||||
|
function Overhealing:GetName()
|
||||||
|
return L["Overhealing"]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Overhealing:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.healing then return {} end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for name, entry in pairs(segment.data.healing) do
|
||||||
|
local class = DataStore:GetClass(name)
|
||||||
|
local r, g, b = NanamiDPS.GetClassColor(class)
|
||||||
|
if not NanamiDPS.validClasses[class] then
|
||||||
|
r, g, b = NanamiDPS.str2rgb(name)
|
||||||
|
r = r * 0.6 + 0.4
|
||||||
|
g = g * 0.6 + 0.4
|
||||||
|
b = b * 0.6 + 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
local effectiveVal = entry._esum or entry._sum
|
||||||
|
local overhealVal = entry._sum - effectiveVal
|
||||||
|
local overhealPct = entry._sum > 0 and (overhealVal / entry._sum * 100) or 0
|
||||||
|
|
||||||
|
if overhealVal > 0 then
|
||||||
|
table.insert(bars, {
|
||||||
|
id = name,
|
||||||
|
name = name,
|
||||||
|
value = overhealVal,
|
||||||
|
class = class,
|
||||||
|
r = r * 0.7 + 0.3, g = g * 0.5, b = b * 0.5,
|
||||||
|
valueText = NanamiDPS.formatNumber(overhealVal) .. " (" .. NanamiDPS.round(overhealPct, 1) .. "%)",
|
||||||
|
overhealPct = overhealPct,
|
||||||
|
totalHeal = entry._sum,
|
||||||
|
effectiveHeal = effectiveVal,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local total = 0
|
||||||
|
for _, bar in ipairs(bars) do total = total + bar.value end
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = total > 0 and (bar.value / total * 100) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function Overhealing:GetTooltip(playerName, segment, tooltip)
|
||||||
|
if not segment or not segment.data.healing[playerName] then return end
|
||||||
|
local entry = segment.data.healing[playerName]
|
||||||
|
local effectiveVal = entry._esum or entry._sum
|
||||||
|
local overhealVal = entry._sum - effectiveVal
|
||||||
|
local overhealPct = entry._sum > 0 and NanamiDPS.round(overhealVal / entry._sum * 100, 1) or 0
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffffd100" .. playerName)
|
||||||
|
tooltip:AddDoubleLine("|cffcc8888" .. L["Overhealing"], "|cffcc8888" .. NanamiDPS.formatNumber(overhealVal))
|
||||||
|
tooltip:AddDoubleLine("|cffcc8888" .. L["Overheal %"], "|cffcc8888" .. overhealPct .. "%")
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Healing Done"], "|cffffffff" .. NanamiDPS.formatNumber(entry._sum))
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Effective"], "|cffffffff" .. NanamiDPS.formatNumber(effectiveVal))
|
||||||
|
|
||||||
|
if entry.spells and entry.effective then
|
||||||
|
tooltip:AddLine(" ")
|
||||||
|
tooltip:AddLine("|cffffd100" .. L["Details"] .. ":")
|
||||||
|
local sorted = {}
|
||||||
|
for spell, amount in pairs(entry.spells) do
|
||||||
|
table.insert(sorted, { spell = spell, amount = amount })
|
||||||
|
end
|
||||||
|
table.sort(sorted, function(a, b) return a.amount > b.amount end)
|
||||||
|
for _, s in ipairs(sorted) do
|
||||||
|
local eff = entry.effective[s.spell] or s.amount
|
||||||
|
local oh = s.amount - eff
|
||||||
|
if oh > 0 then
|
||||||
|
local pct = s.amount > 0 and NanamiDPS.round(oh / s.amount * 100, 1) or 0
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. s.spell,
|
||||||
|
"|cffcc8888+" .. NanamiDPS.formatNumber(oh) .. " (" .. pct .. "%)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Overhealing:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %s (%.1f%%)",
|
||||||
|
i, d.name, NanamiDPS.formatNumber(d.value), d.overhealPct))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("Overhealing", Overhealing)
|
||||||
110
Modules/ThreatEstimate.lua
Normal file
110
Modules/ThreatEstimate.lua
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local ThreatEstimate = {}
|
||||||
|
|
||||||
|
function ThreatEstimate:GetName()
|
||||||
|
return L["Threat (Est.)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ResolvePetOwner(name)
|
||||||
|
local stored = DataStore:GetClass(name)
|
||||||
|
if stored and not NanamiDPS.validClasses[stored] and stored ~= "__other__" then
|
||||||
|
local ownerClass = DataStore:GetClass(stored)
|
||||||
|
if ownerClass and NanamiDPS.validClasses[ownerClass] then
|
||||||
|
return stored, ownerClass
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function ThreatEstimate:GetBars(segment)
|
||||||
|
if not segment or not segment.data or not segment.data.threat then return {} end
|
||||||
|
|
||||||
|
local bars = {}
|
||||||
|
for name, entry in pairs(segment.data.threat) do
|
||||||
|
local class = DataStore:GetClass(name)
|
||||||
|
local r, g, b = NanamiDPS.GetClassColor(class)
|
||||||
|
local displayName = name
|
||||||
|
local ownerName, ownerClass = ResolvePetOwner(name)
|
||||||
|
|
||||||
|
if NanamiDPS.validClasses[class] then
|
||||||
|
-- player: use class color as-is
|
||||||
|
elseif ownerName and ownerClass then
|
||||||
|
r, g, b = NanamiDPS.GetClassColor(ownerClass)
|
||||||
|
r, g, b = r * 0.7, g * 0.7, b * 0.7
|
||||||
|
displayName = name .. " <" .. ownerName .. ">"
|
||||||
|
else
|
||||||
|
r, g, b = NanamiDPS.str2rgb(name)
|
||||||
|
r = r * 0.6 + 0.4
|
||||||
|
g = g * 0.6 + 0.4
|
||||||
|
b = b * 0.6 + 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(bars, {
|
||||||
|
id = name,
|
||||||
|
name = displayName,
|
||||||
|
value = entry._sum or 0,
|
||||||
|
class = ownerClass or class,
|
||||||
|
r = r, g = g, b = b,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(bars, function(a, b) return a.value > b.value end)
|
||||||
|
|
||||||
|
local best = bars[1] and bars[1].value or 0
|
||||||
|
for _, bar in ipairs(bars) do
|
||||||
|
bar.percent = best > 0 and (bar.value / best * 100) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return bars
|
||||||
|
end
|
||||||
|
|
||||||
|
function ThreatEstimate:GetTooltip(playerName, segment, tooltip)
|
||||||
|
if not segment or not segment.data.threat[playerName] then return end
|
||||||
|
local entry = segment.data.threat[playerName]
|
||||||
|
local ownerName, ownerClass = ResolvePetOwner(playerName)
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffffd100" .. playerName)
|
||||||
|
|
||||||
|
if ownerName then
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Owner"], "|cffffffff" .. ownerName)
|
||||||
|
end
|
||||||
|
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Threat (Est.)"], "|cffffffff" .. NanamiDPS.formatNumber(entry._sum))
|
||||||
|
|
||||||
|
tooltip:AddLine(" ")
|
||||||
|
|
||||||
|
if ownerName then
|
||||||
|
tooltip:AddLine("|cffaaaaaa" .. L["Threat Note"])
|
||||||
|
else
|
||||||
|
local dmgEntry = segment.data.damage[playerName]
|
||||||
|
local healEntry = segment.data.healing[playerName]
|
||||||
|
|
||||||
|
if dmgEntry then
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Damage Done"],
|
||||||
|
"|cffffffff" .. NanamiDPS.formatNumber(dmgEntry._sum))
|
||||||
|
end
|
||||||
|
if healEntry then
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. L["Healing Done"],
|
||||||
|
"|cffffffff" .. NanamiDPS.formatNumber(healEntry._sum) .. " (x0.5)")
|
||||||
|
end
|
||||||
|
|
||||||
|
tooltip:AddLine("|cffaaaaaa" .. L["Threat Note"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ThreatEstimate:GetReportLines(segment, count)
|
||||||
|
local bars = self:GetBars(segment)
|
||||||
|
local lines = {}
|
||||||
|
count = count or 5
|
||||||
|
for i = 1, math.min(count, table.getn(bars)) do
|
||||||
|
local d = bars[i]
|
||||||
|
table.insert(lines, string.format("%d. %s - %s (%.1f%%)",
|
||||||
|
i, d.name, NanamiDPS.formatNumber(d.value), d.percent))
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterModule("ThreatEstimate", ThreatEstimate)
|
||||||
34
Nanami-DPS.toc
Normal file
34
Nanami-DPS.toc
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
## Interface: 11200
|
||||||
|
## Title: |cffFF88AANanami|r DPS
|
||||||
|
## Author: Nanami
|
||||||
|
## Notes: Comprehensive combat data meter with Nanami UI integration
|
||||||
|
## Notes-zhCN: Nanami 综合战斗数据监测插件
|
||||||
|
## Version: 1.0.0
|
||||||
|
## Dependencies: Nanami-UI
|
||||||
|
## SavedVariablesPerCharacter: NanamiDPS_DB
|
||||||
|
|
||||||
|
Core.lua
|
||||||
|
Locale.lua
|
||||||
|
Utils.lua
|
||||||
|
DataStore.lua
|
||||||
|
Parser.lua
|
||||||
|
ParserVanilla.lua
|
||||||
|
Modules\DamageDone.lua
|
||||||
|
Modules\DamageTaken.lua
|
||||||
|
Modules\DPS.lua
|
||||||
|
Modules\HealingDone.lua
|
||||||
|
Modules\HPS.lua
|
||||||
|
Modules\Overhealing.lua
|
||||||
|
Modules\Deaths.lua
|
||||||
|
Modules\Dispels.lua
|
||||||
|
Modules\Interrupts.lua
|
||||||
|
Modules\ThreatEstimate.lua
|
||||||
|
Modules\Activity.lua
|
||||||
|
Modules\EnemyDamageDone.lua
|
||||||
|
Modules\DamageBySpell.lua
|
||||||
|
Modules\HealingBySpell.lua
|
||||||
|
BarDisplay.lua
|
||||||
|
Tooltip.lua
|
||||||
|
Window.lua
|
||||||
|
DetailView.lua
|
||||||
|
Options.lua
|
||||||
679
Options.lua
Normal file
679
Options.lua
Normal file
@@ -0,0 +1,679 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local Window = NanamiDPS.Window
|
||||||
|
|
||||||
|
local Options = {}
|
||||||
|
NanamiDPS.Options = Options
|
||||||
|
|
||||||
|
local optionsFrame = nil
|
||||||
|
local activeTabPage = nil
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- UI builder helpers
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
local function ApplyThemeColors(frame, category)
|
||||||
|
local A = SFrames.ActiveTheme
|
||||||
|
if not A then return end
|
||||||
|
if category == "header" and A.headerBg then
|
||||||
|
frame:SetBackdropColor(A.headerBg[1], A.headerBg[2], A.headerBg[3], A.headerBg[4] or 0.98)
|
||||||
|
elseif category == "panel" and A.panelBg then
|
||||||
|
frame:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], A.panelBg[4] or 0.9)
|
||||||
|
elseif category == "button" and A.buttonBg then
|
||||||
|
frame:SetBackdropColor(A.buttonBg[1], A.buttonBg[2], A.buttonBg[3], A.buttonBg[4] or 0.9)
|
||||||
|
if A.buttonBorder then
|
||||||
|
frame:SetBackdropBorderColor(A.buttonBorder[1], A.buttonBorder[2], A.buttonBorder[3], A.buttonBorder[4] or 0.8)
|
||||||
|
end
|
||||||
|
elseif category == "section" and A.sectionBg then
|
||||||
|
frame:SetBackdropColor(A.sectionBg[1], A.sectionBg[2], A.sectionBg[3], 0.5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CreateSectionHeader(parent, text, yOffset)
|
||||||
|
local A = SFrames.ActiveTheme
|
||||||
|
local header = CreateFrame("Frame", nil, parent)
|
||||||
|
header:SetHeight(20)
|
||||||
|
header:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, yOffset)
|
||||||
|
header:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, yOffset)
|
||||||
|
header:SetBackdrop({
|
||||||
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||||
|
tile = false, edgeSize = 0,
|
||||||
|
})
|
||||||
|
if A and A.accent then
|
||||||
|
header:SetBackdropColor(A.accent[1], A.accent[2], A.accent[3], 0.12)
|
||||||
|
else
|
||||||
|
header:SetBackdropColor(0.3, 0.3, 0.3, 0.12)
|
||||||
|
end
|
||||||
|
|
||||||
|
local accent = header:CreateTexture(nil, "ARTWORK")
|
||||||
|
accent:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||||
|
accent:SetWidth(3)
|
||||||
|
accent:SetHeight(14)
|
||||||
|
accent:SetPoint("LEFT", header, "LEFT", 2, 0)
|
||||||
|
if A and A.accent then
|
||||||
|
accent:SetVertexColor(A.accent[1], A.accent[2], A.accent[3], 0.9)
|
||||||
|
else
|
||||||
|
accent:SetVertexColor(0.7, 0.7, 0.7, 0.9)
|
||||||
|
end
|
||||||
|
|
||||||
|
local label = SFrames:CreateFontString(header, 10, "LEFT")
|
||||||
|
label:SetPoint("LEFT", accent, "RIGHT", 6, 0)
|
||||||
|
if A and A.sectionTitle then
|
||||||
|
label:SetTextColor(A.sectionTitle[1], A.sectionTitle[2], A.sectionTitle[3])
|
||||||
|
end
|
||||||
|
label:SetText(text)
|
||||||
|
|
||||||
|
return header
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CreateStyledCheckbox(parent, label, x, y, getValue, setValue)
|
||||||
|
local A = SFrames.ActiveTheme
|
||||||
|
|
||||||
|
local container = CreateFrame("Frame", nil, parent)
|
||||||
|
container:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||||||
|
container:SetWidth(240)
|
||||||
|
container:SetHeight(20)
|
||||||
|
container:EnableMouse(true)
|
||||||
|
|
||||||
|
local check = CreateFrame("CheckButton", nil, container)
|
||||||
|
check:SetWidth(14)
|
||||||
|
check:SetHeight(14)
|
||||||
|
check:SetPoint("LEFT", container, "LEFT", 0, 0)
|
||||||
|
|
||||||
|
check:SetBackdrop({
|
||||||
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||||
|
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||||||
|
tile = false, edgeSize = 1,
|
||||||
|
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||||||
|
})
|
||||||
|
if A and A.checkBg then
|
||||||
|
check:SetBackdropColor(A.checkBg[1], A.checkBg[2], A.checkBg[3], A.checkBg[4] or 0.9)
|
||||||
|
else
|
||||||
|
check:SetBackdropColor(0.12, 0.06, 0.09, 0.9)
|
||||||
|
end
|
||||||
|
if A and A.checkBorder then
|
||||||
|
check:SetBackdropBorderColor(A.checkBorder[1], A.checkBorder[2], A.checkBorder[3], A.checkBorder[4] or 0.8)
|
||||||
|
else
|
||||||
|
check:SetBackdropBorderColor(0.45, 0.3, 0.38, 0.8)
|
||||||
|
end
|
||||||
|
|
||||||
|
local checkMark = check:CreateTexture(nil, "OVERLAY")
|
||||||
|
checkMark:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||||
|
checkMark:SetWidth(8)
|
||||||
|
checkMark:SetHeight(8)
|
||||||
|
checkMark:SetPoint("CENTER", 0, 0)
|
||||||
|
if A and A.checkFill then
|
||||||
|
checkMark:SetVertexColor(A.checkFill[1], A.checkFill[2], A.checkFill[3], A.checkFill[4] or 1)
|
||||||
|
else
|
||||||
|
checkMark:SetVertexColor(0.7, 0.7, 0.7, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local checked = getValue() and true or false
|
||||||
|
check:SetChecked(checked)
|
||||||
|
checkMark:SetAlpha(checked and 1 or 0)
|
||||||
|
|
||||||
|
local text = SFrames:CreateFontString(container, 10, "LEFT")
|
||||||
|
text:SetPoint("LEFT", check, "RIGHT", 6, 0)
|
||||||
|
text:SetText(label)
|
||||||
|
if A and A.text then
|
||||||
|
text:SetTextColor(A.text[1], A.text[2], A.text[3])
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ToggleCheck()
|
||||||
|
local val = not check.isChecked
|
||||||
|
check.isChecked = val
|
||||||
|
check:SetChecked(val)
|
||||||
|
checkMark:SetAlpha(val and 1 or 0)
|
||||||
|
setValue(val)
|
||||||
|
Window:RefreshAll(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
check.isChecked = checked
|
||||||
|
check:SetScript("OnClick", ToggleCheck)
|
||||||
|
container:SetScript("OnMouseUp", ToggleCheck)
|
||||||
|
|
||||||
|
container:SetScript("OnEnter", function()
|
||||||
|
if A and A.checkHoverBorder then
|
||||||
|
check:SetBackdropBorderColor(A.checkHoverBorder[1], A.checkHoverBorder[2], A.checkHoverBorder[3], A.checkHoverBorder[4] or 0.9)
|
||||||
|
else
|
||||||
|
check:SetBackdropBorderColor(0.7, 0.7, 0.7, 0.9)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
container:SetScript("OnLeave", function()
|
||||||
|
if A and A.checkBorder then
|
||||||
|
check:SetBackdropBorderColor(A.checkBorder[1], A.checkBorder[2], A.checkBorder[3], A.checkBorder[4] or 0.8)
|
||||||
|
else
|
||||||
|
check:SetBackdropBorderColor(0.45, 0.3, 0.38, 0.8)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
return container
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CreateStyledSlider(parent, label, x, y, minVal, maxVal, step, getValue, setValue, formatFn)
|
||||||
|
local A = SFrames.ActiveTheme
|
||||||
|
|
||||||
|
local container = CreateFrame("Frame", nil, parent)
|
||||||
|
container:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||||||
|
container:SetWidth(260)
|
||||||
|
container:SetHeight(36)
|
||||||
|
|
||||||
|
local titleText = SFrames:CreateFontString(container, 9, "LEFT")
|
||||||
|
titleText:SetPoint("TOPLEFT", container, "TOPLEFT", 0, 0)
|
||||||
|
titleText:SetText(label)
|
||||||
|
if A and A.text then
|
||||||
|
titleText:SetTextColor(A.text[1], A.text[2], A.text[3])
|
||||||
|
end
|
||||||
|
|
||||||
|
local slider = CreateFrame("Slider", nil, container)
|
||||||
|
slider:SetPoint("TOPLEFT", container, "TOPLEFT", 0, -14)
|
||||||
|
slider:SetWidth(200)
|
||||||
|
slider:SetHeight(12)
|
||||||
|
slider:SetOrientation("HORIZONTAL")
|
||||||
|
slider:SetMinMaxValues(minVal, maxVal)
|
||||||
|
slider:SetValueStep(step)
|
||||||
|
slider:SetValue(getValue())
|
||||||
|
slider:EnableMouseWheel(1)
|
||||||
|
|
||||||
|
slider:SetBackdrop({
|
||||||
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||||
|
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||||||
|
tile = false, edgeSize = 1,
|
||||||
|
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||||||
|
})
|
||||||
|
if A and A.sliderTrack then
|
||||||
|
slider:SetBackdropColor(A.sliderTrack[1], A.sliderTrack[2], A.sliderTrack[3], A.sliderTrack[4] or 0.9)
|
||||||
|
else
|
||||||
|
slider:SetBackdropColor(0.12, 0.06, 0.09, 0.9)
|
||||||
|
end
|
||||||
|
if A and A.buttonBorder then
|
||||||
|
slider:SetBackdropBorderColor(A.buttonBorder[1], A.buttonBorder[2], A.buttonBorder[3], A.buttonBorder[4] or 0.8)
|
||||||
|
else
|
||||||
|
slider:SetBackdropBorderColor(0.35, 0.25, 0.3, 0.8)
|
||||||
|
end
|
||||||
|
|
||||||
|
slider:SetThumbTexture("Interface\\Buttons\\WHITE8X8")
|
||||||
|
local thumb = slider:GetThumbTexture()
|
||||||
|
thumb:SetWidth(8)
|
||||||
|
thumb:SetHeight(12)
|
||||||
|
if A and A.sliderThumb then
|
||||||
|
thumb:SetVertexColor(A.sliderThumb[1], A.sliderThumb[2], A.sliderThumb[3], A.sliderThumb[4] or 0.95)
|
||||||
|
else
|
||||||
|
thumb:SetVertexColor(0.7, 0.7, 0.7, 0.95)
|
||||||
|
end
|
||||||
|
|
||||||
|
local valText = SFrames:CreateFontString(container, 10, "LEFT")
|
||||||
|
valText:SetPoint("LEFT", slider, "RIGHT", 10, 0)
|
||||||
|
valText:SetWidth(40)
|
||||||
|
|
||||||
|
local function UpdateDisplay(val)
|
||||||
|
if formatFn then
|
||||||
|
valText:SetText(formatFn(val))
|
||||||
|
else
|
||||||
|
valText:SetText(tostring(math.floor(val + 0.5)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
UpdateDisplay(getValue())
|
||||||
|
if A and A.accent then
|
||||||
|
valText:SetTextColor(A.accent[1], A.accent[2], A.accent[3], 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
slider:SetScript("OnValueChanged", function()
|
||||||
|
local val = this:GetValue()
|
||||||
|
UpdateDisplay(val)
|
||||||
|
setValue(val)
|
||||||
|
end)
|
||||||
|
|
||||||
|
slider:SetScript("OnMouseUp", function()
|
||||||
|
Window:RefreshAll(true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
slider:SetScript("OnMouseWheel", function()
|
||||||
|
local val = slider:GetValue()
|
||||||
|
if arg1 > 0 then
|
||||||
|
slider:SetValue(math.min(val + step, maxVal))
|
||||||
|
else
|
||||||
|
slider:SetValue(math.max(val - step, minVal))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
return container
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CreateActionButton(parent, text, x, y, width, onClick)
|
||||||
|
local A = SFrames.ActiveTheme
|
||||||
|
|
||||||
|
local btn = CreateFrame("Button", nil, parent)
|
||||||
|
btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||||||
|
btn:SetWidth(width or 120)
|
||||||
|
btn:SetHeight(22)
|
||||||
|
|
||||||
|
btn:SetBackdrop({
|
||||||
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||||
|
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||||||
|
tile = false, edgeSize = 1,
|
||||||
|
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||||||
|
})
|
||||||
|
if A and A.buttonBg then
|
||||||
|
btn:SetBackdropColor(A.buttonBg[1], A.buttonBg[2], A.buttonBg[3], A.buttonBg[4] or 0.9)
|
||||||
|
else
|
||||||
|
btn:SetBackdropColor(0.2, 0.1, 0.15, 0.9)
|
||||||
|
end
|
||||||
|
if A and A.buttonBorder then
|
||||||
|
btn:SetBackdropBorderColor(A.buttonBorder[1], A.buttonBorder[2], A.buttonBorder[3], A.buttonBorder[4] or 0.8)
|
||||||
|
else
|
||||||
|
btn:SetBackdropBorderColor(0.5, 0.35, 0.42, 0.8)
|
||||||
|
end
|
||||||
|
|
||||||
|
local label = SFrames:CreateFontString(btn, 10, "CENTER")
|
||||||
|
label:SetAllPoints()
|
||||||
|
label:SetText(text)
|
||||||
|
|
||||||
|
btn:SetScript("OnClick", onClick)
|
||||||
|
btn:SetScript("OnEnter", function()
|
||||||
|
if A and A.buttonHoverBg then
|
||||||
|
this:SetBackdropColor(A.buttonHoverBg[1], A.buttonHoverBg[2], A.buttonHoverBg[3], A.buttonHoverBg[4] or 0.3)
|
||||||
|
else
|
||||||
|
this:SetBackdropColor(0.3, 0.3, 0.3, 0.2)
|
||||||
|
end
|
||||||
|
if A and A.accent then
|
||||||
|
this:SetBackdropBorderColor(A.accent[1], A.accent[2], A.accent[3], 0.9)
|
||||||
|
else
|
||||||
|
this:SetBackdropBorderColor(0.7, 0.7, 0.7, 0.9)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
btn:SetScript("OnLeave", function()
|
||||||
|
if A and A.buttonBg then
|
||||||
|
this:SetBackdropColor(A.buttonBg[1], A.buttonBg[2], A.buttonBg[3], A.buttonBg[4] or 0.9)
|
||||||
|
else
|
||||||
|
this:SetBackdropColor(0.2, 0.1, 0.15, 0.9)
|
||||||
|
end
|
||||||
|
if A and A.buttonBorder then
|
||||||
|
this:SetBackdropBorderColor(A.buttonBorder[1], A.buttonBorder[2], A.buttonBorder[3], A.buttonBorder[4] or 0.8)
|
||||||
|
else
|
||||||
|
this:SetBackdropBorderColor(0.5, 0.35, 0.42, 0.8)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
return btn
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Tab system
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
local function CreateTab(parent, tabIndex, text, tabBar, contentParent, totalTabs)
|
||||||
|
local A = SFrames.ActiveTheme
|
||||||
|
local tabWidth = math.floor((parent:GetWidth() - 2) / totalTabs)
|
||||||
|
|
||||||
|
local tab = CreateFrame("Button", nil, tabBar)
|
||||||
|
tab:SetWidth(tabWidth)
|
||||||
|
tab:SetHeight(20)
|
||||||
|
|
||||||
|
if tabIndex == 1 then
|
||||||
|
tab:SetPoint("TOPLEFT", tabBar, "TOPLEFT", 1, 0)
|
||||||
|
else
|
||||||
|
tab:SetPoint("LEFT", tabBar.tabs[tabIndex - 1], "RIGHT", 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
tab:SetBackdrop({
|
||||||
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||||
|
tile = false, edgeSize = 0,
|
||||||
|
})
|
||||||
|
if A and A.tabBg then
|
||||||
|
tab:SetBackdropColor(A.tabBg[1], A.tabBg[2], A.tabBg[3], A.tabBg[4] or 0.6)
|
||||||
|
else
|
||||||
|
tab:SetBackdropColor(0.12, 0.06, 0.09, 0.6)
|
||||||
|
end
|
||||||
|
|
||||||
|
local label = SFrames:CreateFontString(tab, 9, "CENTER")
|
||||||
|
label:SetAllPoints()
|
||||||
|
label:SetText(text)
|
||||||
|
if A and A.tabText then
|
||||||
|
label:SetTextColor(A.tabText[1], A.tabText[2], A.tabText[3])
|
||||||
|
end
|
||||||
|
tab.label = label
|
||||||
|
|
||||||
|
tab.tabIndex = tabIndex
|
||||||
|
tab.page = CreateFrame("Frame", nil, contentParent)
|
||||||
|
tab.page:SetAllPoints(contentParent)
|
||||||
|
tab.page:Hide()
|
||||||
|
|
||||||
|
tab:SetScript("OnClick", function()
|
||||||
|
Options:SwitchTab(parent, tabIndex)
|
||||||
|
end)
|
||||||
|
|
||||||
|
tab:SetScript("OnEnter", function()
|
||||||
|
if activeTabPage ~= this.page then
|
||||||
|
if A and A.buttonHoverBg then
|
||||||
|
this:SetBackdropColor(A.buttonHoverBg[1], A.buttonHoverBg[2], A.buttonHoverBg[3], 0.3)
|
||||||
|
else
|
||||||
|
this:SetBackdropColor(0.3, 0.3, 0.3, 0.15)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
tab:SetScript("OnLeave", function()
|
||||||
|
if activeTabPage ~= this.page then
|
||||||
|
if A and A.tabBg then
|
||||||
|
this:SetBackdropColor(A.tabBg[1], A.tabBg[2], A.tabBg[3], A.tabBg[4] or 0.6)
|
||||||
|
else
|
||||||
|
this:SetBackdropColor(0.12, 0.06, 0.09, 0.6)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
return tab
|
||||||
|
end
|
||||||
|
|
||||||
|
function Options:SwitchTab(frame, tabIndex)
|
||||||
|
local A = SFrames.ActiveTheme
|
||||||
|
local tabBar = frame.tabBar
|
||||||
|
if not tabBar or not tabBar.tabs then return end
|
||||||
|
|
||||||
|
for _, tab in ipairs(tabBar.tabs) do
|
||||||
|
if A and A.tabBg then
|
||||||
|
tab:SetBackdropColor(A.tabBg[1], A.tabBg[2], A.tabBg[3], A.tabBg[4] or 0.6)
|
||||||
|
else
|
||||||
|
tab:SetBackdropColor(0.12, 0.06, 0.09, 0.6)
|
||||||
|
end
|
||||||
|
if A and A.tabText then
|
||||||
|
tab.label:SetTextColor(A.tabText[1], A.tabText[2], A.tabText[3])
|
||||||
|
else
|
||||||
|
tab.label:SetTextColor(0.7, 0.7, 0.7)
|
||||||
|
end
|
||||||
|
tab.page:Hide()
|
||||||
|
end
|
||||||
|
|
||||||
|
local active = tabBar.tabs[tabIndex]
|
||||||
|
if active then
|
||||||
|
if A and A.tabActiveBg then
|
||||||
|
active:SetBackdropColor(A.tabActiveBg[1], A.tabActiveBg[2], A.tabActiveBg[3], A.tabActiveBg[4] or 0.3)
|
||||||
|
else
|
||||||
|
active:SetBackdropColor(0.3, 0.3, 0.3, 0.2)
|
||||||
|
end
|
||||||
|
if A and A.tabActiveText then
|
||||||
|
active.label:SetTextColor(A.tabActiveText[1], A.tabActiveText[2], A.tabActiveText[3])
|
||||||
|
else
|
||||||
|
active.label:SetTextColor(1, 0.85, 0.9)
|
||||||
|
end
|
||||||
|
active.page:Show()
|
||||||
|
activeTabPage = active.page
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Build the options panel
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
function Options:CreatePanel()
|
||||||
|
if optionsFrame then return optionsFrame end
|
||||||
|
|
||||||
|
local A = SFrames.ActiveTheme
|
||||||
|
|
||||||
|
optionsFrame = CreateFrame("Frame", "NanamiDPSOptions", UIParent)
|
||||||
|
optionsFrame:SetWidth(340)
|
||||||
|
optionsFrame:SetHeight(460)
|
||||||
|
optionsFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
||||||
|
optionsFrame:SetMovable(true)
|
||||||
|
optionsFrame:EnableMouse(true)
|
||||||
|
optionsFrame:SetClampedToScreen(true)
|
||||||
|
optionsFrame:SetFrameStrata("DIALOG")
|
||||||
|
|
||||||
|
SFrames:CreateRoundBackdrop(optionsFrame)
|
||||||
|
|
||||||
|
-- Title bar
|
||||||
|
local titleBar = CreateFrame("Frame", nil, optionsFrame)
|
||||||
|
titleBar:SetHeight(24)
|
||||||
|
titleBar:SetPoint("TOPLEFT", optionsFrame, "TOPLEFT", 4, -4)
|
||||||
|
titleBar:SetPoint("TOPRIGHT", optionsFrame, "TOPRIGHT", -4, -4)
|
||||||
|
titleBar:SetBackdrop({
|
||||||
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||||
|
tile = false, edgeSize = 0,
|
||||||
|
})
|
||||||
|
ApplyThemeColors(titleBar, "header")
|
||||||
|
if not (A and A.headerBg) then
|
||||||
|
titleBar:SetBackdropColor(0.06, 0.06, 0.08, 0.98)
|
||||||
|
end
|
||||||
|
titleBar:EnableMouse(true)
|
||||||
|
titleBar:RegisterForDrag("LeftButton")
|
||||||
|
titleBar:SetScript("OnDragStart", function() optionsFrame:StartMoving() end)
|
||||||
|
titleBar:SetScript("OnDragStop", function() optionsFrame:StopMovingOrSizing() end)
|
||||||
|
|
||||||
|
local titleText = SFrames:CreateFontString(titleBar, 11, "CENTER")
|
||||||
|
titleText:SetPoint("LEFT", titleBar, "LEFT", 8, 0)
|
||||||
|
titleText:SetPoint("RIGHT", titleBar, "RIGHT", -28, 0)
|
||||||
|
local accentHex = (A and A.accentHex) or "ffFF88AA"
|
||||||
|
titleText:SetText("|c" .. accentHex .. "Nanami|r DPS - " .. L["Settings"])
|
||||||
|
|
||||||
|
local verText = SFrames:CreateFontString(titleBar, 8, "RIGHT")
|
||||||
|
verText:SetPoint("RIGHT", titleBar, "RIGHT", -24, -4)
|
||||||
|
verText:SetText("|cff888888v" .. NanamiDPS.version)
|
||||||
|
|
||||||
|
local closeBtn = CreateFrame("Button", nil, titleBar)
|
||||||
|
closeBtn:SetWidth(18)
|
||||||
|
closeBtn:SetHeight(18)
|
||||||
|
closeBtn:SetPoint("RIGHT", titleBar, "RIGHT", -3, 0)
|
||||||
|
local closeIcon = SFrames:CreateIcon(closeBtn, "close", 12)
|
||||||
|
closeIcon:SetPoint("CENTER", 0, 0)
|
||||||
|
closeBtn:SetScript("OnClick", function() optionsFrame:Hide() end)
|
||||||
|
closeBtn:SetScript("OnEnter", function()
|
||||||
|
closeIcon:SetVertexColor(1, 0.3, 0.3)
|
||||||
|
end)
|
||||||
|
closeBtn:SetScript("OnLeave", function()
|
||||||
|
closeIcon:SetVertexColor(1, 1, 1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Tab bar
|
||||||
|
local tabBar = CreateFrame("Frame", nil, optionsFrame)
|
||||||
|
tabBar:SetHeight(20)
|
||||||
|
tabBar:SetPoint("TOPLEFT", titleBar, "BOTTOMLEFT", 0, 0)
|
||||||
|
tabBar:SetPoint("TOPRIGHT", titleBar, "BOTTOMRIGHT", 0, 0)
|
||||||
|
tabBar:SetBackdrop({
|
||||||
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||||
|
tile = false, edgeSize = 0,
|
||||||
|
})
|
||||||
|
if A and A.sectionBg then
|
||||||
|
tabBar:SetBackdropColor(A.sectionBg[1], A.sectionBg[2], A.sectionBg[3], A.sectionBg[4] or 0.95)
|
||||||
|
else
|
||||||
|
tabBar:SetBackdropColor(0.05, 0.03, 0.04, 0.95)
|
||||||
|
end
|
||||||
|
tabBar.tabs = {}
|
||||||
|
optionsFrame.tabBar = tabBar
|
||||||
|
|
||||||
|
local contentArea = CreateFrame("Frame", nil, optionsFrame)
|
||||||
|
contentArea:SetPoint("TOPLEFT", tabBar, "BOTTOMLEFT", 12, -8)
|
||||||
|
contentArea:SetPoint("BOTTOMRIGHT", optionsFrame, "BOTTOMRIGHT", -12, 14)
|
||||||
|
|
||||||
|
local tabLabels = { L["Display Settings"], L["Data Settings"], L["Window Settings"] }
|
||||||
|
for i, name in ipairs(tabLabels) do
|
||||||
|
local tab = CreateTab(optionsFrame, i, name, tabBar, contentArea, table.getn(tabLabels))
|
||||||
|
tabBar.tabs[i] = tab
|
||||||
|
end
|
||||||
|
|
||||||
|
local cfg = NanamiDPS.config or {}
|
||||||
|
|
||||||
|
---------------------------------------------------------------
|
||||||
|
-- Tab 1: Display Settings
|
||||||
|
---------------------------------------------------------------
|
||||||
|
local page1 = tabBar.tabs[1].page
|
||||||
|
local y = 0
|
||||||
|
|
||||||
|
CreateSectionHeader(page1, L["Bar Height"], y)
|
||||||
|
y = y - 24
|
||||||
|
CreateStyledSlider(page1, "", 8, y, 10, 30, 1,
|
||||||
|
function() return cfg.barHeight or 16 end,
|
||||||
|
function(v) cfg.barHeight = math.floor(v + 0.5) end)
|
||||||
|
y = y - 34
|
||||||
|
|
||||||
|
CreateSectionHeader(page1, L["Bar Spacing"], y)
|
||||||
|
y = y - 24
|
||||||
|
CreateStyledSlider(page1, "", 8, y, 0, 5, 1,
|
||||||
|
function() return cfg.barSpacing or 1 end,
|
||||||
|
function(v) cfg.barSpacing = math.floor(v + 0.5) end)
|
||||||
|
y = y - 34
|
||||||
|
|
||||||
|
CreateSectionHeader(page1, L["Font Size"], y)
|
||||||
|
y = y - 24
|
||||||
|
CreateStyledSlider(page1, "", 8, y, 7, 16, 1,
|
||||||
|
function() return cfg.fontSize or 10 end,
|
||||||
|
function(v) cfg.fontSize = math.floor(v + 0.5) end)
|
||||||
|
y = y - 34
|
||||||
|
|
||||||
|
CreateSectionHeader(page1, L["Backdrop Alpha"], y)
|
||||||
|
y = y - 24
|
||||||
|
CreateStyledSlider(page1, "", 8, y, 0, 100, 5,
|
||||||
|
function() return (cfg.backdropAlpha or 0.92) * 100 end,
|
||||||
|
function(v) cfg.backdropAlpha = v / 100 end,
|
||||||
|
function(v) return math.floor(v + 0.5) .. "%" end)
|
||||||
|
y = y - 40
|
||||||
|
|
||||||
|
CreateStyledCheckbox(page1, L["Show Class Icons"], 8, y,
|
||||||
|
function() return cfg.showClassIcons end,
|
||||||
|
function(v) cfg.showClassIcons = v end)
|
||||||
|
|
||||||
|
---------------------------------------------------------------
|
||||||
|
-- Tab 2: Data Settings
|
||||||
|
---------------------------------------------------------------
|
||||||
|
local page2 = tabBar.tabs[2].page
|
||||||
|
y = 0
|
||||||
|
|
||||||
|
CreateSectionHeader(page2, L["Data Settings"], y)
|
||||||
|
y = y - 28
|
||||||
|
|
||||||
|
CreateStyledCheckbox(page2, L["Track All Units"], 8, y,
|
||||||
|
function() return cfg.trackAllUnits end,
|
||||||
|
function(v) cfg.trackAllUnits = v end)
|
||||||
|
y = y - 26
|
||||||
|
|
||||||
|
CreateStyledCheckbox(page2, L["Merge Pets"], 8, y,
|
||||||
|
function() return cfg.mergePets end,
|
||||||
|
function(v) cfg.mergePets = v end)
|
||||||
|
y = y - 36
|
||||||
|
|
||||||
|
CreateSectionHeader(page2, L["Max Segments"], y)
|
||||||
|
y = y - 24
|
||||||
|
CreateStyledSlider(page2, "", 8, y, 3, 30, 1,
|
||||||
|
function() return cfg.maxSegments or 10 end,
|
||||||
|
function(v) cfg.maxSegments = math.floor(v + 0.5) end)
|
||||||
|
y = y - 50
|
||||||
|
|
||||||
|
CreateSectionHeader(page2, L["Reset"], y)
|
||||||
|
y = y - 28
|
||||||
|
|
||||||
|
CreateActionButton(page2, "|cffff4444" .. L["Reset All Data"], 8, y, 150, function()
|
||||||
|
local dialog = StaticPopupDialogs["NANAMI_DPS_CONFIRM"]
|
||||||
|
dialog.text = L["Reset Data?"]
|
||||||
|
dialog.OnAccept = function() DataStore:ResetAll() end
|
||||||
|
StaticPopup_Show("NANAMI_DPS_CONFIRM")
|
||||||
|
end)
|
||||||
|
|
||||||
|
---------------------------------------------------------------
|
||||||
|
-- Tab 3: Window Settings
|
||||||
|
---------------------------------------------------------------
|
||||||
|
local page3 = tabBar.tabs[3].page
|
||||||
|
y = 0
|
||||||
|
|
||||||
|
CreateSectionHeader(page3, L["Window Settings"], y)
|
||||||
|
y = y - 28
|
||||||
|
|
||||||
|
CreateStyledCheckbox(page3, L["Lock Windows"], 8, y,
|
||||||
|
function() return cfg.locked end,
|
||||||
|
function(v) cfg.locked = v end)
|
||||||
|
y = y - 36
|
||||||
|
|
||||||
|
CreateSectionHeader(page3, L["Create New Window"], y)
|
||||||
|
y = y - 28
|
||||||
|
|
||||||
|
CreateActionButton(page3, L["Create New Window"], 8, y, 150, function()
|
||||||
|
Window:CreateNewWindow()
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Activate first tab
|
||||||
|
self:SwitchTab(optionsFrame, 1)
|
||||||
|
|
||||||
|
optionsFrame:Hide()
|
||||||
|
return optionsFrame
|
||||||
|
end
|
||||||
|
|
||||||
|
function Options:Toggle()
|
||||||
|
local panel = self:CreatePanel()
|
||||||
|
if panel:IsShown() then
|
||||||
|
panel:Hide()
|
||||||
|
else
|
||||||
|
panel:Show()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Slash commands
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
SLASH_NANAMIDPS1 = "/nanami"
|
||||||
|
SLASH_NANAMIDPS2 = "/ndps"
|
||||||
|
|
||||||
|
SlashCmdList["NANAMIDPS"] = function(msg)
|
||||||
|
msg = string.lower(msg or "")
|
||||||
|
msg = NanamiDPS.trim(msg)
|
||||||
|
|
||||||
|
if msg == "" or msg == "toggle" then
|
||||||
|
Window:ToggleVisibility()
|
||||||
|
elseif msg == "reset" then
|
||||||
|
DataStore:ResetAll()
|
||||||
|
local chatHex = (SFrames.ActiveTheme and SFrames.ActiveTheme.accentHex) or "ffFF88AA"
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage("|c" .. chatHex .. "Nanami DPS|r: " .. L["Data Reset"])
|
||||||
|
elseif msg == "config" or msg == "options" or msg == "settings" then
|
||||||
|
Options:Toggle()
|
||||||
|
elseif msg == "lock" then
|
||||||
|
local cfg = NanamiDPS.config or {}
|
||||||
|
cfg.locked = not cfg.locked
|
||||||
|
local chatHex = (SFrames.ActiveTheme and SFrames.ActiveTheme.accentHex) or "ffFF88AA"
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage("|c" .. chatHex .. "Nanami DPS|r: " .. (cfg.locked and L["Windows Locked"] or L["Windows Unlocked"]))
|
||||||
|
elseif msg == "report" then
|
||||||
|
local win = NanamiDPS.windows and NanamiDPS.windows[1]
|
||||||
|
if win then
|
||||||
|
local mod = NanamiDPS.modules[win.activeModuleName]
|
||||||
|
local seg = DataStore:GetSegment(win.segmentIndex or 1)
|
||||||
|
if mod and mod.GetReportLines then
|
||||||
|
local lines = mod:GetReportLines(seg, 5)
|
||||||
|
local modName = mod:GetName()
|
||||||
|
local segName = (win.segmentIndex == 0) and L["Total"] or L["Current"]
|
||||||
|
local chatHex = (SFrames.ActiveTheme and SFrames.ActiveTheme.accentHex) or "ffFF88AA"
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage("|c" .. chatHex .. "Nanami DPS|r - " .. segName .. " " .. modName .. ":")
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage(" " .. line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif string.find(msg, "^report ") then
|
||||||
|
local _, _, channel, countStr = string.find(msg, "^report%s+(%a+)%s*(%d*)")
|
||||||
|
local count = tonumber(countStr) or 5
|
||||||
|
local win = NanamiDPS.windows and NanamiDPS.windows[1]
|
||||||
|
if win and channel then
|
||||||
|
local mod = NanamiDPS.modules[win.activeModuleName]
|
||||||
|
local seg = DataStore:GetSegment(win.segmentIndex or 1)
|
||||||
|
if mod and mod.GetReportLines then
|
||||||
|
local lines = mod:GetReportLines(seg, count)
|
||||||
|
local modName = mod:GetName()
|
||||||
|
local segName = (win.segmentIndex == 0) and L["Total"] or L["Current"]
|
||||||
|
channel = string.upper(channel)
|
||||||
|
SendChatMessage("Nanami DPS - " .. segName .. " " .. modName .. ":", channel)
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
SendChatMessage(line, channel)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif msg == "new" or msg == "newwindow" then
|
||||||
|
Window:CreateNewWindow()
|
||||||
|
else
|
||||||
|
local chatHex = (SFrames.ActiveTheme and SFrames.ActiveTheme.accentHex) or "ffFF88AA"
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage("|c" .. chatHex .. "Nanami DPS|r " .. L["Commands"] .. ":")
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage(" /ndps toggle - " .. L["Show/Hide"])
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage(" /ndps reset - " .. L["Reset All Data"])
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage(" /ndps config - " .. L["Open Settings"])
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage(" /ndps lock - " .. L["Lock/Unlock"])
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage(" /ndps report - " .. L["Report to chat"])
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage(" /ndps report [channel] [count]")
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage(" /ndps new - " .. L["Create New Window"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS:RegisterCallback("INIT", "Options", function()
|
||||||
|
Options:CreatePanel()
|
||||||
|
end)
|
||||||
452
Parser.lua
Normal file
452
Parser.lua
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
|
||||||
|
local Parser = CreateFrame("Frame")
|
||||||
|
NanamiDPS.Parser = Parser
|
||||||
|
|
||||||
|
local validUnits = { ["player"] = true }
|
||||||
|
for i = 1, 4 do validUnits["party" .. i] = true end
|
||||||
|
for i = 1, 40 do validUnits["raid" .. i] = true end
|
||||||
|
|
||||||
|
local validPets = { ["pet"] = true }
|
||||||
|
for i = 1, 4 do validPets["partypet" .. i] = true end
|
||||||
|
for i = 1, 40 do validPets["raidpet" .. i] = true end
|
||||||
|
|
||||||
|
Parser.validUnits = validUnits
|
||||||
|
Parser.validPets = validPets
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Spell threat coefficients (vanilla / classic values, max rank)
|
||||||
|
-- Reference: classic-warrior wiki, warcrafttavern.com
|
||||||
|
-- bonus = fixed threat added ON TOP of damage
|
||||||
|
-- mult = multiplier applied to the DAMAGE portion (default 1.0)
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
local spellThreatData = {
|
||||||
|
["Heroic Strike"] = { bonus = 173 },
|
||||||
|
["Shield Slam"] = { bonus = 254 },
|
||||||
|
["Revenge"] = { bonus = 270, mult = 2.25 },
|
||||||
|
["Shield Bash"] = { bonus = 156, mult = 1.5 },
|
||||||
|
["Cleave"] = { bonus = 100 },
|
||||||
|
["Execute"] = { mult = 1.25 },
|
||||||
|
["Hamstring"] = { bonus = 135, mult = 1.25 },
|
||||||
|
["Thunder Clap"] = { mult = 2.5 },
|
||||||
|
["Overpower"] = { mult = 0.75 },
|
||||||
|
["Disarm"] = { bonus = 99 },
|
||||||
|
["Sunder Armor"] = { bonus = 261 },
|
||||||
|
["Maul"] = { mult = 1.75 },
|
||||||
|
["Swipe"] = { mult = 1.75 },
|
||||||
|
["Faerie Fire (Feral)"] = { bonus = 108 },
|
||||||
|
["Mind Blast"] = { mult = 2.0 },
|
||||||
|
["Holy Nova"] = { mult = 0 },
|
||||||
|
["Earth Shock"] = { mult = 2.0 },
|
||||||
|
["Searing Pain"] = { mult = 2.0 },
|
||||||
|
["Distracting Shot"] = { bonus = 600 },
|
||||||
|
["Scorpid Poison"] = { bonus = 5 },
|
||||||
|
["Intimidation"] = { bonus = 580 },
|
||||||
|
["Life Tap"] = { mult = 0 },
|
||||||
|
["Counterspell"] = { bonus = 300 },
|
||||||
|
["Mocking Blow"] = { bonus = 250 },
|
||||||
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
local cnAliases = {
|
||||||
|
["\232\139\177\229\139\135\230\137\147\229\135\187"] = "Heroic Strike",
|
||||||
|
["\231\155\190\231\137\140\231\140\155\229\135\187"] = "Shield Slam",
|
||||||
|
["\229\164\141\228\187\135"] = "Revenge",
|
||||||
|
["\231\155\190\229\135\187"] = "Shield Bash",
|
||||||
|
["\233\161\186\229\138\136\230\150\169"] = "Cleave",
|
||||||
|
["\230\150\169\230\157\128"] = "Execute",
|
||||||
|
["\230\150\173\231\173\139"] = "Hamstring",
|
||||||
|
["\233\155\183\233\156\134\228\184\128\229\135\187"] = "Thunder Clap",
|
||||||
|
["\229\142\139\229\136\182"] = "Overpower",
|
||||||
|
["\231\188\180\230\162\176"] = "Disarm",
|
||||||
|
["\231\160\180\231\148\178\230\148\187\229\135\187"] = "Sunder Armor",
|
||||||
|
["\230\167\152\229\135\187"] = "Maul",
|
||||||
|
["\230\140\165\229\135\187"] = "Swipe",
|
||||||
|
["\231\178\190\231\129\181\228\185\139\231\129\171(\233\135\142\233\135\145)"] = "Faerie Fire (Feral)",
|
||||||
|
["\229\191\131\231\129\181\233\156\135\231\136\134"] = "Mind Blast",
|
||||||
|
["\231\165\158\229\156\163\230\150\176\230\152\159"] = "Holy Nova",
|
||||||
|
["\229\156\176\233\156\135\230\156\175"] = "Earth Shock",
|
||||||
|
["\231\129\188\231\131\173\228\185\139\231\151\155"] = "Searing Pain",
|
||||||
|
["\230\137\176\228\185\177\229\176\132\229\135\187"] = "Distracting Shot",
|
||||||
|
["\232\157\157\230\175\146"] = "Scorpid Poison",
|
||||||
|
["\232\131\129\232\191\171"] = "Intimidation",
|
||||||
|
["\231\148\159\229\145\189\229\136\134\230\181\129"] = "Life Tap",
|
||||||
|
["\229\143\141\229\136\182\233\173\148\230\179\149"] = "Counterspell",
|
||||||
|
["\229\152\178\229\188\132\230\137\147\229\135\187"] = "Mocking Blow",
|
||||||
|
}
|
||||||
|
for cn, en in pairs(cnAliases) do
|
||||||
|
if spellThreatData[en] then
|
||||||
|
spellThreatData[cn] = spellThreatData[en]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local classThreatMod = {
|
||||||
|
ROGUE = 0.8,
|
||||||
|
}
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Stance / form / buff threat modifier detection
|
||||||
|
-- Scans UnitBuff textures to detect known threat-altering states.
|
||||||
|
-- Cached per-unit for 2 seconds to avoid excessive buff scanning.
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
local stanceScanPatterns = {
|
||||||
|
{ pat = "DefensiveStance", mod = 1.3 },
|
||||||
|
{ pat = "OffensiveStance", mod = 0.8 },
|
||||||
|
{ pat = "Racial_Avatar", mod = 0.8 },
|
||||||
|
{ pat = "BerserkStance", mod = 0.8 },
|
||||||
|
{ pat = "BearForm", mod = 1.3 },
|
||||||
|
{ pat = "CatForm", mod = 0.8 },
|
||||||
|
}
|
||||||
|
|
||||||
|
local buffScanPatterns = {
|
||||||
|
{ pat = "SealOfSalvation", mod = 0.7 },
|
||||||
|
}
|
||||||
|
|
||||||
|
local function ScanUnitThreatMod(unit)
|
||||||
|
if not unit or not UnitExists(unit) then return 1.0 end
|
||||||
|
local stanceMod = 1.0
|
||||||
|
local buffMod = 1.0
|
||||||
|
for i = 1, 32 do
|
||||||
|
local texture = UnitBuff(unit, i)
|
||||||
|
if not texture then break end
|
||||||
|
for _, s in ipairs(stanceScanPatterns) do
|
||||||
|
if string.find(texture, s.pat) then
|
||||||
|
stanceMod = s.mod
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, b in ipairs(buffScanPatterns) do
|
||||||
|
if string.find(texture, b.pat) then
|
||||||
|
buffMod = buffMod * b.mod
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return stanceMod * buffMod
|
||||||
|
end
|
||||||
|
|
||||||
|
local stanceModCache = {}
|
||||||
|
local STANCE_CACHE_TTL = 2
|
||||||
|
|
||||||
|
function Parser:GetUnitThreatMod(name)
|
||||||
|
local now = GetTime()
|
||||||
|
local cached = stanceModCache[name]
|
||||||
|
if cached and (now - cached.time) < STANCE_CACHE_TTL then
|
||||||
|
return cached.mod
|
||||||
|
end
|
||||||
|
local unit = self:UnitByName(name)
|
||||||
|
local mod = ScanUnitThreatMod(unit)
|
||||||
|
stanceModCache[name] = { mod = mod, time = now }
|
||||||
|
return mod
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:CalculateSpellThreat(source, spell, damage)
|
||||||
|
if not damage or damage <= 0 then return 0 end
|
||||||
|
|
||||||
|
local threat = damage
|
||||||
|
local coeff = spell and spellThreatData[spell]
|
||||||
|
|
||||||
|
if coeff then
|
||||||
|
threat = damage * (coeff.mult or 1.0) + (coeff.bonus or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
local class = DataStore:GetClass(source)
|
||||||
|
if class and classThreatMod[class] then
|
||||||
|
threat = threat * classThreatMod[class]
|
||||||
|
end
|
||||||
|
|
||||||
|
threat = threat * self:GetUnitThreatMod(source)
|
||||||
|
|
||||||
|
return math.max(threat, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:CalculateHealThreat(source, amount)
|
||||||
|
if not amount or amount <= 0 then return 0 end
|
||||||
|
|
||||||
|
local threat = amount * 0.5
|
||||||
|
|
||||||
|
local class = DataStore:GetClass(source)
|
||||||
|
if class and classThreatMod[class] then
|
||||||
|
threat = threat * classThreatMod[class]
|
||||||
|
end
|
||||||
|
|
||||||
|
threat = threat * self:GetUnitThreatMod(source)
|
||||||
|
|
||||||
|
return threat
|
||||||
|
end
|
||||||
|
|
||||||
|
local unit_cache = {}
|
||||||
|
function Parser:UnitByName(name)
|
||||||
|
if unit_cache[name] and UnitName(unit_cache[name]) == name then
|
||||||
|
return unit_cache[name]
|
||||||
|
end
|
||||||
|
for unit in pairs(validUnits) do
|
||||||
|
if UnitName(unit) == name then
|
||||||
|
unit_cache[name] = unit
|
||||||
|
return unit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for unit in pairs(validPets) do
|
||||||
|
if UnitName(unit) == name then
|
||||||
|
unit_cache[name] = unit
|
||||||
|
return unit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:ScanName(name)
|
||||||
|
if not name then return nil end
|
||||||
|
|
||||||
|
for unit in pairs(validUnits) do
|
||||||
|
if UnitExists(unit) and UnitName(unit) == name then
|
||||||
|
if UnitIsPlayer(unit) then
|
||||||
|
local _, class = UnitClass(unit)
|
||||||
|
DataStore:SetClass(name, class)
|
||||||
|
return "PLAYER"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- SuperWoW pet format: "PetName (OwnerName)"
|
||||||
|
local match, _, owner = string.find(name, "%((.*)%)", 1)
|
||||||
|
if match and owner then
|
||||||
|
if Parser:ScanName(owner) == "PLAYER" then
|
||||||
|
DataStore:SetClass(name, owner)
|
||||||
|
return "PET"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for unit in pairs(validPets) do
|
||||||
|
if UnitExists(unit) and UnitName(unit) == name then
|
||||||
|
if strsub(unit, 0, 3) == "pet" then
|
||||||
|
DataStore:SetClass(name, UnitName("player"))
|
||||||
|
elseif strsub(unit, 0, 8) == "partypet" then
|
||||||
|
DataStore:SetClass(name, UnitName("party" .. strsub(unit, 9)))
|
||||||
|
elseif strsub(unit, 0, 7) == "raidpet" then
|
||||||
|
DataStore:SetClass(name, UnitName("raid" .. strsub(unit, 8)))
|
||||||
|
end
|
||||||
|
return "PET"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if NanamiDPS.config and NanamiDPS.config.trackAllUnits then
|
||||||
|
DataStore:SetClass(name, DataStore:GetClass(name) or "__other__")
|
||||||
|
return "OTHER"
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:ResolveSource(source)
|
||||||
|
source = NanamiDPS.trim(source)
|
||||||
|
|
||||||
|
local sourceType = self:ScanName(source)
|
||||||
|
if not sourceType then return nil, nil end
|
||||||
|
|
||||||
|
if NanamiDPS.config and NanamiDPS.config.mergePets and sourceType == "PET" then
|
||||||
|
local ownerClass = DataStore:GetClass(source)
|
||||||
|
if ownerClass and ownerClass ~= "__other__" and NanamiDPS.validClasses[DataStore:GetClass(ownerClass)] then
|
||||||
|
return ownerClass, "Pet: " .. source
|
||||||
|
elseif ownerClass and ownerClass ~= "__other__" then
|
||||||
|
return ownerClass, "Pet: " .. source
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return source, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:ProcessDamage(source, spell, target, amount, school)
|
||||||
|
if type(source) ~= "string" or not tonumber(amount) then return end
|
||||||
|
source = NanamiDPS.trim(source)
|
||||||
|
amount = tonumber(amount)
|
||||||
|
|
||||||
|
if source == target then return end
|
||||||
|
|
||||||
|
local resolvedSource, petPrefix = self:ResolveSource(source)
|
||||||
|
if not resolvedSource then return end
|
||||||
|
|
||||||
|
local finalSpell = petPrefix and (petPrefix .. ": " .. (spell or "")) or spell
|
||||||
|
|
||||||
|
if not DataStore.inCombat then
|
||||||
|
DataStore:StartCombat()
|
||||||
|
end
|
||||||
|
|
||||||
|
DataStore:AddDamage(resolvedSource, finalSpell, target, amount, school)
|
||||||
|
|
||||||
|
local targetType = self:ScanName(target)
|
||||||
|
if targetType == "PLAYER" then
|
||||||
|
DataStore:AddDamageTaken(target, finalSpell, resolvedSource, amount, school)
|
||||||
|
end
|
||||||
|
|
||||||
|
DataStore:AddThreat(source, self:CalculateSpellThreat(source, spell, amount))
|
||||||
|
DataStore:UpdateActivity(resolvedSource, GetTime())
|
||||||
|
|
||||||
|
if targetType == "PLAYER" then
|
||||||
|
self:FeedDeathLog(target, {
|
||||||
|
time = GetTime(),
|
||||||
|
source = resolvedSource,
|
||||||
|
spell = finalSpell,
|
||||||
|
amount = -amount,
|
||||||
|
type = "damage",
|
||||||
|
hp = self:GetUnitHP(target),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
self:ThrottledRefresh()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:ProcessHealing(source, spell, target, amount, school)
|
||||||
|
if type(source) ~= "string" or not tonumber(amount) then return end
|
||||||
|
source = NanamiDPS.trim(source)
|
||||||
|
amount = tonumber(amount)
|
||||||
|
|
||||||
|
local resolvedSource, petPrefix = self:ResolveSource(source)
|
||||||
|
if not resolvedSource then return end
|
||||||
|
|
||||||
|
local finalSpell = petPrefix and (petPrefix .. ": " .. (spell or "")) or spell
|
||||||
|
|
||||||
|
local effective = amount
|
||||||
|
local unitstr = self:UnitByName(target)
|
||||||
|
if unitstr then
|
||||||
|
effective = math.min(UnitHealthMax(unitstr) - UnitHealth(unitstr), amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not DataStore.inCombat then
|
||||||
|
DataStore:StartCombat()
|
||||||
|
end
|
||||||
|
|
||||||
|
DataStore:AddHealing(resolvedSource, finalSpell, target, amount, effective)
|
||||||
|
|
||||||
|
DataStore:AddThreat(source, self:CalculateHealThreat(source, amount))
|
||||||
|
DataStore:UpdateActivity(resolvedSource, GetTime())
|
||||||
|
|
||||||
|
local targetType = self:ScanName(target)
|
||||||
|
if targetType == "PLAYER" then
|
||||||
|
self:FeedDeathLog(target, {
|
||||||
|
time = GetTime(),
|
||||||
|
source = resolvedSource,
|
||||||
|
spell = finalSpell,
|
||||||
|
amount = effective,
|
||||||
|
type = "heal",
|
||||||
|
hp = self:GetUnitHP(target),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
self:ThrottledRefresh()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:ProcessDeath(name)
|
||||||
|
if not name then return end
|
||||||
|
local nameType = self:ScanName(name)
|
||||||
|
if nameType ~= "PLAYER" then return end
|
||||||
|
|
||||||
|
local log = self:FlushDeathLog(name)
|
||||||
|
DataStore:AddDeath(name, {
|
||||||
|
time = GetTime(),
|
||||||
|
timeStr = date("%H:%M:%S"),
|
||||||
|
events = log,
|
||||||
|
})
|
||||||
|
|
||||||
|
self:ThrottledRefresh()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:ProcessDispel(source, spell, target, aura)
|
||||||
|
if type(source) ~= "string" then return end
|
||||||
|
source = NanamiDPS.trim(source)
|
||||||
|
|
||||||
|
local resolvedSource = self:ResolveSource(source)
|
||||||
|
if not resolvedSource then return end
|
||||||
|
|
||||||
|
DataStore:AddDispel(resolvedSource, spell or NanamiDPS.L["Unknown"], target, aura)
|
||||||
|
DataStore:UpdateActivity(resolvedSource, GetTime())
|
||||||
|
|
||||||
|
self:ThrottledRefresh()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:ProcessInterrupt(source, spell, target, interrupted)
|
||||||
|
if type(source) ~= "string" then return end
|
||||||
|
source = NanamiDPS.trim(source)
|
||||||
|
|
||||||
|
local resolvedSource = self:ResolveSource(source)
|
||||||
|
if not resolvedSource then return end
|
||||||
|
|
||||||
|
DataStore:AddInterrupt(resolvedSource, spell or NanamiDPS.L["Unknown"], target, interrupted)
|
||||||
|
DataStore:UpdateActivity(resolvedSource, GetTime())
|
||||||
|
|
||||||
|
self:ThrottledRefresh()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:GetUnitHP(name)
|
||||||
|
local unit = self:UnitByName(name)
|
||||||
|
if unit then
|
||||||
|
return UnitHealth(unit), UnitHealthMax(unit)
|
||||||
|
end
|
||||||
|
return 0, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Throttle refresh callbacks to avoid excessive UI updates
|
||||||
|
Parser.lastRefreshTime = 0
|
||||||
|
local REFRESH_INTERVAL = 0.25
|
||||||
|
|
||||||
|
function Parser:ThrottledRefresh()
|
||||||
|
local now = GetTime()
|
||||||
|
if now - self.lastRefreshTime >= REFRESH_INTERVAL then
|
||||||
|
self.lastRefreshTime = now
|
||||||
|
NanamiDPS:FireCallback("refresh")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Death log buffer: last 15 seconds of events per player
|
||||||
|
Parser.deathLogBuffer = {}
|
||||||
|
local DEATH_LOG_WINDOW = 15
|
||||||
|
|
||||||
|
function Parser:FeedDeathLog(name, entry)
|
||||||
|
if not self.deathLogBuffer[name] then
|
||||||
|
self.deathLogBuffer[name] = {}
|
||||||
|
end
|
||||||
|
table.insert(self.deathLogBuffer[name], entry)
|
||||||
|
|
||||||
|
-- Trim old entries
|
||||||
|
local now = GetTime()
|
||||||
|
local buf = self.deathLogBuffer[name]
|
||||||
|
while table.getn(buf) > 0 and (now - buf[1].time) > DEATH_LOG_WINDOW do
|
||||||
|
table.remove(buf, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:FlushDeathLog(name)
|
||||||
|
local log = self.deathLogBuffer[name] or {}
|
||||||
|
self.deathLogBuffer[name] = {}
|
||||||
|
return log
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Combat state detection
|
||||||
|
local combatFrame = CreateFrame("Frame", "NanamiDPSCombatState", UIParent)
|
||||||
|
combatFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||||
|
combatFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||||
|
|
||||||
|
local pendingCombatEnd = false
|
||||||
|
local combatEndTimer = 0
|
||||||
|
|
||||||
|
combatFrame:SetScript("OnEvent", function()
|
||||||
|
if event == "PLAYER_REGEN_DISABLED" then
|
||||||
|
pendingCombatEnd = false
|
||||||
|
if not DataStore.inCombat then
|
||||||
|
DataStore:StartCombat()
|
||||||
|
end
|
||||||
|
elseif event == "PLAYER_REGEN_ENABLED" then
|
||||||
|
pendingCombatEnd = true
|
||||||
|
combatEndTimer = GetTime() + 2
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
combatFrame:SetScript("OnUpdate", function()
|
||||||
|
if (this.tick or 1) > GetTime() then return end
|
||||||
|
this.tick = GetTime() + 1
|
||||||
|
|
||||||
|
if pendingCombatEnd and GetTime() >= combatEndTimer then
|
||||||
|
pendingCombatEnd = false
|
||||||
|
DataStore:StopCombat()
|
||||||
|
end
|
||||||
|
end)
|
||||||
465
ParserVanilla.lua
Normal file
465
ParserVanilla.lua
Normal file
@@ -0,0 +1,465 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local Parser = NanamiDPS.Parser
|
||||||
|
|
||||||
|
-- Pattern utility functions (from ShaguDPS)
|
||||||
|
local sanitize_cache = {}
|
||||||
|
local function sanitize(pattern)
|
||||||
|
if not sanitize_cache[pattern] then
|
||||||
|
local ret = pattern
|
||||||
|
ret = gsub(ret, "([%+%-%*%(%)%?%[%]%^])", "%%%1")
|
||||||
|
ret = gsub(ret, "%d%$", "")
|
||||||
|
ret = gsub(ret, "(%%%a)", "%(%1+%)")
|
||||||
|
ret = gsub(ret, "%%s%+", ".+")
|
||||||
|
ret = gsub(ret, "%(.%+%)%(%%d%+%)", "%(.-%)%(%%d%+%)")
|
||||||
|
sanitize_cache[pattern] = ret
|
||||||
|
end
|
||||||
|
return sanitize_cache[pattern]
|
||||||
|
end
|
||||||
|
|
||||||
|
local capture_cache = {}
|
||||||
|
local function captures(pat)
|
||||||
|
local r = capture_cache
|
||||||
|
if not r[pat] then
|
||||||
|
r[pat] = { nil, nil, nil, nil, nil }
|
||||||
|
for a, b, c, d, e in string.gfind(gsub(pat, "%((.+)%)", "%1"), gsub(pat, "%d%$", "%%(.-)$")) do
|
||||||
|
r[pat][1] = tonumber(a)
|
||||||
|
r[pat][2] = tonumber(b)
|
||||||
|
r[pat][3] = tonumber(c)
|
||||||
|
r[pat][4] = tonumber(d)
|
||||||
|
r[pat][5] = tonumber(e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r[pat][1], r[pat][2], r[pat][3], r[pat][4], r[pat][5]
|
||||||
|
end
|
||||||
|
|
||||||
|
local ra, rb, rc, rd, re, a, b, c, d, e, match, num, va, vb, vc, vd, ve
|
||||||
|
local function cfind(str, pat)
|
||||||
|
a, b, c, d, e = captures(pat)
|
||||||
|
match, num, va, vb, vc, vd, ve = string.find(str, sanitize(pat))
|
||||||
|
ra = e == 1 and ve or d == 1 and vd or c == 1 and vc or b == 1 and vb or va
|
||||||
|
rb = e == 2 and ve or d == 2 and vd or c == 2 and vc or a == 2 and va or vb
|
||||||
|
rc = e == 3 and ve or d == 3 and vd or a == 3 and va or b == 3 and vb or vc
|
||||||
|
rd = e == 4 and ve or a == 4 and va or c == 4 and vc or b == 4 and vb or vd
|
||||||
|
re = a == 5 and va or d == 5 and vd or c == 5 and vc or b == 5 and vb or ve
|
||||||
|
return match, num, ra, rb, rc, rd, re
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Combat log string groups
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
local combatlog_strings = {
|
||||||
|
-- Damage: melee
|
||||||
|
["Hit Damage (self vs. other)"] = {
|
||||||
|
COMBATHITSELFOTHER, COMBATHITSCHOOLSELFOTHER,
|
||||||
|
COMBATHITCRITSELFOTHER, COMBATHITCRITSCHOOLSELFOTHER,
|
||||||
|
},
|
||||||
|
["Hit Damage (other vs. self)"] = {
|
||||||
|
COMBATHITOTHERSELF, COMBATHITCRITOTHERSELF,
|
||||||
|
COMBATHITSCHOOLOTHERSELF, COMBATHITCRITSCHOOLOTHERSELF,
|
||||||
|
},
|
||||||
|
["Hit Damage (other vs. other)"] = {
|
||||||
|
COMBATHITOTHEROTHER, COMBATHITCRITOTHEROTHER,
|
||||||
|
COMBATHITSCHOOLOTHEROTHER, COMBATHITCRITSCHOOLOTHEROTHER,
|
||||||
|
},
|
||||||
|
-- Damage: spell
|
||||||
|
["Spell Damage (self vs. self/other)"] = {
|
||||||
|
SPELLLOGSCHOOLSELFSELF, SPELLLOGCRITSCHOOLSELFSELF,
|
||||||
|
SPELLLOGSELFSELF, SPELLLOGCRITSELFSELF,
|
||||||
|
SPELLLOGSCHOOLSELFOTHER, SPELLLOGCRITSCHOOLSELFOTHER,
|
||||||
|
SPELLLOGSELFOTHER, SPELLLOGCRITSELFOTHER,
|
||||||
|
},
|
||||||
|
["Spell Damage (other vs. self)"] = {
|
||||||
|
SPELLLOGSCHOOLOTHERSELF, SPELLLOGCRITSCHOOLOTHERSELF,
|
||||||
|
SPELLLOGOTHERSELF, SPELLLOGCRITOTHERSELF,
|
||||||
|
},
|
||||||
|
["Spell Damage (other vs. other)"] = {
|
||||||
|
SPELLLOGSCHOOLOTHEROTHER, SPELLLOGCRITSCHOOLOTHEROTHER,
|
||||||
|
SPELLLOGOTHEROTHER, SPELLLOGCRITOTHEROTHER,
|
||||||
|
},
|
||||||
|
-- Damage: shields
|
||||||
|
["Shield Damage (self vs. other)"] = {
|
||||||
|
DAMAGESHIELDSELFOTHER,
|
||||||
|
},
|
||||||
|
["Shield Damage (other vs. self/other)"] = {
|
||||||
|
DAMAGESHIELDOTHERSELF, DAMAGESHIELDOTHEROTHER,
|
||||||
|
},
|
||||||
|
-- Damage: periodic
|
||||||
|
["Periodic Damage (self/other vs. other)"] = {
|
||||||
|
PERIODICAURADAMAGESELFOTHER, PERIODICAURADAMAGEOTHEROTHER,
|
||||||
|
},
|
||||||
|
["Periodic Damage (self/other vs. self)"] = {
|
||||||
|
PERIODICAURADAMAGESELFSELF, PERIODICAURADAMAGEOTHERSELF,
|
||||||
|
},
|
||||||
|
-- Healing
|
||||||
|
["Heal (self vs. self/other)"] = {
|
||||||
|
HEALEDCRITSELFSELF, HEALEDSELFSELF,
|
||||||
|
HEALEDCRITSELFOTHER, HEALEDSELFOTHER,
|
||||||
|
},
|
||||||
|
["Heal (other vs. self/other)"] = {
|
||||||
|
HEALEDCRITOTHERSELF, HEALEDOTHERSELF,
|
||||||
|
HEALEDCRITOTHEROTHER, HEALEDOTHEROTHER,
|
||||||
|
},
|
||||||
|
["Periodic Heal (self/other vs. other)"] = {
|
||||||
|
PERIODICAURAHEALSELFOTHER, PERIODICAURAHEALOTHEROTHER,
|
||||||
|
},
|
||||||
|
["Periodic Heal (other vs. self/other)"] = {
|
||||||
|
PERIODICAURAHEALSELFSELF, PERIODICAURAHEALOTHERSELF,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Event -> pattern mapping
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
local combatlog_events = {
|
||||||
|
-- Damage: melee
|
||||||
|
["CHAT_MSG_COMBAT_SELF_HITS"] = combatlog_strings["Hit Damage (self vs. other)"],
|
||||||
|
["CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS"] = combatlog_strings["Hit Damage (other vs. self)"],
|
||||||
|
["CHAT_MSG_COMBAT_PARTY_HITS"] = combatlog_strings["Hit Damage (other vs. other)"],
|
||||||
|
["CHAT_MSG_COMBAT_FRIENDLYPLAYER_HITS"] = combatlog_strings["Hit Damage (other vs. other)"],
|
||||||
|
["CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS"] = combatlog_strings["Hit Damage (other vs. other)"],
|
||||||
|
["CHAT_MSG_COMBAT_CREATURE_VS_CREATURE_HITS"] = combatlog_strings["Hit Damage (other vs. other)"],
|
||||||
|
["CHAT_MSG_COMBAT_CREATURE_VS_PARTY_HITS"] = combatlog_strings["Hit Damage (other vs. other)"],
|
||||||
|
["CHAT_MSG_COMBAT_PET_HITS"] = combatlog_strings["Hit Damage (other vs. other)"],
|
||||||
|
-- Damage: spell
|
||||||
|
["CHAT_MSG_SPELL_SELF_DAMAGE"] = combatlog_strings["Spell Damage (self vs. self/other)"],
|
||||||
|
["CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"] = combatlog_strings["Spell Damage (other vs. self)"],
|
||||||
|
["CHAT_MSG_SPELL_PARTY_DAMAGE"] = combatlog_strings["Spell Damage (other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE"] = combatlog_strings["Spell Damage (other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE"] = combatlog_strings["Spell Damage (other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE"] = combatlog_strings["Spell Damage (other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_CREATURE_VS_PARTY_DAMAGE"] = combatlog_strings["Spell Damage (other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_PET_DAMAGE"] = combatlog_strings["Spell Damage (other vs. other)"],
|
||||||
|
-- Damage: shields
|
||||||
|
["CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF"] = combatlog_strings["Shield Damage (self vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS"] = combatlog_strings["Shield Damage (other vs. self/other)"],
|
||||||
|
-- Damage: periodic
|
||||||
|
["CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE"] = combatlog_strings["Periodic Damage (self/other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE"] = combatlog_strings["Periodic Damage (self/other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE"] = combatlog_strings["Periodic Damage (self/other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE"] = combatlog_strings["Periodic Damage (self/other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE"] = combatlog_strings["Periodic Damage (self/other vs. self)"],
|
||||||
|
-- Healing
|
||||||
|
["CHAT_MSG_SPELL_SELF_BUFF"] = combatlog_strings["Heal (self vs. self/other)"],
|
||||||
|
["CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF"] = combatlog_strings["Heal (other vs. self/other)"],
|
||||||
|
["CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF"] = combatlog_strings["Heal (other vs. self/other)"],
|
||||||
|
["CHAT_MSG_SPELL_PARTY_BUFF"] = combatlog_strings["Heal (other vs. self/other)"],
|
||||||
|
["CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS"] = combatlog_strings["Periodic Heal (self/other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS"] = combatlog_strings["Periodic Heal (self/other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS"] = combatlog_strings["Periodic Heal (self/other vs. other)"],
|
||||||
|
["CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"] = combatlog_strings["Periodic Heal (other vs. self/other)"],
|
||||||
|
}
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Pattern -> parser function mapping
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
local combatlog_parser = {
|
||||||
|
-- Spell damage: self vs self
|
||||||
|
[SPELLLOGSCHOOLSELFSELF] = function(d, attack, value, school)
|
||||||
|
return d.source, attack, d.target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGCRITSCHOOLSELFSELF] = function(d, attack, value, school)
|
||||||
|
return d.source, attack, d.target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGSELFSELF] = function(d, attack, value)
|
||||||
|
return d.source, attack, d.target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGCRITSELFSELF] = function(d, attack, value)
|
||||||
|
return d.source, attack, d.target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[PERIODICAURADAMAGESELFSELF] = function(d, value, school, attack)
|
||||||
|
return d.source, attack, d.target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
-- Spell damage: self vs other
|
||||||
|
[SPELLLOGSCHOOLSELFOTHER] = function(d, attack, target, value, school)
|
||||||
|
return d.source, attack, target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGCRITSCHOOLSELFOTHER] = function(d, attack, target, value, school)
|
||||||
|
return d.source, attack, target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGSELFOTHER] = function(d, attack, target, value)
|
||||||
|
return d.source, attack, target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGCRITSELFOTHER] = function(d, attack, target, value)
|
||||||
|
return d.source, attack, target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[PERIODICAURADAMAGESELFOTHER] = function(d, target, value, school, attack)
|
||||||
|
return d.source, attack, target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
-- Melee damage: self vs other
|
||||||
|
[COMBATHITSELFOTHER] = function(d, target, value)
|
||||||
|
return d.source, d.attack, target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[COMBATHITCRITSELFOTHER] = function(d, target, value)
|
||||||
|
return d.source, d.attack, target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[COMBATHITSCHOOLSELFOTHER] = function(d, target, value, school)
|
||||||
|
return d.source, d.attack, target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[COMBATHITCRITSCHOOLSELFOTHER] = function(d, target, value, school)
|
||||||
|
return d.source, d.attack, target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
-- Shield damage: self vs other
|
||||||
|
[DAMAGESHIELDSELFOTHER] = function(d, value, school, target)
|
||||||
|
return d.source, "Reflect (" .. school .. ")", target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
-- Spell damage: other vs self
|
||||||
|
[SPELLLOGSCHOOLOTHERSELF] = function(d, source, attack, value, school)
|
||||||
|
return source, attack, d.target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGCRITSCHOOLOTHERSELF] = function(d, source, attack, value, school)
|
||||||
|
return source, attack, d.target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGOTHERSELF] = function(d, source, attack, value)
|
||||||
|
return source, attack, d.target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGCRITOTHERSELF] = function(d, source, attack, value)
|
||||||
|
return source, attack, d.target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[PERIODICAURADAMAGEOTHERSELF] = function(d, value, school, source, attack)
|
||||||
|
return source, attack, d.target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
-- Melee damage: other vs self
|
||||||
|
[COMBATHITOTHERSELF] = function(d, source, value)
|
||||||
|
return source, d.attack, d.target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[COMBATHITCRITOTHERSELF] = function(d, source, value)
|
||||||
|
return source, d.attack, d.target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[COMBATHITSCHOOLOTHERSELF] = function(d, source, value, school)
|
||||||
|
return source, d.attack, d.target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[COMBATHITCRITSCHOOLOTHERSELF] = function(d, source, value, school)
|
||||||
|
return source, d.attack, d.target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
-- Spell damage: other vs other
|
||||||
|
[SPELLLOGSCHOOLOTHEROTHER] = function(d, source, attack, target, value, school)
|
||||||
|
return source, attack, target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGCRITSCHOOLOTHEROTHER] = function(d, source, attack, target, value, school)
|
||||||
|
return source, attack, target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGOTHEROTHER] = function(d, source, attack, target, value)
|
||||||
|
return source, attack, target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[SPELLLOGCRITOTHEROTHER] = function(d, source, attack, target, value, school)
|
||||||
|
return source, attack, target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[PERIODICAURADAMAGEOTHEROTHER] = function(d, target, value, school, source, attack)
|
||||||
|
return source, attack, target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
-- Melee damage: other vs other
|
||||||
|
[COMBATHITOTHEROTHER] = function(d, source, target, value)
|
||||||
|
return source, d.attack, target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[COMBATHITCRITOTHEROTHER] = function(d, source, target, value)
|
||||||
|
return source, d.attack, target, value, d.school, "damage"
|
||||||
|
end,
|
||||||
|
[COMBATHITSCHOOLOTHEROTHER] = function(d, source, target, value, school)
|
||||||
|
return source, d.attack, target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[COMBATHITCRITSCHOOLOTHEROTHER] = function(d, source, target, value, school)
|
||||||
|
return source, d.attack, target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
-- Shield damage: other
|
||||||
|
[DAMAGESHIELDOTHERSELF] = function(d, source, value, school)
|
||||||
|
return source, "Reflect (" .. school .. ")", d.target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
[DAMAGESHIELDOTHEROTHER] = function(d, source, value, school, target)
|
||||||
|
return source, "Reflect (" .. school .. ")", target, value, school, "damage"
|
||||||
|
end,
|
||||||
|
-- Healing: other vs self
|
||||||
|
[HEALEDCRITOTHERSELF] = function(d, source, spell, value)
|
||||||
|
return source, spell, d.target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
[HEALEDOTHERSELF] = function(d, source, spell, value)
|
||||||
|
return source, spell, d.target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
[PERIODICAURAHEALOTHERSELF] = function(d, value, source, spell)
|
||||||
|
return source, spell, d.target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
-- Healing: self vs self
|
||||||
|
[HEALEDCRITSELFSELF] = function(d, spell, value)
|
||||||
|
return d.source, spell, d.target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
[HEALEDSELFSELF] = function(d, spell, value)
|
||||||
|
return d.source, spell, d.target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
[PERIODICAURAHEALSELFSELF] = function(d, value, spell)
|
||||||
|
return d.source, spell, d.target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
-- Healing: self vs other
|
||||||
|
[HEALEDCRITSELFOTHER] = function(d, spell, target, value)
|
||||||
|
return d.source, spell, target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
[HEALEDSELFOTHER] = function(d, spell, target, value)
|
||||||
|
return d.source, spell, target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
[PERIODICAURAHEALSELFOTHER] = function(d, target, value, spell)
|
||||||
|
return d.source, spell, target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
-- Healing: other vs other
|
||||||
|
[HEALEDCRITOTHEROTHER] = function(d, source, spell, target, value)
|
||||||
|
return source, spell, target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
[HEALEDOTHEROTHER] = function(d, source, spell, target, value)
|
||||||
|
return source, spell, target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
[PERIODICAURAHEALOTHEROTHER] = function(d, target, value, source, spell)
|
||||||
|
return source, spell, target, value, d.school, "heal"
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Register combat log events
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
for evt in pairs(combatlog_events) do
|
||||||
|
Parser:RegisterEvent(evt)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Preload patterns
|
||||||
|
for pattern in pairs(combatlog_parser) do
|
||||||
|
sanitize(pattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Death events
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
Parser:RegisterEvent("CHAT_MSG_COMBAT_FRIENDLY_DEATH")
|
||||||
|
Parser:RegisterEvent("CHAT_MSG_COMBAT_HOSTILE_DEATH")
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Interrupt patterns
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
local interrupt_strings = {}
|
||||||
|
if SPELLINTERRUPTSELFOTHER then
|
||||||
|
table.insert(interrupt_strings, SPELLINTERRUPTSELFOTHER)
|
||||||
|
end
|
||||||
|
if SPELLINTERRUPTOTHERSELF then
|
||||||
|
table.insert(interrupt_strings, SPELLINTERRUPTOTHERSELF)
|
||||||
|
end
|
||||||
|
if SPELLINTERRUPTOTHEROTHER then
|
||||||
|
table.insert(interrupt_strings, SPELLINTERRUPTOTHEROTHER)
|
||||||
|
end
|
||||||
|
|
||||||
|
local interrupt_parser = {}
|
||||||
|
if SPELLINTERRUPTSELFOTHER then
|
||||||
|
interrupt_parser[SPELLINTERRUPTSELFOTHER] = function(d, spell, target, interrupted)
|
||||||
|
return d.source, spell, target, interrupted
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if SPELLINTERRUPTOTHERSELF then
|
||||||
|
interrupt_parser[SPELLINTERRUPTOTHERSELF] = function(d, source, spell, interrupted)
|
||||||
|
return source, spell, d.target, interrupted
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if SPELLINTERRUPTOTHEROTHER then
|
||||||
|
interrupt_parser[SPELLINTERRUPTOTHEROTHER] = function(d, source, spell, target, interrupted)
|
||||||
|
return source, spell, target, interrupted
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for pat in pairs(interrupt_parser) do
|
||||||
|
sanitize(pat)
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Dispel patterns
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
local dispel_strings = {}
|
||||||
|
local dispel_parser = {}
|
||||||
|
|
||||||
|
if AURADISPELSELF2 then
|
||||||
|
table.insert(dispel_strings, AURADISPELSELF2)
|
||||||
|
dispel_parser[AURADISPELSELF2] = function(d, spell, aura)
|
||||||
|
return d.source, spell, d.target, aura
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if AURADISPELOTHER2 then
|
||||||
|
table.insert(dispel_strings, AURADISPELOTHER2)
|
||||||
|
dispel_parser[AURADISPELOTHER2] = function(d, source, spell, target, aura)
|
||||||
|
return source, spell, target, aura
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for pat in pairs(dispel_parser) do
|
||||||
|
sanitize(pat)
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Main event handler
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
local defaults = {}
|
||||||
|
local absorb = ABSORB_TRAILER and sanitize(ABSORB_TRAILER) or nil
|
||||||
|
local resist = RESIST_TRAILER and sanitize(RESIST_TRAILER) or nil
|
||||||
|
local empty, physical, autohit = "", "physical", "Auto Hit"
|
||||||
|
local player = UnitName("player")
|
||||||
|
|
||||||
|
Parser:SetScript("OnEvent", function()
|
||||||
|
if not arg1 then return end
|
||||||
|
|
||||||
|
-- Death events
|
||||||
|
if event == "CHAT_MSG_COMBAT_FRIENDLY_DEATH" or event == "CHAT_MSG_COMBAT_HOSTILE_DEATH" then
|
||||||
|
if UNITDIESOTHER then
|
||||||
|
local deathPat = sanitize(UNITDIESOTHER)
|
||||||
|
local _, _, deadName = string.find(arg1, deathPat)
|
||||||
|
if deadName then
|
||||||
|
Parser:ProcessDeath(deadName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if UNITDIESSELF then
|
||||||
|
local selfPat = sanitize(UNITDIESSELF)
|
||||||
|
if string.find(arg1, selfPat) then
|
||||||
|
Parser:ProcessDeath(player)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Strip absorb/resist suffixes
|
||||||
|
if absorb then arg1 = string.gsub(arg1, absorb, empty) end
|
||||||
|
if resist then arg1 = string.gsub(arg1, resist, empty) end
|
||||||
|
|
||||||
|
defaults.source = player
|
||||||
|
defaults.target = player
|
||||||
|
defaults.school = physical
|
||||||
|
defaults.attack = autohit
|
||||||
|
defaults.spell = UNKNOWN or "Unknown"
|
||||||
|
defaults.value = 0
|
||||||
|
|
||||||
|
-- Check interrupt patterns first (they share spell damage events)
|
||||||
|
for _, pat in ipairs(interrupt_strings) do
|
||||||
|
local result, num, a1, a2, a3, a4, a5 = cfind(arg1, pat)
|
||||||
|
if result then
|
||||||
|
local src, spell, tgt, interrupted = interrupt_parser[pat](defaults, a1, a2, a3, a4, a5)
|
||||||
|
Parser:ProcessInterrupt(src, spell, tgt, interrupted)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check dispel patterns
|
||||||
|
for _, pat in ipairs(dispel_strings) do
|
||||||
|
local result, num, a1, a2, a3, a4, a5 = cfind(arg1, pat)
|
||||||
|
if result then
|
||||||
|
local src, spell, tgt, aura = dispel_parser[pat](defaults, a1, a2, a3, a4, a5)
|
||||||
|
Parser:ProcessDispel(src, spell, tgt, aura)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Standard combat log patterns
|
||||||
|
local patterns = combatlog_events[event]
|
||||||
|
if not patterns then return end
|
||||||
|
|
||||||
|
for _, pattern in pairs(patterns) do
|
||||||
|
local result, num, a1, a2, a3, a4, a5 = cfind(arg1, pattern)
|
||||||
|
if result then
|
||||||
|
local source, spell, target, value, school, dtype = combatlog_parser[pattern](defaults, a1, a2, a3, a4, a5)
|
||||||
|
if dtype == "damage" then
|
||||||
|
Parser:ProcessDamage(source, spell, target, value, school)
|
||||||
|
elseif dtype == "heal" then
|
||||||
|
Parser:ProcessHealing(source, spell, target, value, school)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
69
Tooltip.lua
Normal file
69
Tooltip.lua
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
local DataStore = NanamiDPS.DataStore
|
||||||
|
local L = NanamiDPS.L
|
||||||
|
|
||||||
|
local Tooltip = {}
|
||||||
|
NanamiDPS.Tooltip = Tooltip
|
||||||
|
|
||||||
|
function Tooltip:ShowBar(bar, window)
|
||||||
|
if not bar or not bar.barData then return end
|
||||||
|
local data = bar.barData
|
||||||
|
local moduleName = window.activeModuleName
|
||||||
|
local mod = NanamiDPS.modules[moduleName]
|
||||||
|
|
||||||
|
GameTooltip:SetOwner(bar, "ANCHOR_RIGHT")
|
||||||
|
|
||||||
|
if mod and mod.GetTooltip then
|
||||||
|
local seg = DataStore:GetSegment(window.segmentIndex or 1)
|
||||||
|
mod:GetTooltip(data.id or data.name, seg, GameTooltip)
|
||||||
|
else
|
||||||
|
GameTooltip:AddLine(data.name or L["Unknown"])
|
||||||
|
if data.value then
|
||||||
|
GameTooltip:AddDoubleLine("|cffffffff" .. L["Total Amount"], "|cffffffff" .. NanamiDPS.formatNumber(data.value))
|
||||||
|
end
|
||||||
|
if data.perSecond then
|
||||||
|
GameTooltip:AddDoubleLine("|cffffffff" .. L["Per Second"], "|cffffffff" .. NanamiDPS.round(data.perSecond, 1))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
GameTooltip:AddLine(" ")
|
||||||
|
GameTooltip:AddLine("|cff888888" .. L["Click to view details"])
|
||||||
|
GameTooltip:Show()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Tooltip:Hide()
|
||||||
|
GameTooltip:Hide()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Tooltip:ShowSpellDetail(playerName, spells, effectiveSpells, totalSum, effectiveSum, tooltip)
|
||||||
|
tooltip = tooltip or GameTooltip
|
||||||
|
|
||||||
|
if not spells then return end
|
||||||
|
|
||||||
|
tooltip:AddLine(" ")
|
||||||
|
tooltip:AddLine("|cffffd100" .. L["Details"] .. ":")
|
||||||
|
|
||||||
|
local sorted = {}
|
||||||
|
for spell, amount in pairs(spells) do
|
||||||
|
table.insert(sorted, { spell = spell, amount = amount })
|
||||||
|
end
|
||||||
|
table.sort(sorted, function(a, b) return a.amount > b.amount end)
|
||||||
|
|
||||||
|
for _, entry in ipairs(sorted) do
|
||||||
|
local pct = totalSum > 0 and NanamiDPS.round(entry.amount / totalSum * 100, 1) or 0
|
||||||
|
local rightStr
|
||||||
|
|
||||||
|
if effectiveSpells and effectiveSpells[entry.spell] then
|
||||||
|
local eff = effectiveSpells[entry.spell]
|
||||||
|
local overheal = entry.amount - eff
|
||||||
|
rightStr = string.format("|cffcc8888+%s |cffffffff%s (%.1f%%)",
|
||||||
|
NanamiDPS.formatNumber(overheal),
|
||||||
|
NanamiDPS.formatNumber(eff), pct)
|
||||||
|
else
|
||||||
|
rightStr = string.format("|cffffffff%s (%.1f%%)",
|
||||||
|
NanamiDPS.formatNumber(entry.amount), pct)
|
||||||
|
end
|
||||||
|
|
||||||
|
tooltip:AddDoubleLine("|cffffffff" .. entry.spell, rightStr)
|
||||||
|
end
|
||||||
|
end
|
||||||
87
Utils.lua
Normal file
87
Utils.lua
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
local NanamiDPS = NanamiDPS
|
||||||
|
|
||||||
|
function NanamiDPS.round(input, places)
|
||||||
|
if not places then places = 0 end
|
||||||
|
if type(input) == "number" and type(places) == "number" then
|
||||||
|
local pow = 1
|
||||||
|
for i = 1, places do pow = pow * 10 end
|
||||||
|
return floor(input * pow + 0.5) / pow
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function NanamiDPS.trim(str)
|
||||||
|
if not str then return "" end
|
||||||
|
return gsub(str, "^%s*(.-)%s*$", "%1")
|
||||||
|
end
|
||||||
|
|
||||||
|
function NanamiDPS.spairs(t, order)
|
||||||
|
local keys = {}
|
||||||
|
for k in pairs(t) do
|
||||||
|
table.insert(keys, k)
|
||||||
|
end
|
||||||
|
if order then
|
||||||
|
table.sort(keys, function(a, b) return order(t, a, b) end)
|
||||||
|
else
|
||||||
|
table.sort(keys)
|
||||||
|
end
|
||||||
|
local i = 0
|
||||||
|
return function()
|
||||||
|
i = i + 1
|
||||||
|
if keys[i] then
|
||||||
|
return keys[i], t[keys[i]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local rgbcache = {}
|
||||||
|
function NanamiDPS.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
|
||||||
|
|
||||||
|
function NanamiDPS.formatNumber(num)
|
||||||
|
if not num then return "0" end
|
||||||
|
if num >= 1000000 then
|
||||||
|
return NanamiDPS.round(num / 1000000, 1) .. "M"
|
||||||
|
elseif num >= 1000 then
|
||||||
|
return NanamiDPS.round(num / 1000, 1) .. "K"
|
||||||
|
end
|
||||||
|
return tostring(math.floor(num))
|
||||||
|
end
|
||||||
|
|
||||||
|
function NanamiDPS.formatTime(seconds)
|
||||||
|
if not seconds or seconds <= 0 then return "0:00" end
|
||||||
|
local m = math.floor(seconds / 60)
|
||||||
|
local s = math.floor(seconds - m * 60)
|
||||||
|
if s < 10 then
|
||||||
|
return m .. ":0" .. s
|
||||||
|
end
|
||||||
|
return m .. ":" .. s
|
||||||
|
end
|
||||||
|
|
||||||
|
function NanamiDPS.GetClassColor(class)
|
||||||
|
if class and RAID_CLASS_COLORS and RAID_CLASS_COLORS[class] then
|
||||||
|
return RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b
|
||||||
|
end
|
||||||
|
return 0.6, 0.6, 0.6
|
||||||
|
end
|
||||||
|
|
||||||
|
NanamiDPS.validClasses = {
|
||||||
|
WARRIOR = true, MAGE = true, ROGUE = true, DRUID = true, HUNTER = true,
|
||||||
|
SHAMAN = true, PRIEST = true, WARLOCK = true, PALADIN = true,
|
||||||
|
}
|
||||||
1349
Window.lua
Normal file
1349
Window.lua
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user