Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28ef2ed7c9 | |||
| d0329d309e | |||
| 77154b7b11 |
@@ -1,4 +1,4 @@
|
|||||||
<img src="https://cdn.discordapp.com/attachments/1510333697277300856/1522457461569622156/image.png?ex=6a488a80&is=6a473900&hm=0cf7bd7efde50648b2d06345995cdd0a30174335f484aae51ad76026f45bb358&"/>
|
<img src="https://cdn.discordapp.com/attachments/1510333697277300856/1522484471616634943/fff.png?ex=6a51de28&is=6a508ca8&hm=63ff5877df99f9df865b93da3528822af42de0d386126f4ccc465c728b00bed1&"/>
|
||||||
|
|
||||||
* It's recommended to use the KUI-NAMEPLATES addon as it's designed to work with that.
|
* It's recommended to use the KUI-NAMEPLATES addon as it's designed to work with that.
|
||||||
* You can find it here.
|
* You can find it here.
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
## Interface: 11200
|
## Interface: 11200
|
||||||
## Title: RelationshipsThreatPlates - OctoWoW
|
## Title: RelationshipsThreatPlates - OctoWoW
|
||||||
## Notes: OctoWoW-compatible RelationshipsThreatPlates
|
## Notes: OctoWoW-compatible RelationshipsThreatPlates
|
||||||
## Author: Packaged By Relationship
|
## Author: Relationship
|
||||||
## Version: 1.2
|
## Version: 1.4
|
||||||
## SavedVariables: RelationshipsThreatPlatesDB
|
## SavedVariables: RelationshipsThreatPlatesDB
|
||||||
## X-Website: https://github.com/Relationship-OctoWoW/RelationshipsThreatPlates
|
## X-Website: https://github.com/Relationship-OctoWoW/RelationshipsThreatPlates
|
||||||
## X-GitHub: https://github.com/Relationship-OctoWoW/RelationshipsThreatPlates
|
## X-GitHub: https://github.com/Relationship-OctoWoW/RelationshipsThreatPlates
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ addon.MAJOR = 1
|
|||||||
addon.MINOR = 1
|
addon.MINOR = 1
|
||||||
|
|
||||||
-- Version string
|
-- Version string
|
||||||
TP_VERSION = "2.4.0"
|
TP_VERSION = "2.6.1"
|
||||||
TP_ADDON_NAME = "RelationshipsThreatPlates"
|
TP_ADDON_NAME = "RelationshipsThreatPlates"
|
||||||
|
|
||||||
-- ==================================================================
|
-- ==================================================================
|
||||||
@@ -40,6 +40,19 @@ local DEFAULT_CONFIG = {
|
|||||||
glowEnabled = false,
|
glowEnabled = false,
|
||||||
glowSize = 3,
|
glowSize = 3,
|
||||||
glowAsShadow = true,
|
glowAsShadow = true,
|
||||||
|
|
||||||
|
-- New in 2.6
|
||||||
|
combatOnly = false,
|
||||||
|
hideBelowPct = 0,
|
||||||
|
warningSound = false,
|
||||||
|
warningSoundName = "RaidWarning",
|
||||||
|
warningThreshold = 80,
|
||||||
|
flashOnAggro = false,
|
||||||
|
flashSpeed = 4,
|
||||||
|
textFormat = "percent", -- percent | tank | status | combo
|
||||||
|
colorPreset = "kui", -- kui | cool | mono | warband
|
||||||
|
lockGUI = false,
|
||||||
|
|
||||||
guiPos = {
|
guiPos = {
|
||||||
point = "CENTER",
|
point = "CENTER",
|
||||||
relPoint = "CENTER",
|
relPoint = "CENTER",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ local C = {
|
|||||||
|
|
||||||
-- Layout
|
-- Layout
|
||||||
local GUI_WIDTH = 320
|
local GUI_WIDTH = 320
|
||||||
local GUI_HEIGHT = 480
|
local GUI_HEIGHT = 520
|
||||||
local HEADER_H = 30
|
local HEADER_H = 30
|
||||||
local MARGIN = 14
|
local MARGIN = 14
|
||||||
local FONT_NORMAL = "Fonts\\FRIZQT__.TTF"
|
local FONT_NORMAL = "Fonts\\FRIZQT__.TTF"
|
||||||
@@ -44,6 +44,34 @@ local FONT_SIZE = 10
|
|||||||
-- Widget references for refresh
|
-- Widget references for refresh
|
||||||
local widgets = {}
|
local widgets = {}
|
||||||
|
|
||||||
|
-- Only settings that change the visual STRUCTURE of a bar need a full
|
||||||
|
-- RecreateAll (which tears every bar down and re-attaches on the next
|
||||||
|
-- scan). Everything else — filters, modes, alpha, colours — just needs
|
||||||
|
-- a soft UpdateAll, which is far less likely to leave plates without
|
||||||
|
-- bars while pooled nameplate frames are hidden.
|
||||||
|
local NEEDS_RECREATE = {
|
||||||
|
barHeight = true,
|
||||||
|
barWidthScale= true,
|
||||||
|
barOffsetY = true,
|
||||||
|
barOffsetX = true,
|
||||||
|
glowEnabled = true,
|
||||||
|
glowAsShadow = true,
|
||||||
|
glowSize = true,
|
||||||
|
showSpark = true,
|
||||||
|
showFillBG = true,
|
||||||
|
showText = true,
|
||||||
|
fontOutline = true,
|
||||||
|
fontSize = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function ApplySettingChange(configKey)
|
||||||
|
if NEEDS_RECREATE[configKey] then
|
||||||
|
addon.Nameplates:RecreateAll()
|
||||||
|
else
|
||||||
|
addon.Nameplates:UpdateAll()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- ==================================================================
|
-- ==================================================================
|
||||||
-- Helper: safely set backdrop on a frame
|
-- Helper: safely set backdrop on a frame
|
||||||
-- ==================================================================
|
-- ==================================================================
|
||||||
@@ -302,7 +330,7 @@ function addon.GUI:BuildSettings(parent)
|
|||||||
else
|
else
|
||||||
this.checkMark:Hide()
|
this.checkMark:Hide()
|
||||||
end
|
end
|
||||||
addon.Nameplates:RecreateAll()
|
ApplySettingChange(configKey)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local label = parent:CreateFontString(nil, "OVERLAY")
|
local label = parent:CreateFontString(nil, "OVERLAY")
|
||||||
@@ -393,13 +421,87 @@ function addon.GUI:BuildSettings(parent)
|
|||||||
else
|
else
|
||||||
valText:SetText(math.floor(val + 0.5) .. suffix)
|
valText:SetText(math.floor(val + 0.5) .. suffix)
|
||||||
end
|
end
|
||||||
addon.Nameplates:RecreateAll()
|
ApplySettingChange(configKey)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
y = y + 24
|
y = y + 24
|
||||||
widgets[configKey] = slider
|
widgets[configKey] = slider
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- ==============================================================
|
||||||
|
-- Helper: Dropdown (uses Blizzard's UIDropDownMenuTemplate)
|
||||||
|
-- ==============================================================
|
||||||
|
local function Dropdown(labelText, configKey, options)
|
||||||
|
local label = parent:CreateFontString(nil, "OVERLAY")
|
||||||
|
label:SetFont(FONT_NORMAL, FONT_SIZE)
|
||||||
|
label:SetPoint("TOPLEFT", parent, "TOPLEFT", MARGIN, -y)
|
||||||
|
label:SetTextColor(unpack(C.TEXT))
|
||||||
|
label:SetText(labelText)
|
||||||
|
y = y + 16
|
||||||
|
|
||||||
|
local dd = CreateFrame("Frame", "RelationshipsThreatPlatesDD_" .. configKey, parent, "UIDropDownMenuTemplate")
|
||||||
|
dd:SetPoint("TOPLEFT", parent, "TOPLEFT", MARGIN - 14, -y)
|
||||||
|
UIDropDownMenu_SetWidth(160, dd)
|
||||||
|
|
||||||
|
local function GetLabelFor(val)
|
||||||
|
for i = 1, table.getn(options) do
|
||||||
|
if options[i].value == val then return options[i].label end
|
||||||
|
end
|
||||||
|
return tostring(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
UIDropDownMenu_Initialize(dd, function()
|
||||||
|
for i = 1, table.getn(options) do
|
||||||
|
local opt = options[i]
|
||||||
|
local info = {}
|
||||||
|
info.text = opt.label
|
||||||
|
info.value = opt.value
|
||||||
|
info.func = function()
|
||||||
|
RelationshipsThreatPlatesDB[configKey] = this.value
|
||||||
|
UIDropDownMenu_SetSelectedValue(dd, this.value)
|
||||||
|
UIDropDownMenu_SetText(GetLabelFor(this.value), dd)
|
||||||
|
ApplySettingChange(configKey)
|
||||||
|
end
|
||||||
|
info.checked = (RelationshipsThreatPlatesDB[configKey] == opt.value)
|
||||||
|
UIDropDownMenu_AddButton(info)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
UIDropDownMenu_SetSelectedValue(dd, RelationshipsThreatPlatesDB[configKey])
|
||||||
|
UIDropDownMenu_SetText(GetLabelFor(RelationshipsThreatPlatesDB[configKey]), dd)
|
||||||
|
|
||||||
|
y = y + 34
|
||||||
|
widgets[configKey] = dd
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ==============================================================
|
||||||
|
-- Helper: small action button
|
||||||
|
-- ==============================================================
|
||||||
|
local function ActionButton(labelText, onClick)
|
||||||
|
local btn = CreateFrame("Button", nil, parent)
|
||||||
|
btn:SetWidth(W - MARGIN * 2)
|
||||||
|
btn:SetHeight(20)
|
||||||
|
btn:SetPoint("TOPLEFT", parent, "TOPLEFT", MARGIN, -y)
|
||||||
|
SafeSetBackdrop(btn, {
|
||||||
|
bgFile = C.SOLID,
|
||||||
|
edgeFile = C.SOLID,
|
||||||
|
tile = false, tileSize = 0,
|
||||||
|
edgeSize = 1,
|
||||||
|
})
|
||||||
|
btn:SetBackdropColor(unpack(C.BUTTON_BG))
|
||||||
|
btn:SetBackdropBorderColor(unpack(C.BORDER))
|
||||||
|
local t = btn:CreateFontString(nil, "OVERLAY")
|
||||||
|
t:SetFont(FONT_NORMAL, FONT_SIZE, "OUTLINE")
|
||||||
|
t:SetPoint("CENTER", btn, "CENTER", 0, 0)
|
||||||
|
t:SetTextColor(unpack(C.BUTTON_TEXT))
|
||||||
|
t:SetText(labelText)
|
||||||
|
btn:SetFontString(t)
|
||||||
|
btn:SetScript("OnEnter", function() this:SetBackdropColor(unpack(C.BUTTON_HOVER)) end)
|
||||||
|
btn:SetScript("OnLeave", function() this:SetBackdropColor(unpack(C.BUTTON_BG)) end)
|
||||||
|
btn:SetScript("OnClick", onClick)
|
||||||
|
y = y + 24
|
||||||
|
return btn
|
||||||
|
end
|
||||||
|
|
||||||
-- ==============================================================
|
-- ==============================================================
|
||||||
-- GENERAL SECTION
|
-- GENERAL SECTION
|
||||||
-- ==============================================================
|
-- ==============================================================
|
||||||
@@ -441,6 +543,57 @@ function addon.GUI:BuildSettings(parent)
|
|||||||
CheckBox("Show on Neutral", "showOnNeutral", "Show threat bars on neutral units")
|
CheckBox("Show on Neutral", "showOnNeutral", "Show threat bars on neutral units")
|
||||||
CheckBox("Show on Friendly", "showOnFriendly", "Show threat bars on friendly units")
|
CheckBox("Show on Friendly", "showOnFriendly", "Show threat bars on friendly units")
|
||||||
|
|
||||||
|
-- ==============================================================
|
||||||
|
-- BEHAVIOR SECTION (2.6)
|
||||||
|
-- ==============================================================
|
||||||
|
SectionLabel("Behavior")
|
||||||
|
CheckBox("Combat Only", "combatOnly", "Only show bars while you are in combat")
|
||||||
|
Slider("Hide Below %", "hideBelowPct", 0, 90, 5, "%")
|
||||||
|
Dropdown("Text Format", "textFormat", {
|
||||||
|
{ label = "Percent (85%)", value = "percent" },
|
||||||
|
{ label = "Tank Lead (+123)", value = "tank" },
|
||||||
|
{ label = "Status (SAFE/PULL!)", value = "status" },
|
||||||
|
{ label = "Combo (85% +123)", value = "combo" },
|
||||||
|
})
|
||||||
|
Dropdown("Colour Preset", "colorPreset", {
|
||||||
|
{ label = "Kui (green -> red)", value = "kui" },
|
||||||
|
{ label = "Cool (blue -> red)", value = "cool" },
|
||||||
|
{ label = "Mono (grey -> red)", value = "mono" },
|
||||||
|
{ label = "Warband (cyan -> red)", value = "warband" },
|
||||||
|
})
|
||||||
|
|
||||||
|
-- ==============================================================
|
||||||
|
-- WARNINGS SECTION (2.6)
|
||||||
|
-- ==============================================================
|
||||||
|
SectionLabel("Warnings")
|
||||||
|
CheckBox("Flash Bar on Aggro", "flashOnAggro", "Pulse the bar when you have 100% aggro")
|
||||||
|
Slider("Flash Speed", "flashSpeed", 1, 10, 1, "")
|
||||||
|
CheckBox("Play Warning Sound", "warningSound", "Sound when threat crosses the threshold")
|
||||||
|
Slider("Warning Threshold", "warningThreshold", 40, 100, 5, "%")
|
||||||
|
Dropdown("Warning Sound", "warningSoundName", {
|
||||||
|
{ label = "Raid Warning", value = "RaidWarning" },
|
||||||
|
{ label = "Tell Message", value = "TellMessage" },
|
||||||
|
{ label = "Murloc Aggro", value = "MurlocAggroLocal" },
|
||||||
|
{ label = "Auction Open", value = "AuctionWindowOpen"},
|
||||||
|
{ label = "Level Up", value = "LEVELUP" },
|
||||||
|
})
|
||||||
|
ActionButton("Test Warning Sound", function()
|
||||||
|
local snd = RelationshipsThreatPlatesDB.warningSoundName or "RaidWarning"
|
||||||
|
pcall(PlaySound, snd)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- ==============================================================
|
||||||
|
-- WINDOW SECTION (2.6)
|
||||||
|
-- ==============================================================
|
||||||
|
SectionLabel("Config Window")
|
||||||
|
ActionButton("Reset Config Window Position", function()
|
||||||
|
RelationshipsThreatPlatesDB.guiPos = { point = "CENTER", relPoint = "CENTER", x = 0, y = 0 }
|
||||||
|
if RelationshipsThreatPlatesGUI then
|
||||||
|
RelationshipsThreatPlatesGUI:ClearAllPoints()
|
||||||
|
RelationshipsThreatPlatesGUI:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
-- ==============================================================
|
-- ==============================================================
|
||||||
-- Reset button at bottom
|
-- Reset button at bottom
|
||||||
-- ==============================================================
|
-- ==============================================================
|
||||||
|
|||||||
@@ -69,6 +69,37 @@ function addon.Media.Brighten(factor, r, g, b, a)
|
|||||||
a
|
a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- ==================================================================
|
||||||
|
-- Colour presets for the threat gradient (2.6)
|
||||||
|
-- Each preset defines 4 stops: low (0%), medium (50%), high (80%), aggro (100%)
|
||||||
|
-- ==================================================================
|
||||||
|
addon.Media.COLOR_PRESETS = {
|
||||||
|
kui = {
|
||||||
|
low = { 0.33, 0.80, 0.33 },
|
||||||
|
medium = { 1.00, 0.85, 0.00 },
|
||||||
|
high = { 1.00, 0.50, 0.00 },
|
||||||
|
aggro = { 1.00, 0.10, 0.10 },
|
||||||
|
},
|
||||||
|
cool = {
|
||||||
|
low = { 0.20, 0.55, 1.00 },
|
||||||
|
medium = { 0.55, 0.35, 1.00 },
|
||||||
|
high = { 1.00, 0.35, 0.55 },
|
||||||
|
aggro = { 1.00, 0.10, 0.10 },
|
||||||
|
},
|
||||||
|
mono = {
|
||||||
|
low = { 0.55, 0.55, 0.55 },
|
||||||
|
medium = { 0.80, 0.80, 0.80 },
|
||||||
|
high = { 1.00, 1.00, 1.00 },
|
||||||
|
aggro = { 1.00, 0.20, 0.20 },
|
||||||
|
},
|
||||||
|
warband = {
|
||||||
|
low = { 0.20, 0.90, 0.90 },
|
||||||
|
medium = { 0.60, 0.90, 0.30 },
|
||||||
|
high = { 1.00, 0.80, 0.20 },
|
||||||
|
aggro = { 1.00, 0.10, 0.10 },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
-- ==================================================================
|
-- ==================================================================
|
||||||
-- Helper: get smooth interpolated threat colour
|
-- Helper: get smooth interpolated threat colour
|
||||||
-- ==================================================================
|
-- ==================================================================
|
||||||
@@ -76,25 +107,28 @@ function addon.Media.GetThreatColour(pct)
|
|||||||
pct = tonumber(pct) or 0
|
pct = tonumber(pct) or 0
|
||||||
pct = math.min(100, math.max(0, pct))
|
pct = math.min(100, math.max(0, pct))
|
||||||
|
|
||||||
local c = addon.Media.COLOURS
|
local presetName = (RelationshipsThreatPlatesDB and RelationshipsThreatPlatesDB.colorPreset) or "kui"
|
||||||
|
local p = addon.Media.COLOR_PRESETS[presetName] or addon.Media.COLOR_PRESETS.kui
|
||||||
|
local lo, md, hi, ag = p.low, p.medium, p.high, p.aggro
|
||||||
local r, g, b
|
local r, g, b
|
||||||
|
|
||||||
if pct < 50 then
|
if pct < 50 then
|
||||||
local t = pct / 50
|
local t = pct / 50
|
||||||
r = c.THREAT_LOW.r + (c.THREAT_MEDIUM.r - c.THREAT_LOW.r) * t
|
r = lo[1] + (md[1] - lo[1]) * t
|
||||||
g = c.THREAT_LOW.g + (c.THREAT_MEDIUM.g - c.THREAT_LOW.g) * t
|
g = lo[2] + (md[2] - lo[2]) * t
|
||||||
b = c.THREAT_LOW.b + (c.THREAT_MEDIUM.b - c.THREAT_LOW.b) * t
|
b = lo[3] + (md[3] - lo[3]) * t
|
||||||
elseif pct < 80 then
|
elseif pct < 80 then
|
||||||
local t = (pct - 50) / 30
|
local t = (pct - 50) / 30
|
||||||
r = c.THREAT_MEDIUM.r + (c.THREAT_HIGH.r - c.THREAT_MEDIUM.r) * t
|
r = md[1] + (hi[1] - md[1]) * t
|
||||||
g = c.THREAT_MEDIUM.g + (c.THREAT_HIGH.g - c.THREAT_MEDIUM.g) * t
|
g = md[2] + (hi[2] - md[2]) * t
|
||||||
b = c.THREAT_MEDIUM.b + (c.THREAT_HIGH.b - c.THREAT_MEDIUM.b) * t
|
b = md[3] + (hi[3] - md[3]) * t
|
||||||
else
|
else
|
||||||
local t = (pct - 80) / 20
|
local t = (pct - 80) / 20
|
||||||
t = math.min(1, math.max(0, t))
|
if t > 1 then t = 1 end
|
||||||
r = c.THREAT_HIGH.r + (c.THREAT_AGGRO.r - c.THREAT_HIGH.r) * t
|
if t < 0 then t = 0 end
|
||||||
g = c.THREAT_HIGH.g + (c.THREAT_AGGRO.g - c.THREAT_HIGH.g) * t
|
r = hi[1] + (ag[1] - hi[1]) * t
|
||||||
b = c.THREAT_HIGH.b + (c.THREAT_AGGRO.b - c.THREAT_HIGH.b) * t
|
g = hi[2] + (ag[2] - hi[2]) * t
|
||||||
|
b = hi[3] + (ag[3] - hi[3]) * t
|
||||||
end
|
end
|
||||||
|
|
||||||
return r, g, b
|
return r, g, b
|
||||||
|
|||||||
@@ -390,21 +390,24 @@ function addon.Nameplates:ScanForNameplates()
|
|||||||
local nameplate = children[i]
|
local nameplate = children[i]
|
||||||
|
|
||||||
if nameplate and not knownPlates[nameplate] then
|
if nameplate and not knownPlates[nameplate] then
|
||||||
-- Quick visibility filter - don't scan invisible frames
|
-- Only mark a plate as "known" once we have actually
|
||||||
|
-- attached a threat bar to it. Vanilla recycles nameplate
|
||||||
|
-- frames from a pool: a currently-invisible plate frame
|
||||||
|
-- can (and usually will) reappear later for another mob.
|
||||||
|
-- If we flagged it known while invisible, we would never
|
||||||
|
-- attach a bar when it became visible again — that is the
|
||||||
|
-- root cause of "plates stop appearing after changing a
|
||||||
|
-- setting" (RecreateAll clears knownPlates, then this
|
||||||
|
-- scan runs while some pooled plates are hidden).
|
||||||
if nameplate:IsVisible() then
|
if nameplate:IsVisible() then
|
||||||
knownPlates[nameplate] = true
|
|
||||||
|
|
||||||
-- Check if this frame is a nameplate
|
|
||||||
local healthBar, nameText = self:IdentifyNameplate(nameplate)
|
local healthBar, nameText = self:IdentifyNameplate(nameplate)
|
||||||
|
|
||||||
if healthBar then
|
if healthBar then
|
||||||
|
knownPlates[nameplate] = true
|
||||||
self:AttachThreatBar(nameplate, healthBar, nameText)
|
self:AttachThreatBar(nameplate, healthBar, nameText)
|
||||||
end
|
end
|
||||||
else
|
|
||||||
-- Mark as known even if invisible; we'll handle
|
|
||||||
-- visibility in the update loop
|
|
||||||
knownPlates[nameplate] = true
|
|
||||||
end
|
end
|
||||||
|
-- Invisible plates are intentionally left un-flagged so
|
||||||
|
-- the periodic scan will re-check them once they show.
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -887,33 +890,64 @@ function addon.Nameplates:ApplyThreatToPlate(data, pct, situation)
|
|||||||
-- Now we only check if there's actual threat data to show. The combat
|
-- Now we only check if there's actual threat data to show. The combat
|
||||||
-- verification is done earlier in the pipeline (IsPlateInCombat,
|
-- verification is done earlier in the pipeline (IsPlateInCombat,
|
||||||
-- GetThreatByName, etc.).
|
-- GetThreatByName, etc.).
|
||||||
|
-- Combat-only display (2.6)
|
||||||
|
if RelationshipsThreatPlatesDB.combatOnly then
|
||||||
|
local inCombat = false
|
||||||
|
if UnitAffectingCombat then
|
||||||
|
local ok, r = pcall(UnitAffectingCombat, "player")
|
||||||
|
if ok then inCombat = r end
|
||||||
|
end
|
||||||
|
if not inCombat then
|
||||||
|
data.container:Hide()
|
||||||
|
if addon.Nameplates.SetFlashing then addon.Nameplates:SetFlashing(data, false) end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Hide below threshold (2.6)
|
||||||
|
local minShow = RelationshipsThreatPlatesDB.hideBelowPct or 0
|
||||||
|
if minShow > 0 and (pct or 0) < minShow then
|
||||||
|
data.container:Hide()
|
||||||
|
if addon.Nameplates.SetFlashing then addon.Nameplates:SetFlashing(data, false) end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if pct == 0 then
|
if pct == 0 then
|
||||||
data.container:Hide()
|
data.container:Hide()
|
||||||
|
if addon.Nameplates.SetFlashing then addon.Nameplates:SetFlashing(data, false) end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
data.container:Show()
|
data.container:Show()
|
||||||
|
|
||||||
-- Smooth the percentage
|
-- Preserve the RAW threat percent (may exceed 100 when the player
|
||||||
|
-- is the top threat holder and well ahead of the runner-up). The
|
||||||
|
-- text label shows this uncapped value so the user can see e.g.
|
||||||
|
-- "185%" for a tank comfortably holding threat. The bar fill
|
||||||
|
-- itself is clamped to 0-100 because the statusbar range is fixed.
|
||||||
|
local rawPct = pct or 0
|
||||||
|
|
||||||
|
-- Smooth the percentage (bar fill only — clamp to 0..100)
|
||||||
local oldPct = data.lastPct or 0
|
local oldPct = data.lastPct or 0
|
||||||
|
local barPct = math.min(100, math.max(0, rawPct))
|
||||||
local smoothPct
|
local smoothPct
|
||||||
-- Snap instantly when jumping to full aggro (100%) or dropping to 0
|
-- Snap instantly when jumping to full aggro or dropping to 0
|
||||||
-- (mob switched target). Smooth intermediate transitions moderately.
|
-- (mob switched target). Smooth intermediate transitions moderately.
|
||||||
if pct == 0 or pct >= 99.5 or math.abs(pct - oldPct) > 40 then
|
if barPct == 0 or barPct >= 99.5 or math.abs(barPct - oldPct) > 40 then
|
||||||
smoothPct = pct
|
smoothPct = barPct
|
||||||
else
|
else
|
||||||
smoothPct = oldPct + (pct - oldPct) * 0.6
|
smoothPct = oldPct + (barPct - oldPct) * 0.6
|
||||||
end
|
end
|
||||||
data.lastPct = smoothPct
|
data.lastPct = smoothPct
|
||||||
|
|
||||||
smoothPct = math.min(100, math.max(0, smoothPct))
|
smoothPct = math.min(100, math.max(0, smoothPct))
|
||||||
pct = math.min(100, math.max(0, pct))
|
|
||||||
|
|
||||||
-- Update bar value
|
-- Update bar value (0-100 range)
|
||||||
data.bar:SetValue(smoothPct)
|
data.bar:SetValue(smoothPct)
|
||||||
|
|
||||||
-- Update bar colour (smooth gradient)
|
-- Update bar colour (smooth gradient) — driven by barPct so colours
|
||||||
local r, g, b = addon.Media.GetThreatColour(pct)
|
-- top out at aggro red even if rawPct exceeds 100.
|
||||||
|
local r, g, b = addon.Media.GetThreatColour(barPct)
|
||||||
data.bar:SetStatusBarColor(r, g, b, RelationshipsThreatPlatesDB.barAlpha or 1)
|
data.bar:SetStatusBarColor(r, g, b, RelationshipsThreatPlatesDB.barAlpha or 1)
|
||||||
|
|
||||||
-- Update statusbar texture vertex colour
|
-- Update statusbar texture vertex colour
|
||||||
@@ -948,36 +982,63 @@ function addon.Nameplates:ApplyThreatToPlate(data, pct, situation)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update text
|
-- Update text — uses rawPct so values above 100% are shown as-is.
|
||||||
if data.text then
|
if data.text then
|
||||||
|
local fmt = RelationshipsThreatPlatesDB.textFormat or "percent"
|
||||||
|
local pctStr = math.floor(rawPct + 0.5) .. "%"
|
||||||
|
local tankStr = nil
|
||||||
|
if data.plateName and (RelationshipsThreatPlatesDB.tankMode or fmt == "tank" or fmt == "combo") then
|
||||||
|
tankStr = addon.Threat:GetTankDisplayForMob(data.plateName)
|
||||||
|
end
|
||||||
local textStr
|
local textStr
|
||||||
if RelationshipsThreatPlatesDB.tankMode and data.plateName then
|
if RelationshipsThreatPlatesDB.tankMode and tankStr and tankStr ~= "--" then
|
||||||
-- Tank mode: show raw threat lead over the 2nd-highest player
|
textStr = tankStr
|
||||||
-- (positive = you have that much lead as top-threat holder;
|
elseif fmt == "tank" and tankStr and tankStr ~= "--" then
|
||||||
-- negative = you're that much behind the current top).
|
textStr = tankStr
|
||||||
textStr = addon.Threat:GetTankDisplayForMob(data.plateName)
|
elseif fmt == "status" then
|
||||||
if not textStr or textStr == "--" then
|
if rawPct >= 100 then textStr = "PULL!"
|
||||||
-- Fall back to % when no absolute values are available.
|
elseif rawPct >= 80 then textStr = "WARN"
|
||||||
textStr = math.floor(pct + 0.5) .. "%"
|
elseif rawPct >= 50 then textStr = "HIGH"
|
||||||
end
|
else textStr = "SAFE" end
|
||||||
|
elseif fmt == "combo" and tankStr and tankStr ~= "--" then
|
||||||
|
textStr = pctStr .. " " .. tankStr
|
||||||
else
|
else
|
||||||
textStr = math.floor(pct + 0.5) .. "%"
|
textStr = pctStr
|
||||||
end
|
end
|
||||||
data.text:SetText(textStr)
|
data.text:SetText(textStr)
|
||||||
|
|
||||||
if pct >= 90 then
|
if rawPct >= 100 then
|
||||||
data.text:SetTextColor(1, 0.2, 0.2, 1)
|
data.text:SetTextColor(1, 0.2, 0.2, 1)
|
||||||
elseif pct >= 60 then
|
elseif rawPct >= 60 then
|
||||||
data.text:SetTextColor(1, 0.9, 0.8, 1)
|
data.text:SetTextColor(1, 0.9, 0.8, 1)
|
||||||
else
|
else
|
||||||
data.text:SetTextColor(1, 1, 1, 1)
|
data.text:SetTextColor(1, 1, 1, 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update threat glow
|
-- Warning sound (edge-triggered, 2.6)
|
||||||
|
do
|
||||||
|
local db = RelationshipsThreatPlatesDB
|
||||||
|
local prevWarn = data.lastWarnPct or 0
|
||||||
|
local threshold = db.warningThreshold or 80
|
||||||
|
if db.warningSound and threshold > 0
|
||||||
|
and rawPct >= threshold and prevWarn < threshold then
|
||||||
|
local snd = db.warningSoundName or "RaidWarning"
|
||||||
|
pcall(PlaySound, snd)
|
||||||
|
end
|
||||||
|
data.lastWarnPct = rawPct
|
||||||
|
|
||||||
|
-- Flash on aggro (2.6)
|
||||||
|
if addon.Nameplates.SetFlashing then
|
||||||
|
addon.Nameplates:SetFlashing(data, (db.flashOnAggro and rawPct >= 100) and true or false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update threat glow (drive by clamped barPct so it caps at max intensity)
|
||||||
if data.threatGlow then
|
if data.threatGlow then
|
||||||
if pct >= 80 then
|
if barPct >= 80 then
|
||||||
local intensity = (pct - 80) / 20
|
local intensity = (barPct - 80) / 20
|
||||||
|
if intensity > 1 then intensity = 1 end
|
||||||
data.threatGlow:SetVertexColor(0.9, 0, 0, 0.6 * intensity)
|
data.threatGlow:SetVertexColor(0.9, 0, 0, 0.6 * intensity)
|
||||||
data.threatGlow:Show()
|
data.threatGlow:Show()
|
||||||
else
|
else
|
||||||
@@ -1044,7 +1105,7 @@ end
|
|||||||
--
|
--
|
||||||
-- Returns: unit token string, or nil
|
-- Returns: unit token string, or nil
|
||||||
-- ==================================================================
|
-- ==================================================================
|
||||||
function addon.Nameplates:FindCombatUnitForPlate(data)
|
function addon.Nameplates:FindCombatUnitForPlate(data, excludeUnits)
|
||||||
if not data or not data.plateName then return nil end
|
if not data or not data.plateName then return nil end
|
||||||
if strlen(data.plateName) == 0 then return nil end
|
if strlen(data.plateName) == 0 then return nil end
|
||||||
|
|
||||||
@@ -1062,25 +1123,67 @@ function addon.Nameplates:FindCombatUnitForPlate(data)
|
|||||||
-- Get plate health percentage for matching
|
-- Get plate health percentage for matching
|
||||||
local plateHealthPct = GetPlateHealthPercent(data)
|
local plateHealthPct = GetPlateHealthPercent(data)
|
||||||
|
|
||||||
|
-- Same-name mob safety: if THIS plate's health looks idle
|
||||||
|
-- (near-full and not dropping), refuse to bind any combat
|
||||||
|
-- unit token. Binding a nearby attacker's unit token to an
|
||||||
|
-- idle same-name plate is the root cause of the "idle mob
|
||||||
|
-- shows attacker's threat bar" bug.
|
||||||
|
local plateLooksIdle = false
|
||||||
|
if plateHealthPct and plateHealthPct >= 99.5 then
|
||||||
|
if not data.previousHealth or data.previousHealth >= 99.5 then
|
||||||
|
plateLooksIdle = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if plateLooksIdle then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
-- Score each candidate: we want a unit that is:
|
-- Score each candidate: we want a unit that is:
|
||||||
-- 1. In combat (mandatory — idle mobs must never match)
|
-- 1. In combat (mandatory — idle mobs must never match)
|
||||||
-- 2. Health % close to the plate's health bar
|
-- 2. Health % close to the plate's health bar
|
||||||
-- 3. Hostile and attackable
|
-- 3. Hostile and attackable
|
||||||
|
-- 4. Not already assigned to a different plate this cycle
|
||||||
local bestUnit = nil
|
local bestUnit = nil
|
||||||
local bestScore = -999999
|
local bestScore = -999999
|
||||||
|
|
||||||
for i = 1, table.getn(candidates) do
|
for i = 1, table.getn(candidates) do
|
||||||
local c = candidates[i]
|
local c = candidates[i]
|
||||||
|
|
||||||
-- MANDATORY: Unit must be in combat
|
-- Skip candidates already claimed by another plate this cycle
|
||||||
if c.inCombat then
|
local excluded = false
|
||||||
local score = 0
|
if excludeUnits then
|
||||||
|
for j = 1, table.getn(excludeUnits) do
|
||||||
|
if IsSameUnitToken(excludeUnits[j], c.unit) then
|
||||||
|
excluded = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Health % matching (most important discriminator)
|
-- MANDATORY: Unit must be in combat AND not already claimed.
|
||||||
|
if c.inCombat and not excluded then
|
||||||
|
local score = 0
|
||||||
|
local healthMatched = false
|
||||||
|
|
||||||
|
-- Health % matching (most important discriminator).
|
||||||
|
-- Require a reasonably close match — if the plate is at
|
||||||
|
-- 40% and the only combat candidate is at 100%, it is
|
||||||
|
-- almost certainly a DIFFERENT mob with the same name
|
||||||
|
-- and must NOT be bound to this plate.
|
||||||
if plateHealthPct and c.healthPct then
|
if plateHealthPct and c.healthPct then
|
||||||
local diff = math.abs(plateHealthPct - c.healthPct)
|
local diff = math.abs(plateHealthPct - c.healthPct)
|
||||||
-- Close health match = strong signal this is the right mob
|
if diff <= 8 then
|
||||||
score = score + math.max(0, 500 - (diff * 15))
|
healthMatched = true
|
||||||
|
score = score + math.max(0, 500 - (diff * 15))
|
||||||
|
else
|
||||||
|
-- Too far apart — skip this candidate entirely.
|
||||||
|
score = -999999
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- No health info to compare. Fall back to previous
|
||||||
|
-- behaviour so freshly-shown plates can still bind
|
||||||
|
-- when their health bar hasn't been read yet.
|
||||||
|
healthMatched = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Hostile/attackable bonus
|
-- Hostile/attackable bonus
|
||||||
@@ -1088,7 +1191,7 @@ function addon.Nameplates:FindCombatUnitForPlate(data)
|
|||||||
score = score + 50
|
score = score + 50
|
||||||
end
|
end
|
||||||
|
|
||||||
if score > bestScore then
|
if score > bestScore and healthMatched then
|
||||||
bestScore = score
|
bestScore = score
|
||||||
bestUnit = c.unit
|
bestUnit = c.unit
|
||||||
end
|
end
|
||||||
@@ -1127,6 +1230,23 @@ end
|
|||||||
-- - Mobs attacking party members show threat even when player
|
-- - Mobs attacking party members show threat even when player
|
||||||
-- isn't in combat
|
-- isn't in combat
|
||||||
-- ==================================================================
|
-- ==================================================================
|
||||||
|
|
||||||
|
-- ==================================================================
|
||||||
|
-- Fallback threat values when we know a mob is in combat but have
|
||||||
|
-- no ThreatEngine percentage. Distinguishes "mob is attacking the
|
||||||
|
-- player" (full aggro on us) from "mob is attacking a party member"
|
||||||
|
-- (we're not the aggro target, show a low visible bar).
|
||||||
|
-- ==================================================================
|
||||||
|
local function GetFallbackThreatForMob(name)
|
||||||
|
if name
|
||||||
|
and addon.ThreatEngine
|
||||||
|
and addon.ThreatEngine.IsMobAttackingPlayer
|
||||||
|
and addon.ThreatEngine:IsMobAttackingPlayer(name) then
|
||||||
|
return 100, 3
|
||||||
|
end
|
||||||
|
return 20, 0
|
||||||
|
end
|
||||||
|
|
||||||
function addon.Nameplates:UpdateAllPlates()
|
function addon.Nameplates:UpdateAllPlates()
|
||||||
if not RelationshipsThreatPlatesDB or not RelationshipsThreatPlatesDB.enabled then
|
if not RelationshipsThreatPlatesDB or not RelationshipsThreatPlatesDB.enabled then
|
||||||
for nameplate, data in pairs(self.plates) do
|
for nameplate, data in pairs(self.plates) do
|
||||||
@@ -1169,6 +1289,16 @@ function addon.Nameplates:UpdateAllPlates()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Step 4: Update threat display for each visible plate.
|
-- Step 4: Update threat display for each visible plate.
|
||||||
|
-- Track combat unit tokens assigned this cycle so a single
|
||||||
|
-- combat mob can never be bound to multiple same-name plates
|
||||||
|
-- (which was causing idle same-name plates to display the
|
||||||
|
-- attacker's threat bar).
|
||||||
|
local assignedUnits = {}
|
||||||
|
for nameplate, data in pairs(self.plates) do
|
||||||
|
if data.unit then
|
||||||
|
assignedUnits[table.getn(assignedUnits) + 1] = data.unit
|
||||||
|
end
|
||||||
|
end
|
||||||
for nameplate, data in pairs(self.plates) do
|
for nameplate, data in pairs(self.plates) do
|
||||||
if nameplate:IsVisible() then
|
if nameplate:IsVisible() then
|
||||||
local isUniqueName = (platesPerName[data.plateName] or 1) <= 1
|
local isUniqueName = (platesPerName[data.plateName] or 1) <= 1
|
||||||
@@ -1177,6 +1307,21 @@ function addon.Nameplates:UpdateAllPlates()
|
|||||||
local plateInCombat = IsPlateInCombat(data)
|
local plateInCombat = IsPlateInCombat(data)
|
||||||
local hasThreatData = data.plateName and addon.ThreatEngine:HasDataForTarget(data.plateName)
|
local hasThreatData = data.plateName and addon.ThreatEngine:HasDataForTarget(data.plateName)
|
||||||
|
|
||||||
|
-- v2.6.2: A mob is also "in combat with us" if the incoming
|
||||||
|
-- combat-log parser has seen it hit or miss the player /
|
||||||
|
-- a party member within the last few seconds. In Vanilla
|
||||||
|
-- 1.12 non-targeted mobs have no unit token, so this is
|
||||||
|
-- the ONLY reliable signal for "this specific mob is
|
||||||
|
-- attacking the group" when its health bar hasn't
|
||||||
|
-- dropped yet. We only apply this signal to unique-name
|
||||||
|
-- plates to avoid lighting up every same-name plate when
|
||||||
|
-- only one of them is actually attacking.
|
||||||
|
local mobAttackingGroup =
|
||||||
|
data.plateName
|
||||||
|
and addon.ThreatEngine.IsMobAttackingGroup
|
||||||
|
and addon.ThreatEngine:IsMobAttackingGroup(data.plateName)
|
||||||
|
or false
|
||||||
|
|
||||||
if data.unit and UnitExists(data.unit) then
|
if data.unit and UnitExists(data.unit) then
|
||||||
-- We have a resolved unit token. Verify it's in combat.
|
-- We have a resolved unit token. Verify it's in combat.
|
||||||
local ok_combat, mobInCombat = pcall(UnitAffectingCombat, data.unit)
|
local ok_combat, mobInCombat = pcall(UnitAffectingCombat, data.unit)
|
||||||
@@ -1189,91 +1334,67 @@ function addon.Nameplates:UpdateAllPlates()
|
|||||||
self:ApplyThreatToPlate(data, pct, situation or 0)
|
self:ApplyThreatToPlate(data, pct, situation or 0)
|
||||||
end
|
end
|
||||||
elseif isUniqueName and data.plateName then
|
elseif isUniqueName and data.plateName then
|
||||||
-- UNIQUE-NAME MOB with no unit token:
|
-- UNIQUE-NAME MOB with no unit token.
|
||||||
-- This is the common case for non-targeted attacking mobs.
|
--
|
||||||
-- Since only ONE mob on screen has this name, ThreatEngine
|
-- CAUTION: "unique" here only means one plate of this
|
||||||
-- data is unambiguous. Use health-based combat detection.
|
-- name is currently VISIBLE. The camera may have rotated
|
||||||
|
-- away from a same-name attacker who is still active.
|
||||||
|
-- Because ThreatEngine data (and IsMobAttackingGroup)
|
||||||
|
-- are keyed by NAME, trusting them for an idle-looking
|
||||||
|
-- plate would light up the wrong mob when its twin
|
||||||
|
-- attacker is off-screen. So we require one of:
|
||||||
|
-- (a) confirmed health drop on THIS plate, OR
|
||||||
|
-- (b) a resolved combat unit token whose health
|
||||||
|
-- matches THIS plate.
|
||||||
|
-- A name-only signal (mobAttackingGroup / ThreatEngine
|
||||||
|
-- data) is never enough on its own.
|
||||||
|
|
||||||
if plateInCombat then
|
if plateInCombat then
|
||||||
-- Mob is in combat (health dropping or below 100%).
|
-- This specific plate's health is dropping — it's
|
||||||
-- Record when we first detected combat for grace period.
|
-- in combat. Safe to trust name-keyed threat data.
|
||||||
if not data.combatDetectedAt then
|
if not data.combatDetectedAt then
|
||||||
data.combatDetectedAt = now
|
data.combatDetectedAt = now
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Try ThreatEngine data first
|
|
||||||
local pct, sit = addon.Threat:GetThreatByName(data.plateName)
|
local pct, sit = addon.Threat:GetThreatByName(data.plateName)
|
||||||
if pct > 0 then
|
if pct > 0 then
|
||||||
self:ApplyThreatToPlate(data, pct, sit or 0)
|
self:ApplyThreatToPlate(data, pct, sit or 0)
|
||||||
else
|
else
|
||||||
-- No ThreatEngine data but mob IS in combat.
|
|
||||||
-- This happens for body pulls, or when only
|
|
||||||
-- a party member has hit the mob.
|
|
||||||
--
|
|
||||||
-- If a group member is in combat, show a default
|
|
||||||
-- threat bar. The mob is fighting someone.
|
|
||||||
if groupInCombat then
|
if groupInCombat then
|
||||||
self:ApplyThreatToPlate(data, 100, 3)
|
do local _fp, _fs = GetFallbackThreatForMob(data.plateName); self:ApplyThreatToPlate(data, _fp, _fs) end
|
||||||
else
|
else
|
||||||
data.container:Hide()
|
data.container:Hide()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif hasThreatData and groupInCombat then
|
elseif groupInCombat and (mobAttackingGroup or hasThreatData) then
|
||||||
-- Plate looks idle (full health) but ThreatEngine has
|
-- Name-only signal says a mob of this name is
|
||||||
-- data and group is in combat. This can happen briefly
|
-- attacking the group, but THIS plate shows no
|
||||||
-- after a mob is first hit (health bar hasn't updated)
|
-- health drop. Verify by matching a real combat
|
||||||
-- or for a mob being attacked by someone else.
|
-- unit token to this plate (health % match). If
|
||||||
-- Give a grace period.
|
-- none matches, this plate is an idle bystander
|
||||||
if not data.combatDetectedAt then
|
-- (e.g. the real attacker rotated off-screen).
|
||||||
data.combatDetectedAt = now
|
local unit = self:FindCombatUnitForPlate(data, assignedUnits)
|
||||||
end
|
if unit then
|
||||||
if now - data.combatDetectedAt < 0.5 then
|
data.unit = unit
|
||||||
local pct, sit = addon.Threat:GetThreatByName(data.plateName)
|
data.guid = addon:GetUnitID(unit)
|
||||||
if pct > 0 then
|
assignedUnits[table.getn(assignedUnits) + 1] = unit
|
||||||
self:ApplyThreatToPlate(data, pct, sit or 0)
|
|
||||||
else
|
|
||||||
self:ApplyThreatToPlate(data, 100, 3)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Grace period expired and health still at 100%.
|
|
||||||
-- This is likely an idle mob. Hide bar.
|
|
||||||
data.combatDetectedAt = nil
|
|
||||||
data.container:Hide()
|
|
||||||
end
|
|
||||||
elseif groupInCombat then
|
|
||||||
-- No health decrease, no ThreatEngine data, but group
|
|
||||||
-- is in combat. Check if this mob is attacking a group
|
|
||||||
-- member by looking for a unit token via party targets.
|
|
||||||
--
|
|
||||||
-- This catches the case of a body-pulled mob (at 100%
|
|
||||||
-- health, no damage dealt to it yet, but actively
|
|
||||||
-- attacking a group member).
|
|
||||||
local mobAttackingGroup = false
|
|
||||||
local foundUnit = self:FindUnitByName(data.plateName)
|
|
||||||
if foundUnit then
|
|
||||||
local ok_mt, mt = pcall(UnitName, foundUnit .. "target")
|
|
||||||
if ok_mt and mt then
|
|
||||||
if mt == UnitName("player") or addon.Threat:IsPartyMember(mt) then
|
|
||||||
mobAttackingGroup = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if mobAttackingGroup then
|
|
||||||
-- Mob is confirmed attacking a group member!
|
|
||||||
-- Show a high-threat bar.
|
|
||||||
if not data.combatDetectedAt then
|
if not data.combatDetectedAt then
|
||||||
data.combatDetectedAt = now
|
data.combatDetectedAt = now
|
||||||
end
|
end
|
||||||
self:ApplyThreatToPlate(data, 100, 3)
|
local pct, situation = addon.Threat:GetThreatPercent(unit)
|
||||||
|
if pct > 0 then
|
||||||
|
self:ApplyThreatToPlate(data, pct, situation or 0)
|
||||||
|
else
|
||||||
|
do local _fp, _fs = GetFallbackThreatForMob(data.plateName); self:ApplyThreatToPlate(data, _fp, _fs) end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- Can't confirm mob is attacking anyone.
|
-- Can't verify this plate is the attacker.
|
||||||
-- It might be idle. Don't show bar.
|
-- Do NOT show a bar based on name alone.
|
||||||
data.combatDetectedAt = nil
|
data.combatDetectedAt = nil
|
||||||
data.container:Hide()
|
data.container:Hide()
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- No combat detected, no threat data, group not in combat.
|
-- No combat detected, no threat data, or group not in combat.
|
||||||
data.combatDetectedAt = nil
|
data.combatDetectedAt = nil
|
||||||
data.container:Hide()
|
data.container:Hide()
|
||||||
end
|
end
|
||||||
@@ -1289,10 +1410,11 @@ function addon.Nameplates:UpdateAllPlates()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Try to find a combat-verified unit with matching health.
|
-- Try to find a combat-verified unit with matching health.
|
||||||
local unit = self:FindCombatUnitForPlate(data)
|
local unit = self:FindCombatUnitForPlate(data, assignedUnits)
|
||||||
if unit then
|
if unit then
|
||||||
data.unit = unit
|
data.unit = unit
|
||||||
data.guid = addon:GetUnitID(unit)
|
data.guid = addon:GetUnitID(unit)
|
||||||
|
assignedUnits[table.getn(assignedUnits) + 1] = unit
|
||||||
local pct, situation = addon.Threat:GetThreatPercent(unit)
|
local pct, situation = addon.Threat:GetThreatPercent(unit)
|
||||||
self:ApplyThreatToPlate(data, pct, situation or 0)
|
self:ApplyThreatToPlate(data, pct, situation or 0)
|
||||||
elseif hasThreatData then
|
elseif hasThreatData then
|
||||||
@@ -1307,7 +1429,7 @@ function addon.Nameplates:UpdateAllPlates()
|
|||||||
-- In combat but no threat data — mob is being
|
-- In combat but no threat data — mob is being
|
||||||
-- attacked by someone we can't track.
|
-- attacked by someone we can't track.
|
||||||
if groupInCombat then
|
if groupInCombat then
|
||||||
self:ApplyThreatToPlate(data, 100, 3)
|
do local _fp, _fs = GetFallbackThreatForMob(data.plateName); self:ApplyThreatToPlate(data, _fp, _fs) end
|
||||||
else
|
else
|
||||||
data.container:Hide()
|
data.container:Hide()
|
||||||
end
|
end
|
||||||
@@ -1316,7 +1438,7 @@ function addon.Nameplates:UpdateAllPlates()
|
|||||||
-- In combat (health dropping) but no ThreatEngine data.
|
-- In combat (health dropping) but no ThreatEngine data.
|
||||||
-- Show default high-threat bar.
|
-- Show default high-threat bar.
|
||||||
if groupInCombat then
|
if groupInCombat then
|
||||||
self:ApplyThreatToPlate(data, 100, 3)
|
do local _fp, _fs = GetFallbackThreatForMob(data.plateName); self:ApplyThreatToPlate(data, _fp, _fs) end
|
||||||
else
|
else
|
||||||
data.container:Hide()
|
data.container:Hide()
|
||||||
end
|
end
|
||||||
@@ -1326,11 +1448,12 @@ function addon.Nameplates:UpdateAllPlates()
|
|||||||
-- But check if a unit token reveals this specific mob
|
-- But check if a unit token reveals this specific mob
|
||||||
-- is attacking a group member (body pull scenario).
|
-- is attacking a group member (body pull scenario).
|
||||||
if groupInCombat then
|
if groupInCombat then
|
||||||
local unit = self:FindCombatUnitForPlate(data)
|
local unit = self:FindCombatUnitForPlate(data, assignedUnits)
|
||||||
if unit then
|
if unit then
|
||||||
-- Found a combat-verified unit for this plate.
|
-- Found a combat-verified unit for this plate.
|
||||||
data.unit = unit
|
data.unit = unit
|
||||||
data.guid = addon:GetUnitID(unit)
|
data.guid = addon:GetUnitID(unit)
|
||||||
|
assignedUnits[table.getn(assignedUnits) + 1] = unit
|
||||||
local pct, situation = addon.Threat:GetThreatPercent(unit)
|
local pct, situation = addon.Threat:GetThreatPercent(unit)
|
||||||
if pct > 0 then
|
if pct > 0 then
|
||||||
if not data.combatDetectedAt then
|
if not data.combatDetectedAt then
|
||||||
@@ -1399,3 +1522,52 @@ function addon.Nameplates:RecreateAll()
|
|||||||
knownPlates = {}
|
knownPlates = {}
|
||||||
self:ScanForNameplates()
|
self:ScanForNameplates()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- ==================================================================
|
||||||
|
-- Flash driver (2.6) — pulses bar alpha on aggro plates
|
||||||
|
-- ==================================================================
|
||||||
|
addon.Nameplates.flashSet = addon.Nameplates.flashSet or {}
|
||||||
|
|
||||||
|
if not addon.Nameplates.flashFrame then
|
||||||
|
local ff = CreateFrame("Frame")
|
||||||
|
addon.Nameplates.flashFrame = ff
|
||||||
|
ff:SetScript("OnUpdate", function()
|
||||||
|
local db = RelationshipsThreatPlatesDB
|
||||||
|
if not db then return end
|
||||||
|
local baseA = db.barAlpha or 1
|
||||||
|
if not db.flashOnAggro then
|
||||||
|
-- Restore any lingering flashers to base alpha
|
||||||
|
for d in pairs(addon.Nameplates.flashSet) do
|
||||||
|
if d and d.bar then d.bar:SetAlpha(baseA) end
|
||||||
|
addon.Nameplates.flashSet[d] = nil
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local speed = db.flashSpeed or 4
|
||||||
|
local t = GetTime() * speed
|
||||||
|
-- fabs(sin) style pulse in [0.35, 1.0]
|
||||||
|
local s = math.sin(t)
|
||||||
|
if s < 0 then s = -s end
|
||||||
|
local a = 0.35 + 0.65 * s
|
||||||
|
for d in pairs(addon.Nameplates.flashSet) do
|
||||||
|
if d and d.bar and d.container and d.container:IsShown() then
|
||||||
|
d.bar:SetAlpha(a * baseA)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function addon.Nameplates:SetFlashing(data, on)
|
||||||
|
if not data then return end
|
||||||
|
if on then
|
||||||
|
addon.Nameplates.flashSet[data] = true
|
||||||
|
else
|
||||||
|
if addon.Nameplates.flashSet[data] then
|
||||||
|
addon.Nameplates.flashSet[data] = nil
|
||||||
|
if data.bar then
|
||||||
|
data.bar:SetAlpha(RelationshipsThreatPlatesDB and RelationshipsThreatPlatesDB.barAlpha or 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -420,61 +420,37 @@ function addon.Threat:GetThreatPercent(unit)
|
|||||||
-- If the mob is targeting an outsider -> show player's threat
|
-- If the mob is targeting an outsider -> show player's threat
|
||||||
-- Otherwise -> show the best (highest) threat from any member
|
-- Otherwise -> show the best (highest) threat from any member
|
||||||
|
|
||||||
|
-- The threat bar on a nameplate is always displayed from the
|
||||||
|
-- PLAYER's point of view: "how close am I to pulling aggro
|
||||||
|
-- off whoever currently has it?". Showing another group
|
||||||
|
-- member's percentage would incorrectly make every mob a
|
||||||
|
-- party member is tanking read as 100% — that is the bug
|
||||||
|
-- users describe as "snap causes all party-combat enemies
|
||||||
|
-- to show 100%". So: always return the player's own value
|
||||||
|
-- here. If the player has no threat on this mob, return 0
|
||||||
|
-- so the bar hides.
|
||||||
|
|
||||||
if mobTargetName == playerName then
|
if mobTargetName == playerName then
|
||||||
-- Mob is attacking the player - show player's own threat
|
-- Mob is attacking the player - show player's own threat
|
||||||
return playerPct, playerSit
|
return playerPct, playerSit
|
||||||
elseif mobTargetName and partyNames[mobTargetName] then
|
elseif mobTargetName and partyNames[mobTargetName] then
|
||||||
-- Mob is attacking a group member - show that member's threat
|
-- Mob is attacking a group member. Show the player's
|
||||||
-- Look up that member's threat from the group scan
|
-- own relative threat, not the party member's.
|
||||||
local memberUnit = partyNames[mobTargetName]
|
|
||||||
local memberPct = nil
|
|
||||||
local memberSit = nil
|
|
||||||
|
|
||||||
if hasUnitDetailedThreatSituation then
|
|
||||||
local _, status, _, rawPct = SafeCall5(UnitDetailedThreatSituation, memberUnit, unit)
|
|
||||||
if rawPct then
|
|
||||||
memberPct = math.min(100, math.max(0, rawPct))
|
|
||||||
memberSit = status or 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not memberPct and hasGetThreatPercent then
|
|
||||||
local pct = SafeCall2(GetThreatPercent, memberUnit, unit)
|
|
||||||
if pct then
|
|
||||||
memberPct = math.min(100, math.max(0, pct))
|
|
||||||
if hasUnitThreatSituation then
|
|
||||||
memberSit = SafeCall2(UnitThreatSituation, memberUnit, unit) or 0
|
|
||||||
else
|
|
||||||
if memberPct >= 100 then memberSit = 3
|
|
||||||
elseif memberPct >= 60 then memberSit = 2
|
|
||||||
elseif memberPct >= 30 then memberSit = 1
|
|
||||||
else memberSit = 0 end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not memberPct and hasUnitThreatSituation then
|
|
||||||
memberSit = SafeCall2(UnitThreatSituation, memberUnit, unit) or 0
|
|
||||||
if memberSit == 0 then memberPct = 0
|
|
||||||
elseif memberSit == 1 then memberPct = 25
|
|
||||||
elseif memberSit == 2 then memberPct = 60
|
|
||||||
elseif memberSit == 3 then memberPct = 100 end
|
|
||||||
end
|
|
||||||
|
|
||||||
if memberPct then
|
|
||||||
return memberPct, memberSit or 0
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Fallback: can't get specific member data, use best
|
|
||||||
return bestPct, bestSit
|
|
||||||
else
|
|
||||||
-- Mob targets an outsider, or we can't see its target.
|
|
||||||
-- Show player's threat if they have any, otherwise the
|
|
||||||
-- best group threat.
|
|
||||||
if playerPct > 0 then
|
if playerPct > 0 then
|
||||||
return playerPct, playerSit
|
return playerPct, playerSit
|
||||||
end
|
end
|
||||||
return bestPct, bestSit
|
-- Player has no threat yet, but a party member is in
|
||||||
|
-- the aggro seat. Show a low visible bar so the plate
|
||||||
|
-- is still displayed (bar hides completely at pct=0).
|
||||||
|
return 8, 0
|
||||||
|
else
|
||||||
|
-- Mob targets an outsider, or we can't see its target.
|
||||||
|
-- Show player's own threat only.
|
||||||
|
if playerPct > 0 then
|
||||||
|
return playerPct, playerSit
|
||||||
|
end
|
||||||
|
-- Fall through: let ThreatEngine / heuristic decide
|
||||||
|
-- below instead of hiding the bar outright.
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -600,13 +576,19 @@ function addon.Threat:GetThreatPercent(unit)
|
|||||||
threatPct = 100
|
threatPct = 100
|
||||||
situation = 3
|
situation = 3
|
||||||
elseif partyNames[mobTargetName] then
|
elseif partyNames[mobTargetName] then
|
||||||
-- Mob is targeting a PARTY/RAID member.
|
-- Mob is targeting a PARTY/RAID member. The party member
|
||||||
-- That member has aggro - show high threat.
|
-- has aggro, NOT the player. Showing 100% here would
|
||||||
-- This is the KEY fix: previously showed only 60%/30%
|
-- incorrectly light every mob in party combat up as full
|
||||||
-- for mobs targeting group members. Now shows 100%
|
-- aggro on the player. Show a low estimate instead — the
|
||||||
-- because that group member IS the one being attacked.
|
-- player is not in the aggro seat, but the bar must still
|
||||||
threatPct = 100
|
-- be visible so users know the mob is engaged.
|
||||||
situation = 3
|
if UnitIsUnit("target", unit) then
|
||||||
|
threatPct = 20
|
||||||
|
situation = 0
|
||||||
|
else
|
||||||
|
threatPct = 8
|
||||||
|
situation = 0
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- Mob is targeting someone outside the party
|
-- Mob is targeting someone outside the party
|
||||||
-- (another player, a pet, etc.)
|
-- (another player, a pet, etc.)
|
||||||
@@ -663,7 +645,13 @@ function addon.Threat:UpdateUnitThreat(unit)
|
|||||||
and mobTargetName and playerName and mobTargetName == playerName then
|
and mobTargetName and playerName and mobTargetName == playerName then
|
||||||
local ok_pc, pc = pcall(UnitAffectingCombat, "player")
|
local ok_pc, pc = pcall(UnitAffectingCombat, "player")
|
||||||
if ok_pc and pc then
|
if ok_pc and pc then
|
||||||
pct = 100
|
-- Player is being targeted, so they have aggro. Force
|
||||||
|
-- pct to at least 100 without clobbering a real value
|
||||||
|
-- above 100 (which happens when the player is top and
|
||||||
|
-- well ahead of the second-highest threat holder).
|
||||||
|
if not pct or pct < 100 then
|
||||||
|
pct = 100
|
||||||
|
end
|
||||||
situation = 3
|
situation = 3
|
||||||
targetSnapped = true
|
targetSnapped = true
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -94,6 +94,25 @@ local playerInCombat = false
|
|||||||
-- members when the player is NOT in combat themselves.
|
-- members when the player is NOT in combat themselves.
|
||||||
local groupInCombat = false
|
local groupInCombat = false
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Multi-mob incoming-attack tracking (v2.6.2)
|
||||||
|
--
|
||||||
|
-- Vanilla WoW 1.12 exposes NO unit token for a mob that isn't
|
||||||
|
-- currently targeted / mouseovered / party-targeted. That meant
|
||||||
|
-- earlier versions only lit the threat bar on the ONE enemy the
|
||||||
|
-- player had targeted. To show bars for EVERY enemy in combat with
|
||||||
|
-- the group, we parse the incoming-combat chat events (mob->player
|
||||||
|
-- and mob->party) and remember which mob names have hit or missed
|
||||||
|
-- a group member recently. UpdateAllPlates then treats any plate
|
||||||
|
-- whose mob name is in this table as "in combat" and displays a
|
||||||
|
-- threat bar even when its health bar hasn't dropped yet and no
|
||||||
|
-- unit token is available.
|
||||||
|
-- ============================================================
|
||||||
|
local mobsAttackingGroup = {}
|
||||||
|
local mobsAttackingPlayer = {}
|
||||||
|
local MOB_ATTACK_TTL = 6.0 -- seconds a mob stays flagged as attacking
|
||||||
|
local INCOMING_PARSERS = {}
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- Safe API helpers
|
-- Safe API helpers
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
@@ -414,6 +433,67 @@ local function BuildAllParsers()
|
|||||||
" party damage, " .. table.getn(PARTY_HEAL_PARSERS) ..
|
" party damage, " .. table.getn(PARTY_HEAL_PARSERS) ..
|
||||||
" party heal, " .. table.getn(PARTY_PERIODIC_PARSERS) ..
|
" party heal, " .. table.getn(PARTY_PERIODIC_PARSERS) ..
|
||||||
" party periodic parsers built")
|
" party periodic parsers built")
|
||||||
|
|
||||||
|
-- ========================================
|
||||||
|
-- INCOMING (mob -> player/party) parsers (v2.6.2)
|
||||||
|
-- Used to detect that a specific mob NAME is actively engaged
|
||||||
|
-- with the group even though we have no unit token for it.
|
||||||
|
-- We only care about the first capture (the mob / source name).
|
||||||
|
-- Any OTHERSELF or OTHEROTHER GlobalString whose first %s is the
|
||||||
|
-- attacker qualifies. Some of these constants may not exist on
|
||||||
|
-- every Turtle/OctoWoW build; missing ones are silently skipped.
|
||||||
|
-- ========================================
|
||||||
|
local incomingSources = {
|
||||||
|
-- Melee mob -> player
|
||||||
|
"COMBATHITOTHERSELF", "COMBATHITCRITOTHERSELF",
|
||||||
|
"COMBATHITSCHOOLOTHERSELF", "COMBATHITCRITSCHOOLOTHERSELF",
|
||||||
|
"MISSEDOTHERSELF",
|
||||||
|
"VSDODGEOTHERSELF", "VSPARRYOTHERSELF", "VSBLOCKOTHERSELF",
|
||||||
|
"VSABSORBOTHERSELF", "VSIMMUNEOTHERSELF", "VSRESISTOTHERSELF",
|
||||||
|
"VSDEFLECTOTHERSELF", "VSEVADEOTHERSELF",
|
||||||
|
-- Spell mob -> player
|
||||||
|
"SPELLLOGOTHERSELF", "SPELLLOGSCHOOLOTHERSELF",
|
||||||
|
"SPELLLOGCRITOTHERSELF", "SPELLLOGCRITSCHOOLOTHERSELF",
|
||||||
|
"SPELLMISSOTHERSELF", "SPELLDODGEDOTHERSELF",
|
||||||
|
"SPELLPARRIEDOTHERSELF", "SPELLBLOCKEDOTHERSELF",
|
||||||
|
"SPELLRESISTOTHERSELF", "SPELLIMMUNEOTHERSELF",
|
||||||
|
"SPELLEVADEDOTHERSELF", "SPELLDEFLECTEDOTHERSELF",
|
||||||
|
"SPELLREFLECTOTHERSELF",
|
||||||
|
-- Periodic (mob DoT ticking on player)
|
||||||
|
"PERIODICAURADAMAGEOTHERSELF",
|
||||||
|
-- Melee mob -> other (party member)
|
||||||
|
"COMBATHITOTHEROTHER", "COMBATHITCRITOTHEROTHER",
|
||||||
|
"COMBATHITSCHOOLOTHEROTHER", "COMBATHITCRITSCHOOLOTHEROTHER",
|
||||||
|
"MISSEDOTHEROTHER",
|
||||||
|
"VSDODGEOTHEROTHER", "VSPARRYOTHEROTHER", "VSBLOCKOTHEROTHER",
|
||||||
|
"VSABSORBOTHEROTHER", "VSIMMUNEOTHEROTHER", "VSRESISTOTHEROTHER",
|
||||||
|
"VSDEFLECTOTHEROTHER", "VSEVADEOTHEROTHER",
|
||||||
|
-- Spell mob -> other (party member)
|
||||||
|
"SPELLLOGOTHEROTHER", "SPELLLOGSCHOOLOTHEROTHER",
|
||||||
|
"SPELLLOGCRITOTHEROTHER", "SPELLLOGCRITSCHOOLOTHEROTHER",
|
||||||
|
"SPELLMISSOTHEROTHER", "SPELLDODGEDOTHEROTHER",
|
||||||
|
"SPELLPARRIEDOTHEROTHER", "SPELLBLOCKEDOTHEROTHER",
|
||||||
|
"SPELLRESISTOTHEROTHER", "SPELLIMMUNEOTHEROTHER",
|
||||||
|
-- Periodic (mob DoT ticking on party member)
|
||||||
|
"PERIODICAURADAMAGEOTHEROTHER",
|
||||||
|
}
|
||||||
|
for i = 1, table.getn(incomingSources) do
|
||||||
|
local gname = incomingSources[i]
|
||||||
|
local fs = getglobal(gname)
|
||||||
|
if fs then
|
||||||
|
local r = FormatToRegex(fs)
|
||||||
|
if r then
|
||||||
|
local toPlayer = false
|
||||||
|
if string.find(gname, "OTHERSELF") or string.find(gname, "TOSELF") then
|
||||||
|
toPlayer = true
|
||||||
|
end
|
||||||
|
INCOMING_PARSERS[table.getn(INCOMING_PARSERS) + 1] =
|
||||||
|
{ regex = r, globalVar = gname, toPlayer = toPlayer }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
addon:ui_print("Threat engine: " .. table.getn(INCOMING_PARSERS) ..
|
||||||
|
" incoming (mob->group) parsers built")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
@@ -1137,6 +1217,79 @@ end
|
|||||||
-- Event handler for combat log events
|
-- Event handler for combat log events
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Mark a mob name as actively attacking someone in the group.
|
||||||
|
-- Filters out anything that is obviously a player (self or a cached
|
||||||
|
-- party/raid member class). Unknown names are assumed to be mobs.
|
||||||
|
local function MarkMobAttackingGroup(name)
|
||||||
|
if not name or name == "" then return end
|
||||||
|
if playerName and name == playerName then return end
|
||||||
|
if partyClassCache and partyClassCache[name] then return end
|
||||||
|
mobsAttackingGroup[name] = GetTime()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Same as MarkMobAttackingGroup, but flags the mob as specifically
|
||||||
|
-- attacking the player. Used to distinguish "attacking me" from
|
||||||
|
-- "attacking a party member" in the nameplate fallback branches.
|
||||||
|
local function MarkMobAttackingPlayer(name)
|
||||||
|
if not name or name == "" then return end
|
||||||
|
if playerName and name == playerName then return end
|
||||||
|
if partyClassCache and partyClassCache[name] then return end
|
||||||
|
mobsAttackingPlayer[name] = GetTime()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Parse an incoming combat-log message and, if it matches any known
|
||||||
|
-- mob->group pattern, flag the first capture (the source mob name).
|
||||||
|
local function ParseIncomingMessage(msg)
|
||||||
|
if not msg or not INCOMING_PARSERS then return end
|
||||||
|
for i = 1, table.getn(INCOMING_PARSERS) do
|
||||||
|
local p = INCOMING_PARSERS[i]
|
||||||
|
local _, _, c1 = string.find(msg, p.regex)
|
||||||
|
if c1 then
|
||||||
|
MarkMobAttackingGroup(c1)
|
||||||
|
if p.toPlayer then
|
||||||
|
MarkMobAttackingPlayer(c1)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Public API: is this mob name currently flagged as attacking the group?
|
||||||
|
function addon.ThreatEngine:IsMobAttackingGroup(mobName)
|
||||||
|
if not mobName then return false end
|
||||||
|
local t = mobsAttackingGroup[mobName]
|
||||||
|
if not t then return false end
|
||||||
|
if GetTime() - t > MOB_ATTACK_TTL then
|
||||||
|
mobsAttackingGroup[mobName] = nil
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Public API: is this mob name currently flagged as specifically
|
||||||
|
-- attacking the player (not just any group member)?
|
||||||
|
function addon.ThreatEngine:IsMobAttackingPlayer(mobName)
|
||||||
|
if not mobName then return false end
|
||||||
|
local t = mobsAttackingPlayer[mobName]
|
||||||
|
if not t then return false end
|
||||||
|
if GetTime() - t > MOB_ATTACK_TTL then
|
||||||
|
mobsAttackingPlayer[mobName] = nil
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Public API: return the raw table (name -> lastSeen). Caller must not
|
||||||
|
-- mutate it. Used mainly by debug commands.
|
||||||
|
function addon.ThreatEngine:GetMobsAttackingGroup()
|
||||||
|
return mobsAttackingGroup
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Called by the event frame for any CREATURE_VS_* combat log event.
|
||||||
|
function addon.ThreatEngine:OnIncomingCombat(ev, msg)
|
||||||
|
ParseIncomingMessage(msg)
|
||||||
|
end
|
||||||
|
|
||||||
function addon.ThreatEngine:OnCombatEvent(ev, msg)
|
function addon.ThreatEngine:OnCombatEvent(ev, msg)
|
||||||
if not msg then return end
|
if not msg then return end
|
||||||
if not playerName then return end
|
if not playerName then return end
|
||||||
@@ -1298,8 +1451,31 @@ function addon.ThreatEngine:GetThreatOnTarget(mobName)
|
|||||||
return 0, 0, 0, 0, false, nil, 0
|
return 0, 0, 0, 0, false, nil, 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Calculate the player's percentage relative to top threat
|
-- Calculate the player's threat percentage.
|
||||||
local pct = (playerThreat / topThreat) * 100
|
--
|
||||||
|
-- Two-mode display so the number matches user intuition:
|
||||||
|
-- * If the player IS the current top-threat holder, show how far
|
||||||
|
-- ahead they are of the runner-up: pct = playerThreat/second * 100.
|
||||||
|
-- A tank comfortably tanking a mob will read something like 120%
|
||||||
|
-- or 200% — anything > 100 means "you are above the next player".
|
||||||
|
-- * If the player is NOT top, show their share of the top holder's
|
||||||
|
-- threat: pct = playerThreat/top * 100. This is 0-100 and answers
|
||||||
|
-- "how close am I to pulling aggro off the current top holder".
|
||||||
|
--
|
||||||
|
-- This lets the plate show > 100% for the actual threat leader
|
||||||
|
-- instead of clamping everyone with aggro to a flat 100%.
|
||||||
|
local pct
|
||||||
|
if topPlayer == playerName then
|
||||||
|
if secondThreat > 0 then
|
||||||
|
pct = (playerThreat / secondThreat) * 100
|
||||||
|
else
|
||||||
|
pct = 100
|
||||||
|
end
|
||||||
|
if pct < 100 then pct = 100 end -- safety: top holder never below 100
|
||||||
|
else
|
||||||
|
pct = (playerThreat / topThreat) * 100
|
||||||
|
if pct > 100 then pct = 100 end -- non-top can't exceed the leader
|
||||||
|
end
|
||||||
|
|
||||||
-- Determine threat situation based on the player's threat
|
-- Determine threat situation based on the player's threat
|
||||||
local sit = 0
|
local sit = 0
|
||||||
@@ -1522,6 +1698,8 @@ end
|
|||||||
|
|
||||||
function addon.ThreatEngine:ClearAll()
|
function addon.ThreatEngine:ClearAll()
|
||||||
threatData = {}
|
threatData = {}
|
||||||
|
mobsAttackingGroup = {}
|
||||||
|
mobsAttackingPlayer = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
@@ -1786,6 +1964,20 @@ function addon.ThreatEngine:Initialize()
|
|||||||
combatFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
|
combatFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
|
||||||
combatFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE")
|
combatFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE")
|
||||||
|
|
||||||
|
-- Incoming combat: mob attacking player / party. Used by v2.6.2
|
||||||
|
-- multi-mob threat display to flag mobs that are engaged with
|
||||||
|
-- the group but for which we have no unit token.
|
||||||
|
combatFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS")
|
||||||
|
combatFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES")
|
||||||
|
combatFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_PARTY_HITS")
|
||||||
|
combatFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_PARTY_MISSES")
|
||||||
|
combatFrame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE")
|
||||||
|
combatFrame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_SELF_MISSES")
|
||||||
|
combatFrame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_PARTY_DAMAGE")
|
||||||
|
combatFrame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_PARTY_MISSES")
|
||||||
|
combatFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_VS_SELF_DAMAGE")
|
||||||
|
combatFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_VS_PARTY_DAMAGE")
|
||||||
|
|
||||||
combatFrame:SetScript("OnEvent", function()
|
combatFrame:SetScript("OnEvent", function()
|
||||||
-- Vanilla 1.12: event name is in global 'event', args in global arg1..arg9
|
-- Vanilla 1.12: event name is in global 'event', args in global arg1..arg9
|
||||||
-- IMPORTANT: we must NOT shadow 'event' with a local or parameter
|
-- IMPORTANT: we must NOT shadow 'event' with a local or parameter
|
||||||
@@ -1815,6 +2007,18 @@ function addon.ThreatEngine:Initialize()
|
|||||||
elseif event == "CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS"
|
elseif event == "CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS"
|
||||||
or event == "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS" then
|
or event == "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS" then
|
||||||
addon.ThreatEngine:OnPartyPeriodicEvent(event, arg1)
|
addon.ThreatEngine:OnPartyPeriodicEvent(event, arg1)
|
||||||
|
-- Incoming combat: mob attacking player / party (v2.6.2)
|
||||||
|
elseif event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS"
|
||||||
|
or event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES"
|
||||||
|
or event == "CHAT_MSG_COMBAT_CREATURE_VS_PARTY_HITS"
|
||||||
|
or event == "CHAT_MSG_COMBAT_CREATURE_VS_PARTY_MISSES"
|
||||||
|
or event == "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"
|
||||||
|
or event == "CHAT_MSG_SPELL_CREATURE_VS_SELF_MISSES"
|
||||||
|
or event == "CHAT_MSG_SPELL_CREATURE_VS_PARTY_DAMAGE"
|
||||||
|
or event == "CHAT_MSG_SPELL_CREATURE_VS_PARTY_MISSES"
|
||||||
|
or event == "CHAT_MSG_SPELL_PERIODIC_CREATURE_VS_SELF_DAMAGE"
|
||||||
|
or event == "CHAT_MSG_SPELL_PERIODIC_CREATURE_VS_PARTY_DAMAGE" then
|
||||||
|
addon.ThreatEngine:OnIncomingCombat(event, arg1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle non-combat events
|
-- Handle non-combat events
|
||||||
|
|||||||
Reference in New Issue
Block a user