调整dps插件对仇恨的估算方式
优化dps插件
This commit is contained in:
384
ThreatDisplay.lua
Normal file
384
ThreatDisplay.lua
Normal file
@@ -0,0 +1,384 @@
|
||||
local NanamiDPS = NanamiDPS
|
||||
local TE = NanamiDPS.ThreatEngine
|
||||
local TC = NanamiDPS.ThreatCoefficients
|
||||
local L = NanamiDPS.L
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- ThreatDisplay: Visual warning system, nameplate anchoring, role views
|
||||
--
|
||||
-- 1. Full-screen edge glow when approaching OT threshold
|
||||
-- 2. Sound warning at critical threat levels
|
||||
-- 3. Nameplate threat indicators (SuperAPI dependent)
|
||||
-- 4. Tank Mode / Healer Mode smart views
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local TD = CreateFrame("Frame", "NanamiDPSThreatDisplay", UIParent)
|
||||
NanamiDPS.ThreatDisplay = TD
|
||||
|
||||
local _floor = math.floor
|
||||
local _min = math.min
|
||||
local _max = math.max
|
||||
|
||||
TD.warningState = "SAFE"
|
||||
TD.lastWarnSound = 0
|
||||
TD.lastGlowUpdate = 0
|
||||
TD.nameplateFrames = {}
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Full-screen glow overlay
|
||||
-------------------------------------------------------------------------------
|
||||
local glowFrame = CreateFrame("Frame", "NanamiDPS_OTGlow", UIParent)
|
||||
glowFrame:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
glowFrame:SetAllPoints(UIParent)
|
||||
glowFrame:EnableMouse(false)
|
||||
glowFrame:Hide()
|
||||
|
||||
local glowTop = glowFrame:CreateTexture(nil, "OVERLAY")
|
||||
glowTop:SetTexture(1, 0, 0)
|
||||
glowTop:SetPoint("TOPLEFT", glowFrame, "TOPLEFT")
|
||||
glowTop:SetPoint("TOPRIGHT", glowFrame, "TOPRIGHT")
|
||||
glowTop:SetHeight(40)
|
||||
|
||||
local glowBottom = glowFrame:CreateTexture(nil, "OVERLAY")
|
||||
glowBottom:SetTexture(1, 0, 0)
|
||||
glowBottom:SetPoint("BOTTOMLEFT", glowFrame, "BOTTOMLEFT")
|
||||
glowBottom:SetPoint("BOTTOMRIGHT", glowFrame, "BOTTOMRIGHT")
|
||||
glowBottom:SetHeight(40)
|
||||
|
||||
local glowLeft = glowFrame:CreateTexture(nil, "OVERLAY")
|
||||
glowLeft:SetTexture(1, 0, 0)
|
||||
glowLeft:SetPoint("TOPLEFT", glowFrame, "TOPLEFT")
|
||||
glowLeft:SetPoint("BOTTOMLEFT", glowFrame, "BOTTOMLEFT")
|
||||
glowLeft:SetWidth(30)
|
||||
|
||||
local glowRight = glowFrame:CreateTexture(nil, "OVERLAY")
|
||||
glowRight:SetTexture(1, 0, 0)
|
||||
glowRight:SetPoint("TOPRIGHT", glowFrame, "TOPRIGHT")
|
||||
glowRight:SetPoint("BOTTOMRIGHT", glowFrame, "BOTTOMRIGHT")
|
||||
glowRight:SetWidth(30)
|
||||
|
||||
local glowAlpha = 0
|
||||
local glowDir = 1
|
||||
local GLOW_SPEED = 2.5
|
||||
local GLOW_MAX = 0.55
|
||||
local GLOW_MIN = 0.05
|
||||
|
||||
glowFrame:SetScript("OnUpdate", function()
|
||||
local dt = arg1 or 0.016
|
||||
glowAlpha = glowAlpha + glowDir * GLOW_SPEED * dt
|
||||
if glowAlpha >= GLOW_MAX then
|
||||
glowAlpha = GLOW_MAX
|
||||
glowDir = -1
|
||||
elseif glowAlpha <= GLOW_MIN then
|
||||
glowAlpha = GLOW_MIN
|
||||
glowDir = 1
|
||||
end
|
||||
glowTop:SetAlpha(glowAlpha)
|
||||
glowBottom:SetAlpha(glowAlpha)
|
||||
glowLeft:SetAlpha(glowAlpha * 0.7)
|
||||
glowRight:SetAlpha(glowAlpha * 0.7)
|
||||
end)
|
||||
|
||||
function TD:ShowGlow()
|
||||
glowAlpha = GLOW_MIN
|
||||
glowDir = 1
|
||||
glowFrame:Show()
|
||||
end
|
||||
|
||||
function TD:HideGlow()
|
||||
glowFrame:Hide()
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Warning text overlay (center screen)
|
||||
-------------------------------------------------------------------------------
|
||||
local warnFrame = CreateFrame("Frame", "NanamiDPS_OTWarn", UIParent)
|
||||
warnFrame:SetFrameStrata("HIGH")
|
||||
warnFrame:SetWidth(300)
|
||||
warnFrame:SetHeight(50)
|
||||
warnFrame:SetPoint("TOP", UIParent, "TOP", 0, -180)
|
||||
warnFrame:EnableMouse(false)
|
||||
warnFrame:Hide()
|
||||
|
||||
local warnText = warnFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
|
||||
warnText:SetAllPoints()
|
||||
warnText:SetFont(STANDARD_TEXT_FONT, 22, "OUTLINE")
|
||||
warnText:SetTextColor(1, 0.2, 0)
|
||||
|
||||
local warnFadeTimer = 0
|
||||
|
||||
warnFrame:SetScript("OnUpdate", function()
|
||||
local dt = arg1 or 0.016
|
||||
warnFadeTimer = warnFadeTimer - dt
|
||||
if warnFadeTimer <= 0 then
|
||||
warnFrame:Hide()
|
||||
elseif warnFadeTimer < 1 then
|
||||
warnFrame:SetAlpha(warnFadeTimer)
|
||||
end
|
||||
end)
|
||||
|
||||
function TD:FlashWarning(text)
|
||||
warnText:SetText(text)
|
||||
warnFrame:SetAlpha(1)
|
||||
warnFadeTimer = 3.0
|
||||
warnFrame:Show()
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Main update loop: assess OT danger and trigger warnings
|
||||
-------------------------------------------------------------------------------
|
||||
local WARNING_SOUND = "Sound\\Doodad\\BellTollAlliance.wav"
|
||||
local WARN_COOLDOWN = 3.0
|
||||
|
||||
function TD:Update()
|
||||
if not TE or not TE.inCombat then
|
||||
self:HideGlow()
|
||||
self.warningState = "SAFE"
|
||||
return
|
||||
end
|
||||
|
||||
local config = NanamiDPS.config
|
||||
if not config then return end
|
||||
if not config.otWarning then
|
||||
self:HideGlow()
|
||||
self.warningState = "SAFE"
|
||||
return
|
||||
end
|
||||
|
||||
local targetKey = TE:GetActiveTargetKey()
|
||||
if not targetKey then
|
||||
self:HideGlow()
|
||||
self.warningState = "SAFE"
|
||||
return
|
||||
end
|
||||
|
||||
local otStatus = TE:GetOTStatus(targetKey)
|
||||
if not otStatus or not otStatus.tankName then
|
||||
self:HideGlow()
|
||||
self.warningState = "SAFE"
|
||||
return
|
||||
end
|
||||
|
||||
local pct = otStatus.pct or 0
|
||||
local now = GetTime()
|
||||
|
||||
if pct >= 90 then
|
||||
if self.warningState ~= "CRITICAL" then
|
||||
self.warningState = "CRITICAL"
|
||||
if (now - self.lastWarnSound) >= WARN_COOLDOWN then
|
||||
PlaySoundFile(WARNING_SOUND)
|
||||
self.lastWarnSound = now
|
||||
end
|
||||
self:FlashWarning(L["OT Warning Critical"])
|
||||
end
|
||||
self:ShowGlow()
|
||||
elseif pct >= 70 then
|
||||
if self.warningState ~= "DANGER" then
|
||||
self.warningState = "DANGER"
|
||||
self:FlashWarning(L["OT Warning Danger"])
|
||||
end
|
||||
self:HideGlow()
|
||||
elseif pct >= 50 then
|
||||
self.warningState = "CAUTION"
|
||||
self:HideGlow()
|
||||
else
|
||||
self.warningState = "SAFE"
|
||||
self:HideGlow()
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Nameplate threat indicators (SuperAPI / pfUI integration)
|
||||
-- Attaches small threat % text to enemy nameplates when available.
|
||||
-------------------------------------------------------------------------------
|
||||
local NP_UPDATE_INTERVAL = 0.5
|
||||
TD.lastNPUpdate = 0
|
||||
|
||||
function TD:UpdateNameplates()
|
||||
if not TE or not TE.inCombat then
|
||||
self:HideAllNameplateIndicators()
|
||||
return
|
||||
end
|
||||
|
||||
local config = NanamiDPS.config
|
||||
if not config or not config.nameplateThreat then return end
|
||||
|
||||
local now = GetTime()
|
||||
if (now - self.lastNPUpdate) < NP_UPDATE_INTERVAL then return end
|
||||
self.lastNPUpdate = now
|
||||
|
||||
local worldFrame = WorldFrame
|
||||
if not worldFrame then return end
|
||||
|
||||
if not worldFrame.GetChildren then return end
|
||||
local children = { worldFrame:GetChildren() }
|
||||
for i = 1, table.getn(children) do
|
||||
local child = children[i]
|
||||
if child and child:IsVisible() and child.GetName and child:GetName() == nil then
|
||||
self:ProcessNameplate(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TD:HideAllNameplateIndicators()
|
||||
for name, indicator in pairs(self.nameplateFrames) do
|
||||
if indicator and indicator.SetText then
|
||||
indicator:SetText("")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TD:ProcessNameplate(frame)
|
||||
if not frame or not frame:IsVisible() then return end
|
||||
|
||||
local regions = { frame:GetRegions() }
|
||||
local nameRegion = nil
|
||||
for _, region in pairs(regions) do
|
||||
if region:GetObjectType() == "FontString" then
|
||||
local text = region:GetText()
|
||||
if text and text ~= "" then
|
||||
nameRegion = region
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not nameRegion then return end
|
||||
|
||||
local npName = nameRegion:GetText()
|
||||
if not npName then return end
|
||||
|
||||
local indicator = self.nameplateFrames[npName]
|
||||
if not indicator then
|
||||
indicator = frame:CreateFontString(nil, "OVERLAY")
|
||||
indicator:SetFont(STANDARD_TEXT_FONT, 10, "OUTLINE")
|
||||
indicator:SetPoint("TOP", nameRegion, "BOTTOM", 0, -2)
|
||||
self.nameplateFrames[npName] = indicator
|
||||
end
|
||||
|
||||
local playerName = TE.playerName
|
||||
local targetKey = nil
|
||||
|
||||
for key, td in pairs(TE.targets) do
|
||||
if td.players[playerName] then
|
||||
targetKey = key
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not targetKey then
|
||||
indicator:SetText("")
|
||||
return
|
||||
end
|
||||
|
||||
local td = TE.targets[targetKey]
|
||||
if not td then
|
||||
indicator:SetText("")
|
||||
return
|
||||
end
|
||||
|
||||
local pd = td.players[playerName]
|
||||
if not pd then
|
||||
indicator:SetText("")
|
||||
return
|
||||
end
|
||||
|
||||
local pct = pd.perc or 0
|
||||
local r, g, b = 0.2, 1, 0.2
|
||||
if pct >= 80 then
|
||||
r, g, b = 1, 0.2, 0
|
||||
elseif pct >= 50 then
|
||||
r, g, b = 1, 1, 0
|
||||
end
|
||||
|
||||
indicator:SetTextColor(r, g, b)
|
||||
indicator:SetText(string.format("%.0f%%", pct))
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Tank Mode: Show which mobs have loose threat
|
||||
-- Returns a summary table for UI consumption
|
||||
-------------------------------------------------------------------------------
|
||||
function TD:GetTankModeSummary()
|
||||
if not TE then return {} end
|
||||
|
||||
local summary = {}
|
||||
local myName = TE.playerName
|
||||
|
||||
for targetKey, td in pairs(TE.targets) do
|
||||
if td.tankName and td.tankName ~= myName then
|
||||
local myData = td.players[myName]
|
||||
local tankData = td.players[td.tankName]
|
||||
|
||||
if myData and tankData then
|
||||
table.insert(summary, {
|
||||
targetKey = targetKey,
|
||||
tankName = td.tankName,
|
||||
myThreat = myData.threat,
|
||||
tankThreat = tankData.threat,
|
||||
gap = tankData.threat - myData.threat,
|
||||
pct = tankData.threat > 0 and (myData.threat / tankData.threat * 100) or 0,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(summary, function(a, b) return a.pct > b.pct end)
|
||||
return summary
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Healer Mode: Show which mobs don't have solid tank threat
|
||||
-------------------------------------------------------------------------------
|
||||
function TD:GetHealerModeSummary(masterTankName)
|
||||
if not TE or not masterTankName then return {} end
|
||||
|
||||
local summary = {}
|
||||
|
||||
for targetKey, td in pairs(TE.targets) do
|
||||
local tankData = td.players[masterTankName]
|
||||
if not tankData or not td.tankName or td.tankName ~= masterTankName then
|
||||
local topThreat = 0
|
||||
local topName = ""
|
||||
for name, pd in pairs(td.players) do
|
||||
if pd.threat > topThreat then
|
||||
topThreat = pd.threat
|
||||
topName = name
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(summary, {
|
||||
targetKey = targetKey,
|
||||
topThreatName = topName,
|
||||
topThreat = topThreat,
|
||||
tankThreat = tankData and tankData.threat or 0,
|
||||
isLoose = td.tankName ~= masterTankName,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(summary, function(a, b) return a.tankThreat < b.tankThreat end)
|
||||
return summary
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Hook into ThreatEngine update cycle
|
||||
-------------------------------------------------------------------------------
|
||||
NanamiDPS:RegisterCallback("threat_update", "ThreatDisplay", function()
|
||||
TD:Update()
|
||||
end)
|
||||
|
||||
TD:SetScript("OnUpdate", function()
|
||||
if not TE or not TE.inCombat then
|
||||
if TD.nameplateFrames then
|
||||
TD:HideAllNameplateIndicators()
|
||||
end
|
||||
return
|
||||
end
|
||||
local config = NanamiDPS.config
|
||||
if not config or not config.nameplateThreat then return end
|
||||
local now = GetTime()
|
||||
if (now - (TD.lastNPUpdate or 0)) >= NP_UPDATE_INTERVAL then
|
||||
TD:UpdateNameplates()
|
||||
end
|
||||
end)
|
||||
Reference in New Issue
Block a user