Add files via upload
This commit is contained in:
@@ -890,8 +890,31 @@ function addon.Nameplates:ApplyThreatToPlate(data, pct, situation)
|
||||
-- Now we only check if there's actual threat data to show. The combat
|
||||
-- verification is done earlier in the pipeline (IsPlateInCombat,
|
||||
-- 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
|
||||
data.container:Hide()
|
||||
if addon.Nameplates.SetFlashing then addon.Nameplates:SetFlashing(data, false) end
|
||||
return
|
||||
end
|
||||
|
||||
@@ -961,24 +984,30 @@ function addon.Nameplates:ApplyThreatToPlate(data, pct, situation)
|
||||
|
||||
-- Update text — uses rawPct so values above 100% are shown as-is.
|
||||
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
|
||||
if RelationshipsThreatPlatesDB.tankMode and data.plateName then
|
||||
-- Tank mode: show raw threat lead over the 2nd-highest player
|
||||
-- (positive = you have that much lead as top-threat holder;
|
||||
-- negative = you're that much behind the current top).
|
||||
textStr = addon.Threat:GetTankDisplayForMob(data.plateName)
|
||||
if not textStr or textStr == "--" then
|
||||
-- Fall back to % when no absolute values are available.
|
||||
textStr = math.floor(rawPct + 0.5) .. "%"
|
||||
end
|
||||
if RelationshipsThreatPlatesDB.tankMode and tankStr and tankStr ~= "--" then
|
||||
textStr = tankStr
|
||||
elseif fmt == "tank" and tankStr and tankStr ~= "--" then
|
||||
textStr = tankStr
|
||||
elseif fmt == "status" then
|
||||
if rawPct >= 100 then textStr = "PULL!"
|
||||
elseif rawPct >= 80 then textStr = "WARN"
|
||||
elseif rawPct >= 50 then textStr = "HIGH"
|
||||
else textStr = "SAFE" end
|
||||
elseif fmt == "combo" and tankStr and tankStr ~= "--" then
|
||||
textStr = pctStr .. " " .. tankStr
|
||||
else
|
||||
textStr = math.floor(rawPct + 0.5) .. "%"
|
||||
textStr = pctStr
|
||||
end
|
||||
data.text:SetText(textStr)
|
||||
|
||||
if rawPct >= 100 then
|
||||
-- Aggro / over-aggro: bright red text so the top holder
|
||||
-- stands out even when the bar itself is pegged at full.
|
||||
data.text:SetTextColor(1, 0.2, 0.2, 1)
|
||||
elseif rawPct >= 60 then
|
||||
data.text:SetTextColor(1, 0.9, 0.8, 1)
|
||||
@@ -987,6 +1016,24 @@ function addon.Nameplates:ApplyThreatToPlate(data, pct, situation)
|
||||
end
|
||||
end
|
||||
|
||||
-- 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 barPct >= 80 then
|
||||
@@ -1058,7 +1105,7 @@ end
|
||||
--
|
||||
-- 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 strlen(data.plateName) == 0 then return nil end
|
||||
|
||||
@@ -1076,25 +1123,67 @@ function addon.Nameplates:FindCombatUnitForPlate(data)
|
||||
-- Get plate health percentage for matching
|
||||
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:
|
||||
-- 1. In combat (mandatory — idle mobs must never match)
|
||||
-- 2. Health % close to the plate's health bar
|
||||
-- 3. Hostile and attackable
|
||||
-- 4. Not already assigned to a different plate this cycle
|
||||
local bestUnit = nil
|
||||
local bestScore = -999999
|
||||
|
||||
for i = 1, table.getn(candidates) do
|
||||
local c = candidates[i]
|
||||
|
||||
-- MANDATORY: Unit must be in combat
|
||||
if c.inCombat then
|
||||
local score = 0
|
||||
-- Skip candidates already claimed by another plate this cycle
|
||||
local excluded = false
|
||||
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
|
||||
local diff = math.abs(plateHealthPct - c.healthPct)
|
||||
-- Close health match = strong signal this is the right mob
|
||||
score = score + math.max(0, 500 - (diff * 15))
|
||||
if diff <= 8 then
|
||||
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
|
||||
|
||||
-- Hostile/attackable bonus
|
||||
@@ -1102,7 +1191,7 @@ function addon.Nameplates:FindCombatUnitForPlate(data)
|
||||
score = score + 50
|
||||
end
|
||||
|
||||
if score > bestScore then
|
||||
if score > bestScore and healthMatched then
|
||||
bestScore = score
|
||||
bestUnit = c.unit
|
||||
end
|
||||
@@ -1141,6 +1230,23 @@ end
|
||||
-- - Mobs attacking party members show threat even when player
|
||||
-- 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()
|
||||
if not RelationshipsThreatPlatesDB or not RelationshipsThreatPlatesDB.enabled then
|
||||
for nameplate, data in pairs(self.plates) do
|
||||
@@ -1183,6 +1289,16 @@ function addon.Nameplates:UpdateAllPlates()
|
||||
end
|
||||
|
||||
-- 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
|
||||
if nameplate:IsVisible() then
|
||||
local isUniqueName = (platesPerName[data.plateName] or 1) <= 1
|
||||
@@ -1191,6 +1307,21 @@ function addon.Nameplates:UpdateAllPlates()
|
||||
local plateInCombat = IsPlateInCombat(data)
|
||||
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
|
||||
-- We have a resolved unit token. Verify it's in combat.
|
||||
local ok_combat, mobInCombat = pcall(UnitAffectingCombat, data.unit)
|
||||
@@ -1203,91 +1334,67 @@ function addon.Nameplates:UpdateAllPlates()
|
||||
self:ApplyThreatToPlate(data, pct, situation or 0)
|
||||
end
|
||||
elseif isUniqueName and data.plateName then
|
||||
-- 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
|
||||
-- data is unambiguous. Use health-based combat detection.
|
||||
-- UNIQUE-NAME MOB with no unit token.
|
||||
--
|
||||
-- CAUTION: "unique" here only means one plate of this
|
||||
-- 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
|
||||
-- Mob is in combat (health dropping or below 100%).
|
||||
-- Record when we first detected combat for grace period.
|
||||
-- This specific plate's health is dropping — it's
|
||||
-- in combat. Safe to trust name-keyed threat data.
|
||||
if not data.combatDetectedAt then
|
||||
data.combatDetectedAt = now
|
||||
end
|
||||
|
||||
-- Try ThreatEngine data first
|
||||
local pct, sit = addon.Threat:GetThreatByName(data.plateName)
|
||||
if pct > 0 then
|
||||
self:ApplyThreatToPlate(data, pct, sit or 0)
|
||||
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
|
||||
self:ApplyThreatToPlate(data, 20, 0) -- was 100,3: showing full aggro for any party-combat mob with no threat data was misleading
|
||||
do local _fp, _fs = GetFallbackThreatForMob(data.plateName); self:ApplyThreatToPlate(data, _fp, _fs) end
|
||||
else
|
||||
data.container:Hide()
|
||||
end
|
||||
end
|
||||
elseif hasThreatData and groupInCombat then
|
||||
-- Plate looks idle (full health) but ThreatEngine has
|
||||
-- data and group is in combat. This can happen briefly
|
||||
-- after a mob is first hit (health bar hasn't updated)
|
||||
-- or for a mob being attacked by someone else.
|
||||
-- Give a grace period.
|
||||
if not data.combatDetectedAt then
|
||||
data.combatDetectedAt = now
|
||||
end
|
||||
if now - data.combatDetectedAt < 0.5 then
|
||||
local pct, sit = addon.Threat:GetThreatByName(data.plateName)
|
||||
if pct > 0 then
|
||||
self:ApplyThreatToPlate(data, pct, sit or 0)
|
||||
else
|
||||
self:ApplyThreatToPlate(data, 20, 0) -- was 100,3: showing full aggro for any party-combat mob with no threat data was misleading
|
||||
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.
|
||||
elseif groupInCombat and (mobAttackingGroup or hasThreatData) then
|
||||
-- Name-only signal says a mob of this name is
|
||||
-- attacking the group, but THIS plate shows no
|
||||
-- health drop. Verify by matching a real combat
|
||||
-- unit token to this plate (health % match). If
|
||||
-- none matches, this plate is an idle bystander
|
||||
-- (e.g. the real attacker rotated off-screen).
|
||||
local unit = self:FindCombatUnitForPlate(data, assignedUnits)
|
||||
if unit then
|
||||
data.unit = unit
|
||||
data.guid = addon:GetUnitID(unit)
|
||||
assignedUnits[table.getn(assignedUnits) + 1] = unit
|
||||
if not data.combatDetectedAt then
|
||||
data.combatDetectedAt = now
|
||||
end
|
||||
self:ApplyThreatToPlate(data, 20, 0) -- was 100,3: showing full aggro for any party-combat mob with no threat data was misleading
|
||||
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
|
||||
-- Can't confirm mob is attacking anyone.
|
||||
-- It might be idle. Don't show bar.
|
||||
-- Can't verify this plate is the attacker.
|
||||
-- Do NOT show a bar based on name alone.
|
||||
data.combatDetectedAt = nil
|
||||
data.container:Hide()
|
||||
end
|
||||
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.container:Hide()
|
||||
end
|
||||
@@ -1303,10 +1410,11 @@ function addon.Nameplates:UpdateAllPlates()
|
||||
end
|
||||
|
||||
-- Try to find a combat-verified unit with matching health.
|
||||
local unit = self:FindCombatUnitForPlate(data)
|
||||
local unit = self:FindCombatUnitForPlate(data, assignedUnits)
|
||||
if unit then
|
||||
data.unit = unit
|
||||
data.guid = addon:GetUnitID(unit)
|
||||
assignedUnits[table.getn(assignedUnits) + 1] = unit
|
||||
local pct, situation = addon.Threat:GetThreatPercent(unit)
|
||||
self:ApplyThreatToPlate(data, pct, situation or 0)
|
||||
elseif hasThreatData then
|
||||
@@ -1321,7 +1429,7 @@ function addon.Nameplates:UpdateAllPlates()
|
||||
-- In combat but no threat data — mob is being
|
||||
-- attacked by someone we can't track.
|
||||
if groupInCombat then
|
||||
self:ApplyThreatToPlate(data, 20, 0) -- was 100,3: showing full aggro for any party-combat mob with no threat data was misleading
|
||||
do local _fp, _fs = GetFallbackThreatForMob(data.plateName); self:ApplyThreatToPlate(data, _fp, _fs) end
|
||||
else
|
||||
data.container:Hide()
|
||||
end
|
||||
@@ -1330,7 +1438,7 @@ function addon.Nameplates:UpdateAllPlates()
|
||||
-- In combat (health dropping) but no ThreatEngine data.
|
||||
-- Show default high-threat bar.
|
||||
if groupInCombat then
|
||||
self:ApplyThreatToPlate(data, 20, 0) -- was 100,3: showing full aggro for any party-combat mob with no threat data was misleading
|
||||
do local _fp, _fs = GetFallbackThreatForMob(data.plateName); self:ApplyThreatToPlate(data, _fp, _fs) end
|
||||
else
|
||||
data.container:Hide()
|
||||
end
|
||||
@@ -1340,11 +1448,12 @@ function addon.Nameplates:UpdateAllPlates()
|
||||
-- But check if a unit token reveals this specific mob
|
||||
-- is attacking a group member (body pull scenario).
|
||||
if groupInCombat then
|
||||
local unit = self:FindCombatUnitForPlate(data)
|
||||
local unit = self:FindCombatUnitForPlate(data, assignedUnits)
|
||||
if unit then
|
||||
-- Found a combat-verified unit for this plate.
|
||||
data.unit = unit
|
||||
data.guid = addon:GetUnitID(unit)
|
||||
assignedUnits[table.getn(assignedUnits) + 1] = unit
|
||||
local pct, situation = addon.Threat:GetThreatPercent(unit)
|
||||
if pct > 0 then
|
||||
if not data.combatDetectedAt then
|
||||
@@ -1413,3 +1522,52 @@ function addon.Nameplates:RecreateAll()
|
||||
knownPlates = {}
|
||||
self:ScanForNameplates()
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user