local NanamiDPS = NanamiDPS local DataStore = NanamiDPS.DataStore local L = NanamiDPS.L local ThreatEstimate = {} function ThreatEstimate:GetName() return L["Threat (Est.)"] end ------------------------------------------------------------------------------- -- Resolve pet owner for display ------------------------------------------------------------------------------- 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 ------------------------------------------------------------------------------- -- Color helpers for OT danger level ------------------------------------------------------------------------------- local function GetThreatColor(pct) if pct >= 80 then local f = math.min((pct - 80) / 20, 1.0) return 1.0, 0.2 * (1 - f), 0 elseif pct >= 50 then local f = (pct - 50) / 30 return 1.0, 1.0 - 0.6 * f, 0 else local f = pct / 50 return 0.2 + 0.8 * f, 1.0, 0.2 * (1 - f) end end ------------------------------------------------------------------------------- -- GetBars: Primary bar data provider for the module system -- Prefers ThreatEngine live data (Track 1 API or Track 2 local), -- falls back to legacy DataStore accumulation if engine has no data. ------------------------------------------------------------------------------- function ThreatEstimate:GetBars(segment) local bars = {} local TE = NanamiDPS.ThreatEngine local TC = NanamiDPS.ThreatCoefficients local targetKey = TE and TE:GetActiveTargetKey() or nil local threatList = targetKey and TE:GetThreatList(targetKey) or nil if threatList and table.getn(threatList) > 0 then local otStatus = TE:GetOTStatus(targetKey) local tankThreat = otStatus and otStatus.tankThreat or 0 for _, entry in pairs(threatList) do local class = DataStore:GetClass(entry.name) local r, g, b = NanamiDPS.GetClassColor(class) local displayName = entry.name local ownerName, ownerClass = ResolvePetOwner(entry.name) if ownerName and ownerClass then r, g, b = NanamiDPS.GetClassColor(ownerClass) r, g, b = r * 0.7, g * 0.7, b * 0.7 displayName = entry.name .. " <" .. ownerName .. ">" elseif not NanamiDPS.validClasses[class] and class ~= nil then r, g, b = NanamiDPS.str2rgb(entry.name) r = r * 0.6 + 0.4 g = g * 0.6 + 0.4 b = b * 0.6 + 0.4 end local suffix = "" if entry.isTanking then suffix = " [T]" end local tpsStr = "" if entry.tps and entry.tps > 0 then tpsStr = " (" .. NanamiDPS.formatNumber(entry.tps) .. " TPS)" end table.insert(bars, { id = entry.name, name = displayName .. suffix, value = entry.threat, valueText = NanamiDPS.formatNumber(entry.threat) .. tpsStr, class = ownerClass or class, r = r, g = g, b = b, percent = entry.relativePercent or 0, isTanking = entry.isTanking, threatPct = entry.perc or 0, }) end return bars end if not segment or not segment.data or not segment.data.threat then return {} end 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 -- 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 ------------------------------------------------------------------------------- -- Tooltip: show detailed threat breakdown ------------------------------------------------------------------------------- function ThreatEstimate:GetTooltip(playerName, segment, tooltip) local TE = NanamiDPS.ThreatEngine local targetKey = TE and TE:GetActiveTargetKey() or nil if targetKey then local td = TE.targets[targetKey] if td and td.players[playerName] then local pd = td.players[playerName] tooltip:AddLine("|cffffd100" .. playerName) tooltip:AddDoubleLine("|cffffffff" .. L["Threat"], "|cffffffff" .. NanamiDPS.formatNumber(pd.threat)) if pd.tps and pd.tps > 0 then tooltip:AddDoubleLine("|cffffffff" .. L["TPS"], "|cffffffff" .. NanamiDPS.formatNumber(pd.tps)) end if pd.isTanking then tooltip:AddLine("|cff00ff00" .. L["Has Aggro"]) end local otStatus = TE:GetOTStatus(targetKey) if otStatus and otStatus.tankName then tooltip:AddLine(" ") tooltip:AddDoubleLine("|cffffffff" .. L["Tank"], "|cffffffff" .. otStatus.tankName) tooltip:AddDoubleLine("|cffffffff" .. L["Tank Threat"], "|cffffffff" .. NanamiDPS.formatNumber(otStatus.tankThreat)) local threshold = pd.isMelee and "110%" or "130%" tooltip:AddDoubleLine("|cffffffff" .. L["OT Threshold"], "|cffffffff" .. threshold) if not pd.isTanking then local myOT = otStatus.tankThreat * (pd.isMelee and 1.1 or 1.3) local buffer = myOT - pd.threat local pct = myOT > 0 and (pd.threat / myOT * 100) or 0 local pr, pg, pb = GetThreatColor(pct) tooltip:AddDoubleLine( "|cffffffff" .. L["OT Buffer"], string.format("|cff%02x%02x%02x%s (%.0f%%)", pr * 255, pg * 255, pb * 255, NanamiDPS.formatNumber(buffer), pct)) end end tooltip:AddLine(" ") if td.source == "api" then tooltip:AddLine("|cff00ff00" .. L["Threat Source API"]) else tooltip:AddLine("|cffaaaaaa" .. L["Threat Source Local"]) end return end end 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 ------------------------------------------------------------------------------- -- Report ------------------------------------------------------------------------------- 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)