Add files via upload

This commit is contained in:
Wurmschwanz
2026-09-30 19:07:47 +02:00
committed by GitHub
parent 3b1105acd4
commit 060cc4aacc
9 changed files with 841 additions and 19 deletions
+2
View File
@@ -22,6 +22,7 @@ spells\mage.lua
spells\druid.lua
spells\shaman.lua
spells\paladin.lua
spells\other_debuffs.lua
spells\registry_shadow.lua
modules\debug.lua
modules\spellprobe.lua
@@ -32,6 +33,7 @@ modules\fullalpha.lua
modules\shagu_darkmode.lua
modules\visualguard.lua
modules\unknown.lua
modules\otherdebuffs.lua
modules\auras.lua
modules\immunities.lua
modules\totems.lua
+54 -1
View File
@@ -35,6 +35,8 @@ BNP.defaults = BNP.defaults or {
crowdControl = true,
separateCCRow = true,
showOtherCCs = true,
otherDebuffs = false,
otherDebuffSelections = {},
pvpImmunities = true,
immunityIconSize = 18,
immunityPosition = "top",
@@ -50,6 +52,7 @@ BNP.defaults = BNP.defaults or {
healthTextFontSize = 9,
healthTextOutline = "outline",
targetFocus = true,
targetOnlyNameplates = false,
targetBorderColorEnabled = false,
targetBorderColor = { r = 1.00, g = 0.20, b = 0.20 },
targetBorderBold = false,
@@ -140,6 +143,8 @@ function BNP:InitConfig()
if BNP_DB.crowdControl == nil then BNP_DB.crowdControl = (BNP_DB.debuffs ~= false) and true or false end
if BNP_DB.separateCCRow == nil then BNP_DB.separateCCRow = self.defaults.separateCCRow end
if BNP_DB.showOtherCCs == nil then BNP_DB.showOtherCCs = self.defaults.showOtherCCs end
if BNP_DB.otherDebuffs == nil then BNP_DB.otherDebuffs = self.defaults.otherDebuffs end
if type(BNP_DB.otherDebuffSelections) ~= "table" then BNP_DB.otherDebuffSelections = {} end
if BNP_DB.pvpImmunities == nil then BNP_DB.pvpImmunities = self.defaults.pvpImmunities end
if BNP_DB.immunityIconSize == nil then BNP_DB.immunityIconSize = BNP_DB.iconSize or self.defaults.immunityIconSize end
if BNP_DB.immunityPosition == nil then BNP_DB.immunityPosition = self.defaults.immunityPosition end
@@ -164,6 +169,7 @@ function BNP:InitConfig()
if BNP_DB.healthTextFontSize == nil then BNP_DB.healthTextFontSize = self.defaults.healthTextFontSize end
if BNP_DB.healthTextOutline == nil then BNP_DB.healthTextOutline = self.defaults.healthTextOutline end
if BNP_DB.targetFocus == nil then BNP_DB.targetFocus = self.defaults.targetFocus end
if BNP_DB.targetOnlyNameplates == nil then BNP_DB.targetOnlyNameplates = self.defaults.targetOnlyNameplates end
if BNP_DB.targetBorderColorEnabled == nil then BNP_DB.targetBorderColorEnabled = self.defaults.targetBorderColorEnabled end
if BNP_DB.targetBorderBold == nil then BNP_DB.targetBorderBold = self.defaults.targetBorderBold end
if type(BNP_DB.targetBorderColor) ~= "table" then
@@ -526,6 +532,10 @@ function BNP:IsTargetFocusEnabled()
return not BNP_DB or BNP_DB.targetFocus ~= false
end
function BNP:IsTargetOnlyNameplatesEnabled()
return BNP_DB and BNP_DB.targetOnlyNameplates == true
end
function BNP:IsTargetScaleEnabled()
return BNP_DB and BNP_DB.targetScaleEnabled == true
end
@@ -728,8 +738,51 @@ function BNP:IsAuraCooldownSpiralEnabled()
return BNP_DB and BNP_DB.cooldownSpiral == true
end
function BNP:AreOtherDebuffsEnabled()
return BNP_DB and BNP_DB.otherDebuffs == true
end
function BNP:IsOtherDebuffSelected(key)
return key and self.OtherDebuffByKey and self.OtherDebuffByKey[key]
and BNP_DB and type(BNP_DB.otherDebuffSelections) == "table"
and BNP_DB.otherDebuffSelections[key] == true
end
function BNP:HasSelectedOtherDebuffs()
if not BNP_DB or type(BNP_DB.otherDebuffSelections) ~= "table" then return false end
local key, enabled
for key, enabled in pairs(BNP_DB.otherDebuffSelections) do
if enabled == true and self.OtherDebuffByKey and self.OtherDebuffByKey[key] then return true end
end
return false
end
function BNP:SetOtherDebuffSelected(key, enabled)
if not key then return end
BNP_DB = BNP_DB or {}
if type(BNP_DB.otherDebuffSelections) ~= "table" then BNP_DB.otherDebuffSelections = {} end
BNP_DB.otherDebuffSelections[key] = enabled and true or false
end
function BNP:SetOtherDebuffClassEnabled(classKey, enabled)
local defs = self.OtherDebuffDefs and self.OtherDebuffDefs[classKey]
if not defs then return end
local i
for i = 1, table.getn(defs) do
self:SetOtherDebuffSelected(defs[i].key, enabled)
end
end
function BNP:SetAllOtherDebuffsEnabled(enabled)
local classOrder = self.OtherDebuffClassOrder or {}
local i
for i = 1, table.getn(classOrder) do
self:SetOtherDebuffClassEnabled(classOrder[i], enabled)
end
end
function BNP:AreAnyAurasEnabled()
return self:AreDebuffsEnabled() or self:AreCrowdControlEnabled()
return self:AreDebuffsEnabled() or self:AreCrowdControlEnabled() or self:AreOtherDebuffsEnabled()
end
function BNP:IsSeparateCCRowEnabled()
+257 -8
View File
@@ -605,11 +605,226 @@ function BNP:OpenAuraRecorder()
self:OpenEmergencyAuraRecorder()
end
local function BuildOtherDebuffsOptions(frame, otherPage, createSection)
createSection(otherPage, "Other Class Debuffs", -4)
local otherDebuffsEnabled = CreateCheck(otherPage, "Enable Other Class Debuffs", -28, function()
BNP_DB.otherDebuffs = this:GetChecked() and true or false
if BNP.RefreshOtherDebuffs then BNP:RefreshOtherDebuffs() end
if frame.RefreshOtherDebuffPage then frame:RefreshOtherDebuffPage() end
end, 22)
otherDebuffsEnabled:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Other Class Debuffs", 1, 0.82, 0)
GameTooltip:AddLine("Shows selected non-CC debuffs applied by other players/classes. The feature is off by default and never changes BNP's own-cast tracking.", 1, 1, 1, true)
GameTooltip:Show()
end)
otherDebuffsEnabled:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.otherDebuffsEnabledCheck = otherDebuffsEnabled
local otherNote = otherPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
otherNote:SetPoint("TOPLEFT", otherPage, "TOPLEFT", 28, -58)
otherNote:SetWidth(390)
otherNote:SetJustifyH("LEFT")
otherNote:SetText("Select which non-CC foreign debuffs BNP should display. Crowd Control stays in BNP's dedicated CC system.")
local OTHER_TAB_W = 80
local OTHER_TAB_H = 20
local OTHER_TAB_GAP = 3
local OTHER_TAB_X = 5
local OTHER_TAB_Y = -90
frame.otherDebuffClassTabs = {}
frame.otherSelectedClass = "warrior"
local classOrder = BNP.OtherDebuffClassOrder or { "warrior", "rogue", "hunter", "mage", "warlock", "priest", "druid", "shaman", "paladin" }
local classLabels = BNP.OtherDebuffClassLabels or {}
local classColors = BNP.OtherDebuffClassColors or {}
local i
for i = 1, table.getn(classOrder) do
local classKey = classOrder[i]
local tab = CreateFrame("Button", nil, otherPage)
tab:SetWidth(OTHER_TAB_W)
tab:SetHeight(OTHER_TAB_H)
tab:EnableMouse(true)
if i <= 5 then
tab:SetPoint("TOPLEFT", otherPage, "TOPLEFT", OTHER_TAB_X + (i - 1) * (OTHER_TAB_W + OTHER_TAB_GAP), OTHER_TAB_Y)
else
tab:SetPoint("TOPLEFT", otherPage, "TOPLEFT", OTHER_TAB_X + (i - 6) * (OTHER_TAB_W + OTHER_TAB_GAP), OTHER_TAB_Y - OTHER_TAB_H - 3)
end
local color = classColors[classKey] or { 0.6, 0.6, 0.6 }
local bg = tab:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(tab)
bg:SetTexture(color[1] * 0.4, color[2] * 0.4, color[3] * 0.4, 0.85)
tab.BNPBG = bg
local text = tab:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
text:SetPoint("CENTER", tab, "CENTER", 0, 0)
text:SetText(classLabels[classKey] or classKey)
text:SetTextColor(color[1], color[2], color[3])
tab.BNPText = text
tab.BNPClassKey = classKey
tab:SetScript("OnClick", function()
frame.otherSelectedClass = this.BNPClassKey
if frame.RefreshOtherDebuffPage then frame:RefreshOtherDebuffPage() end
end)
tab:SetScript("OnEnter", function()
local c = classColors[this.BNPClassKey] or { 0.6, 0.6, 0.6 }
if this.BNPBG then this.BNPBG:SetTexture(c[1] * 0.6, c[2] * 0.6, c[3] * 0.6, 1) end
end)
tab:SetScript("OnLeave", function()
local c = classColors[this.BNPClassKey] or { 0.6, 0.6, 0.6 }
local selected = frame.otherSelectedClass == this.BNPClassKey
if this.BNPBG then
if selected then this.BNPBG:SetTexture(c[1] * 0.55, c[2] * 0.55, c[3] * 0.55, 1)
else this.BNPBG:SetTexture(c[1] * 0.4, c[2] * 0.4, c[3] * 0.4, 0.85) end
end
end)
frame.otherDebuffClassTabs[classKey] = tab
end
local selectedClassLabel = otherPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
selectedClassLabel:SetPoint("TOPLEFT", otherPage, "TOPLEFT", 14, -147)
selectedClassLabel:SetText("Warrior")
frame.otherSelectedClassLabel = selectedClassLabel
local enableClassButton = CreateFrame("Button", nil, otherPage, "UIPanelButtonTemplate")
enableClassButton:SetWidth(92)
enableClassButton:SetHeight(22)
enableClassButton:SetPoint("TOPRIGHT", otherPage, "TOPRIGHT", -112, -140)
enableClassButton:SetText("Enable All")
enableClassButton:SetScript("OnClick", function()
BNP:SetAllOtherDebuffsEnabled(true)
if BNP.RefreshOtherDebuffs then BNP:RefreshOtherDebuffs() end
if frame.RefreshOtherDebuffPage then frame:RefreshOtherDebuffPage() end
end)
local disableClassButton = CreateFrame("Button", nil, otherPage, "UIPanelButtonTemplate")
disableClassButton:SetWidth(92)
disableClassButton:SetHeight(22)
disableClassButton:SetPoint("TOPRIGHT", otherPage, "TOPRIGHT", -14, -140)
disableClassButton:SetText("Disable All")
disableClassButton:SetScript("OnClick", function()
BNP:SetAllOtherDebuffsEnabled(false)
if BNP.RefreshOtherDebuffs then BNP:RefreshOtherDebuffs() end
if frame.RefreshOtherDebuffPage then frame:RefreshOtherDebuffPage() end
end)
frame.otherEnableClassButton = enableClassButton
frame.otherDisableClassButton = disableClassButton
frame.otherDebuffRows = {}
for i = 1, 12 do
local row = CreateFrame("Frame", nil, otherPage)
row:SetWidth(405)
row:SetHeight(28)
row:SetPoint("TOPLEFT", otherPage, "TOPLEFT", 14, -174 - ((i - 1) * 29))
local icon = row:CreateTexture(nil, "ARTWORK")
icon:SetWidth(20)
icon:SetHeight(20)
icon:SetPoint("LEFT", row, "LEFT", 2, 0)
icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
row.BNPIcon = icon
local check = CreateFrame("CheckButton", nil, row, "UICheckButtonTemplate")
check:SetWidth(24)
check:SetHeight(24)
check:SetPoint("LEFT", icon, "RIGHT", 5, 0)
row.BNPCheck = check
local label = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
label:SetPoint("LEFT", check, "RIGHT", 3, 0)
label:SetWidth(300)
label:SetJustifyH("LEFT")
row.BNPLabel = label
check:SetScript("OnClick", function()
if not this.BNPDebuffKey then return end
BNP:SetOtherDebuffSelected(this.BNPDebuffKey, this:GetChecked() and true or false)
if BNP.RefreshOtherDebuffs then BNP:RefreshOtherDebuffs() end
end)
check:SetScript("OnEnter", function()
local def = this.BNPDebuffDef
if not def then return end
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(def.label or def.key, 1, 0.82, 0)
if def.description then GameTooltip:AddLine(def.description, 1, 1, 1, true) end
GameTooltip:Show()
end)
check:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.otherDebuffRows[i] = row
end
local emptyClassNote = otherPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
emptyClassNote:SetPoint("TOPLEFT", otherPage, "TOPLEFT", 20, -184)
emptyClassNote:SetWidth(380)
emptyClassNote:SetJustifyH("LEFT")
emptyClassNote:SetText("No shared Shaman debuffs are defined in the imported Cursive class list.")
emptyClassNote:Hide()
frame.otherEmptyClassNote = emptyClassNote
function frame:RefreshOtherDebuffPage()
if self.otherDebuffsEnabledCheck then
self.otherDebuffsEnabledCheck:SetChecked(BNP.AreOtherDebuffsEnabled and BNP:AreOtherDebuffsEnabled() or false)
end
local selected = self.otherSelectedClass or "warrior"
local defs = BNP.OtherDebuffDefs and BNP.OtherDebuffDefs[selected] or nil
local selectedColor = classColors[selected] or { 1, 0.82, 0 }
if self.otherSelectedClassLabel then
self.otherSelectedClassLabel:SetText(classLabels[selected] or selected)
self.otherSelectedClassLabel:SetTextColor(selectedColor[1], selectedColor[2], selectedColor[3])
end
local classKey, tab
for classKey, tab in pairs(self.otherDebuffClassTabs or {}) do
local c = classColors[classKey] or { 0.6, 0.6, 0.6 }
if tab.BNPBG then
if classKey == selected then tab.BNPBG:SetTexture(c[1] * 0.55, c[2] * 0.55, c[3] * 0.55, 1)
else tab.BNPBG:SetTexture(c[1] * 0.4, c[2] * 0.4, c[3] * 0.4, 0.85) end
end
end
local count = defs and table.getn(defs) or 0
if self.otherEmptyClassNote then
if count == 0 then self.otherEmptyClassNote:Show() else self.otherEmptyClassNote:Hide() end
end
local rowIndex
for rowIndex = 1, table.getn(self.otherDebuffRows or {}) do
local row = self.otherDebuffRows[rowIndex]
local def = defs and defs[rowIndex] or nil
if def then
row.BNPDef = def
row.BNPCheck.BNPDebuffKey = def.key
row.BNPCheck.BNPDebuffDef = def
row.BNPCheck:SetChecked(BNP.IsOtherDebuffSelected and BNP:IsOtherDebuffSelected(def.key) or false)
row.BNPIcon:SetTexture(BNP.GetOtherDebuffTexture and BNP:GetOtherDebuffTexture(def) or "Interface\\Icons\\INV_Misc_QuestionMark")
row.BNPLabel:SetText(def.label or def.key)
row.BNPLabel:SetTextColor(selectedColor[1], selectedColor[2], selectedColor[3])
row:Show()
else
row.BNPDef = nil
row.BNPCheck.BNPDebuffKey = nil
row.BNPCheck.BNPDebuffDef = nil
row:Hide()
end
end
end
end
function BNP:CreateOptions()
if self.optionsFrame then return end
local frame = CreateFrame("Frame", "BNPOptionsFrame", UIParent)
frame:SetWidth(430)
frame:SetWidth(470)
frame:SetHeight(710)
-- Keep the top edge in roughly the same place and add the extra room
-- downward. The Icons page uses the height for Totems, Raid Marks and
@@ -684,17 +899,19 @@ function BNP:CreateOptions()
totems = CreatePage(),
castbar = CreatePage(),
target = CreatePage(),
other = CreatePage(),
tools = CreatePage(),
}
frame.tabs = {}
local tabDefs = {
{ key = "nameplates", label = "Nameplates", width = 76 },
{ key = "auras", label = "Auras", width = 52 },
{ key = "totems", label = "Icons", width = 58 },
{ key = "castbar", label = "Castbar", width = 60 },
{ key = "target", label = "Target", width = 56 },
{ key = "tools", label = "Tools", width = 50 },
{ key = "nameplates", label = "Nameplates", width = 72 },
{ key = "auras", label = "Auras", width = 50 },
{ key = "totems", label = "Icons", width = 52 },
{ key = "castbar", label = "Castbar", width = 58 },
{ key = "target", label = "Target", width = 54 },
{ key = "other", label = "Other Debuffs", width = 84 },
{ key = "tools", label = "Tools", width = 46 },
}
function frame:ShowTab(key)
@@ -718,9 +935,12 @@ function BNP:CreateOptions()
end
end
self.selectedTab = key
if key == "other" and self.RefreshOtherDebuffPage then
self:RefreshOtherDebuffPage()
end
end
local tabX = 28
local tabX = 15
local i
for i = 1, table.getn(tabDefs) do
local def = tabDefs[i]
@@ -1740,6 +1960,21 @@ function BNP:CreateOptions()
thickArrows:SetPoint("TOPLEFT", targetPage, "TOPLEFT", 204, -220)
frame.targetArrowThickCheck = thickArrows
local targetOnlyNameplates = CreateCheck(targetPage, "Only Show Target", -250, function()
BNP_DB.targetOnlyNameplates = this:GetChecked() and true or false
if BNP.RefreshTargetOnlyNameplates then BNP:RefreshTargetOnlyNameplates() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end)
targetOnlyNameplates:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Only Show Target", 1, 0.82, 0)
GameTooltip:AddLine("While a target is selected, hides every other nameplate by making it fully transparent.", 1, 1, 1, true)
GameTooltip:AddLine("With no target selected, all nameplates stay visible so you can still select one. Disabling this option immediately restores normal visibility.", 0.8, 0.8, 0.8, true)
GameTooltip:Show()
end)
targetOnlyNameplates:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.targetOnlyNameplatesCheck = targetOnlyNameplates
-- Target border
CreateSection(targetPage, "Target Border", -280)
@@ -1803,6 +2038,9 @@ function BNP:CreateOptions()
end)
frame.targetScaleSlider = targetScale
-- OTHER DEBUFFS TAB ------------------------------------------------------
BuildOtherDebuffsOptions(frame, frame.pages.other, CreateSection)
-- TOOLS TAB --------------------------------------------------------------
local toolsPage = frame.pages.tools
CreateSection(toolsPage, "Tools", -4)
@@ -1971,6 +2209,12 @@ function BNP:CreateOptions()
end
SetCheckEnabled(self.targetArrowThickCheck, arrowsEnabled)
local targetOnlyEnabled = BNP.IsTargetOnlyNameplatesEnabled and BNP:IsTargetOnlyNameplatesEnabled() or false
if self.nonTargetAlphaSlider then
self.nonTargetAlphaSlider:SetAlpha(targetOnlyEnabled and 0.45 or 1.0)
if self.nonTargetAlphaSlider.EnableMouse then self.nonTargetAlphaSlider:EnableMouse(not targetOnlyEnabled) end
end
local targetScaleEnabled = BNP.IsTargetScaleEnabled and BNP:IsTargetScaleEnabled() or false
if self.targetScaleSlider then
self.targetScaleSlider:SetAlpha(targetScaleEnabled and 1.0 or 0.45)
@@ -2109,6 +2353,8 @@ function BNP:SyncOptions()
if frame.crowdControlCheck then frame.crowdControlCheck:SetChecked(self:AreCrowdControlEnabled()) end
if frame.separateCCRowCheck then frame.separateCCRowCheck:SetChecked(self:IsSeparateCCRowEnabled()) end
if frame.showOtherCCsCheck then frame.showOtherCCsCheck:SetChecked(self:ShowOtherPlayersCCs()) end
if frame.otherDebuffsEnabledCheck then frame.otherDebuffsEnabledCheck:SetChecked(self:AreOtherDebuffsEnabled()) end
if frame.RefreshOtherDebuffPage then frame:RefreshOtherDebuffPage() end
if frame.pvpImmunitiesCheck then frame.pvpImmunitiesCheck:SetChecked(self:ArePvPImmunitiesEnabled()) end
if frame.immunityPositionDropdown then
local position = self:GetImmunityPosition()
@@ -2169,6 +2415,9 @@ function BNP:SyncOptions()
UIDropDownMenu_SetText(labels[outline] or "Outline", frame.healthTextOutlineDropdown)
end
frame.targetFocusCheck:SetChecked(self:IsTargetFocusEnabled())
if frame.targetOnlyNameplatesCheck then
frame.targetOnlyNameplatesCheck:SetChecked(self.IsTargetOnlyNameplatesEnabled and self:IsTargetOnlyNameplatesEnabled() or false)
end
if frame.targetBorderColorCheck then
frame.targetBorderColorCheck:SetChecked(self.IsTargetBorderColorEnabled and self:IsTargetBorderColorEnabled() or false)
end
+65 -3
View File
@@ -367,6 +367,21 @@ end
local function ApplyTargetAuraPriority(plate, container)
if not container then return end
-- Aura/CC containers are siblings of the projected nameplate and therefore
-- do not inherit its alpha. In Target Only mode explicitly mirror the plate
-- alpha so hidden non-target plates cannot leave floating DoT/CC icons behind.
-- DesiredAlpha fails open while target identity is unresolved, so this also
-- stays flicker-safe during target switches and dense nameplate recycling.
if BNP.IsTargetOnlyNameplatesEnabled and BNP:IsTargetOnlyNameplatesEnabled() then
local alpha = 1
if plate and plate.GetAlpha then
local ok, value = pcall(plate.GetAlpha, plate)
if ok and value ~= nil then alpha = value end
end
SetTargetPriorityAuraAlpha(container, alpha)
return
end
-- Flicker fix: aura opacity must not depend on transient target/nameplate
-- resolution. In dense raid stacks ClassicAPI/SuperWoW can briefly resolve
-- a projected plate differently, which previously made the whole debuff/CC
@@ -977,6 +992,7 @@ local function ClearGUIDAuraState(guid)
if BNP.svExpiryByGUID then BNP.svExpiryByGUID[guid] = nil end
if BNP.svRenderRevisionByGUID then BNP.svRenderRevisionByGUID[guid] = nil end
if BNP.guidLiveCCs then BNP.guidLiveCCs[guid] = nil end
if BNP.guidOtherDebuffs then BNP.guidOtherDebuffs[guid] = nil end
if BNP.pendingAuras then BNP.pendingAuras[guid] = nil end
if BNP.pvpAuraProtection then BNP.pvpAuraProtection[guid] = nil end
if BNP.darkHarvest and BNP.darkHarvest.guid == guid then BNP.darkHarvest = nil end
@@ -998,6 +1014,7 @@ local function ClearHarmfulAuraStateOnly(guid)
if BNP.svExpiryByGUID then BNP.svExpiryByGUID[guid] = nil end
if BNP.svRenderRevisionByGUID then BNP.svRenderRevisionByGUID[guid] = nil end
if BNP.guidLiveCCs then BNP.guidLiveCCs[guid] = nil end
if BNP.guidOtherDebuffs then BNP.guidOtherDebuffs[guid] = nil end
if BNP.pendingAuras then BNP.pendingAuras[guid] = nil end
if BNP.pvpAuraProtection then BNP.pvpAuraProtection[guid] = nil end
if BNP.darkHarvest and BNP.darkHarvest.guid == guid then BNP.darkHarvest = nil end
@@ -2684,6 +2701,10 @@ local function SyncSharedAuras(unit, guid, now, snapshot, eventAuthoritative)
end
-- Optional other-class debuff tracking lives in modules/otherdebuffs.lua.
-- auras.lua only consumes that cache for rendering and supplies live snapshots.
-- Live aura removal ---------------------------------------------------------
-- Timers alone are not enough for CC/roots/traps: effects may be dispelled,
-- broken by damage, removed by trinkets, or otherwise end early.
@@ -2744,7 +2765,7 @@ local function NeedsExactLiveRemoval(key)
end
local function IsCrowdControlDef(def)
return def and EXACT_LIVE_REMOVAL[def.key] and true or false
return def and (def.isCC or EXACT_LIVE_REMOVAL[def.key]) and true or false
end
local function GetEntryIconSize(entry, forceCC)
@@ -3071,6 +3092,9 @@ local function UpdateCCRow(plate, guid, cache, now)
end
end
-- Other Debuffs intentionally excludes Crowd Control. Foreign CCs are
-- handled only by BNP's dedicated CC tracker below.
if WantsForeignCCs() then
local live = guid and BNP.guidLiveCCs[guid]
local key, aura
@@ -3561,6 +3585,41 @@ local function UpdatePlate(plate, now)
end
end
-- Selected non-CC live debuffs from other players/classes are appended
-- after the local player's own entries. Crowd Control is handled exclusively
-- by BNP's dedicated CC tracker and never enters this list.
if BNP:WantsOtherDebuffs() and guid then
local otherLive = BNP.guidOtherDebuffs and BNP.guidOtherDebuffs[guid]
local classOrder = BNP.OtherDebuffClassOrder or {}
local classIndex, classKey, defs, defIndex, otherDef, otherAura
for classIndex = 1, table.getn(classOrder) do
if activeCount >= MAX_VISIBLE_ICONS then break end
classKey = classOrder[classIndex]
defs = BNP.OtherDebuffDefs and BNP.OtherDebuffDefs[classKey] or nil
if defs then
for defIndex = 1, table.getn(defs) do
if activeCount >= MAX_VISIBLE_ICONS then break end
otherDef = defs[defIndex]
if BNP:IsOtherDebuffDefSelected(otherDef) then
otherAura = otherLive and otherLive[otherDef.key] or nil
if otherAura and otherAura.livePresence
and not BNP:ActiveAuraHasSpellID(active, activeCount, otherAura.spellID) then
activeCount = activeCount + 1
local entry = activePool[activeCount]
active[activeCount] = entry
entry.def = otherDef
entry.aura = otherAura
local otherRemaining = otherAura.expires and (otherAura.expires - now) or nil
if otherRemaining and otherRemaining <= 0 then otherRemaining = nil end
entry.remaining = otherRemaining
entry.svRenderRevision = nil
end
end
end
end
end
end
if activeCount > 1 then
table.sort(active, BNP._AuraEntryOrderLess)
end
@@ -3725,6 +3784,7 @@ function BNP:_HandleClassicUnitAura(token, plate)
-- this exact event snapshot. eventAuthoritative=true removes the old
-- multi-miss protection that was only required for projected SuperWoW units.
SyncSharedAuras(token, guid, now, snapshot, true)
BNP:SyncOtherDebuffs(token, guid, now, snapshot, true)
if BNP.guidAuras[guid] then
SyncAuraRemoval(token, guid, now, snapshot, GetTargetGUID(), true)
end
@@ -3900,11 +3960,12 @@ renderer:SetScript("OnUpdate", function()
if doTargetRemovalScan and targetGUID and not targetSuppressed then
local targetSnapshot = nil
if HAS_SHARED_AURAS or CacheHasTrackedAuras(BNP.guidAuras[targetGUID]) then
if HAS_SHARED_AURAS or (BNP.WantsOtherDebuffs and BNP:WantsOtherDebuffs()) or CacheHasTrackedAuras(BNP.guidAuras[targetGUID]) then
targetSnapshot = CaptureLiveDebuffSnapshot("target", now)
end
SyncSharedAuras("target", targetGUID, now, targetSnapshot)
BNP:SyncOtherDebuffs("target", targetGUID, now, targetSnapshot)
if BNP.guidAuras[targetGUID] then
SyncAuraRemoval("target", targetGUID, now, targetSnapshot, targetGUID)
@@ -3951,11 +4012,12 @@ renderer:SetScript("OnUpdate", function()
local identityTrusted = stableGUID and stableGUID == guid
if identityTrusted then
local snapshot = nil
if HAS_SHARED_AURAS or CacheHasTrackedAuras(BNP.guidAuras[stableGUID]) then
if HAS_SHARED_AURAS or (BNP.WantsOtherDebuffs and BNP:WantsOtherDebuffs()) or CacheHasTrackedAuras(BNP.guidAuras[stableGUID]) then
snapshot = CaptureLiveDebuffSnapshot(token, now)
end
SyncSharedAuras(token, stableGUID, now, snapshot)
BNP:SyncOtherDebuffs(token, stableGUID, now, snapshot)
-- Non-target exact-removal also requires multiple misses below, so
-- crowded raid aura lists cannot make Banish/CC blink.
+105 -4
View File
@@ -271,8 +271,47 @@ local function IsForeignTagged(plate)
end
local function DesiredAlpha(plate, targetGUID)
-- No selected target: keep all nameplates fully visible.
targetGUID = targetGUID or GetTargetGUID()
-- Target Only is deliberately implemented with alpha instead of Hide()/Show().
-- Blizzard owns projected nameplate visibility and recycles these frames as
-- units enter/leave the screen. Setting alpha to zero hides only the visual
-- while leaving the client free to manage the underlying frame safely.
--
-- With no selected target, keep every plate visible so the player can still
-- click a nameplate to acquire a target. Once a target exists, Target Only
-- must be fail-closed: newly projected/recycled plates are hidden unless they
-- are positively identified as the current target. Failing open here caused
-- every newly shown plate to pop back in while rotating the camera because
-- the projected unit token can be unavailable for a few frames.
local targetOnly = BNP.IsTargetOnlyNameplatesEnabled and
BNP:IsTargetOnlyNameplatesEnabled()
if targetOnly then
if not targetGUID then return 1 end
local strictTargetPlate = GetStrictTargetPlate(targetGUID)
if strictTargetPlate then
if plate == strictTargetPlate then return 1 end
return 0
end
-- Strict resolution can be briefly unavailable while Blizzard recycles a
-- projected plate. During that window only accept exact unit/GUID evidence;
-- never use the visual name/level fallback because identical mobs could
-- otherwise make several plates visible at once.
local token = GetPlateToken(plate)
if token and UnitIsUnit then
local ok, sameUnit = pcall(UnitIsUnit, token, "target")
if ok and sameUnit then return 1 end
end
local plateGUID = GetVerifiedPlateGUID(plate)
if plateGUID and plateGUID == targetGUID then return 1 end
return 0
end
-- No selected target: keep all nameplates fully visible.
if not targetGUID then return 1 end
local classicTargetPlate, classicTargetAvailable =
@@ -826,15 +865,54 @@ local function ApplyTargetArrows(plate)
end
local function ApplyTargetOnlyPresentation(plate, alpha, targetOnly)
if not plate then return end
-- Auras/CC/immunities are sibling frames, so they do not inherit the
-- projected plate alpha. Keep them in lockstep with Target Only and restore
-- them to full opacity immediately when the option is disabled.
local detachedAlpha = targetOnly and alpha or 1
local function SetObjectAlpha(object)
if object and object.SetAlpha and
(not object.GetAlpha or object:GetAlpha() ~= detachedAlpha) then
object:SetAlpha(detachedAlpha)
end
end
SetObjectAlpha(plate.BNPAuraContainer)
SetObjectAlpha(plate.BNPCCContainer)
SetObjectAlpha(plate.BNPImmunityContainer)
-- BNP routes nameplate clicks through BNPScaleWrapper. Alpha-0 frames can
-- still receive mouse input in the WoW UI, so disable the wrapper hit area
-- for hidden non-targets and restore it for visible plates.
local wrapper = plate.BNPScaleWrapper
if wrapper and wrapper.EnableMouse then
local shouldEnableMouse = (not targetOnly) or alpha > 0
wrapper:EnableMouse(shouldEnableMouse)
if not shouldEnableMouse and BNP.BNPMouseoverOwner == wrapper then
BNP.BNPMouseoverOwner = nil
wrapper.BNPMouseoverUnit = nil
if SetMouseoverUnit then pcall(SetMouseoverUnit) end
if GameTooltip and GameTooltip.Hide then GameTooltip:Hide() end
end
end
end
local function ApplyTargetAlpha(plate)
if not plate or not plate:IsShown() then return end
local targetGUID = GetTargetGUID()
local totemIconOnly = plate.BNPTotemLastKey and
BNP.AreTotemIndicatorsEnabled and BNP:AreTotemIndicatorsEnabled()
local wanted = totemIconOnly and 1 or DesiredAlpha(plate, targetGUID)
local targetOnly = BNP.IsTargetOnlyNameplatesEnabled and
BNP:IsTargetOnlyNameplatesEnabled()
-- Totem icon replacement normally stays fully opaque, but Target Only must
-- apply to every projected nameplate consistently, including totems.
local wanted = (totemIconOnly and not targetOnly) and 1 or DesiredAlpha(plate, targetGUID)
if plate:GetAlpha() ~= wanted then
plate:SetAlpha(wanted)
end
ApplyTargetOnlyPresentation(plate, wanted, targetOnly)
-- Publish a STRICT single target identity for detached aura containers and
-- target-only visuals. Do not reuse the alpha resolver here: its intentional
@@ -901,7 +979,14 @@ if BNP.libnameplate then
if not current then return end
InvalidateTargetResolution()
current.BNPAlphaIdentityHoldUntil = GetTime() + ALPHA_IDENTITY_HOLD_TIME
if current.GetAlpha and current.SetAlpha and current:GetAlpha() ~= 1 then
-- Do not reset a recycled/newly projected plate to alpha 1 while Target
-- Only is active. That one-frame reset was visible as plates popping back
-- in when turning the camera. ApplyTargetAlpha below decides immediately
-- whether the frame is the target or must stay hidden.
local targetOnly = BNP.IsTargetOnlyNameplatesEnabled and
BNP:IsTargetOnlyNameplatesEnabled()
if not targetOnly and current.GetAlpha and current.SetAlpha and current:GetAlpha() ~= 1 then
current:SetAlpha(1)
end
InstallAlphaGuard(current)
@@ -924,8 +1009,12 @@ targetEvents:SetScript("OnEvent", function()
local _, classicTargetAvailable = GetClassicAPITargetPlate(targetGUID)
local holdUntil = GetTime() + ALPHA_IDENTITY_HOLD_TIME
local plate
local targetOnly = BNP.IsTargetOnlyNameplatesEnabled and
BNP:IsTargetOnlyNameplatesEnabled()
for plate in pairs(BNP.plates or {}) do
if classicTargetAvailable then
if classicTargetAvailable or targetOnly then
-- Target Only is intentionally fail-closed and therefore does not use
-- the legacy full-alpha identity hold during target switches.
plate.BNPAlphaIdentityHoldUntil = nil
ApplyTargetAlpha(plate)
if BNP.MaintainTargetScale then BNP:MaintainTargetScale(plate) end
@@ -963,3 +1052,15 @@ function BNP:RefreshNonTargetAlpha()
ApplyTargetAlpha(plate)
end
end
function BNP:RefreshTargetOnlyNameplates()
InvalidateTargetResolution()
local plate
for plate in pairs(self.plates or {}) do
ApplyTargetAlpha(plate)
end
-- Aura/CC rows are sibling frames so they do not inherit plate alpha. Make
-- them follow Target Only explicitly, then restore them immediately when the
-- option is disabled.
if self.RefreshAuraPriorityAlpha then self:RefreshAuraPriorityAlpha() end
end
+37
View File
@@ -30,6 +30,41 @@ local function EnsureCustomNameOverlay(plate)
return overlay
end
-- The custom name uses a separate FontString so Blizzard cannot recolor it.
-- A newly-created FontString does not automatically inherit the native
-- nameplate text shadow, so mirror that presentation here as well.
local function SyncCustomNameShadow(source, target)
if not source or not target then return end
local shadowCopied = false
if source.GetShadowColor and target.SetShadowColor then
local r, g, b, a = source:GetShadowColor()
if r ~= nil and g ~= nil and b ~= nil then
target:SetShadowColor(r, g, b, a or 1)
shadowCopied = true
end
end
-- Vanilla/older clients or replacement FontStrings may not expose the
-- shadow getters. Keep the classic Blizzard-style drop shadow in that case.
if not shadowCopied and target.SetShadowColor then
target:SetShadowColor(0, 0, 0, 1)
end
local offsetCopied = false
if source.GetShadowOffset and target.SetShadowOffset then
local x, y = source:GetShadowOffset()
if x ~= nil and y ~= nil then
target:SetShadowOffset(x, y)
offsetCopied = true
end
end
if not offsetCopied and target.SetShadowOffset then
target:SetShadowOffset(1, -1)
end
end
local function SyncCustomNameOverlay(plate)
if not plate or not plate.name then return end
@@ -64,6 +99,8 @@ local function SyncCustomNameOverlay(plate)
if font and size then overlay:SetFont(font, size, flags) end
end
SyncCustomNameShadow(plate.name, overlay)
if plate.name.GetText and overlay.SetText then
overlay:SetText(plate.name:GetText() or "")
end
+152
View File
@@ -0,0 +1,152 @@
BNP = BNP or {}
-- Optional live debuffs from other players/classes.
-- Kept outside auras.lua because that file is already close to Vanilla Lua's
-- per-chunk local-variable limit. This module owns only the foreign live cache;
-- BNP's proven own-cast tracker remains completely separate.
BNP.guidOtherDebuffs = BNP.guidOtherDebuffs or {}
local SEEN = {}
local seenGeneration = 0
function BNP:WantsOtherDebuffs()
return self.AreOtherDebuffsEnabled and self:AreOtherDebuffsEnabled()
and self.HasSelectedOtherDebuffs and self:HasSelectedOtherDebuffs()
and self.OtherDebuffByID ~= nil
end
function BNP:IsOtherDebuffDefSelected(def)
return def and self.IsOtherDebuffSelected and self:IsOtherDebuffSelected(def.key)
end
function BNP:SyncOtherDebuffs(unit, guid, now, snapshot, eventAuthoritative)
if not unit or not guid then return end
if not self:WantsOtherDebuffs() then
self.guidOtherDebuffs[guid] = nil
return
end
seenGeneration = seenGeneration + 1
local generation = seenGeneration
local live = self.guidOtherDebuffs[guid]
local i
local maxIndex = snapshot and snapshot.count or 64
for i = 1, maxIndex do
local texture, stacks, auraSpellID, duration, expirationTime, source
if snapshot then
local entry = snapshot.entries[i]
if not entry then break end
texture = entry[1]
stacks = entry[2]
auraSpellID = entry[4]
duration = entry[5]
expirationTime = entry[6]
source = entry[7]
else
local dtype
texture, stacks, dtype, auraSpellID, duration, expirationTime, source = self:_ReadClassicDebuff(unit, i)
if not texture then break end
end
local def = auraSpellID and self.OtherDebuffByID and self.OtherDebuffByID[auraSpellID] or nil
-- This feature is specifically for auras from other players/classes. When
-- ClassicAPI positively identifies our own source, let the proven own-cast
-- tracker remain the only owner of that icon/timer.
if def and self:IsOtherDebuffDefSelected(def) and not self:_IsOwnClassicAuraSource(source) then
SEEN[def.key] = generation
if not live then
live = {}
self.guidOtherDebuffs[guid] = live
end
local aura = live[def.key]
if not aura then
aura = {
firstSeen = now,
order = def.order or (1000 + i),
}
live[def.key] = aura
end
local previousStacks = tonumber(aura.stacks) or 0
local currentStacks = tonumber(stacks) or 0
aura.def = def
aura.spellID = auraSpellID
aura.texture = texture or (self.GetOtherDebuffTexture and self:GetOtherDebuffTexture(def)) or def.texture
aura.stacks = currentStacks
aura.lastSeen = now
aura.livePresence = true
aura.eventDriven = eventAuthoritative and true or false
aura.missingScans = nil
local fallbackDuration = self.GetOtherDebuffDuration and self:GetOtherDebuffDuration(def, auraSpellID) or def.duration
local liveDuration = tonumber(duration)
local liveExpires = tonumber(expirationTime)
if liveExpires and liveExpires > now then
aura.duration = liveDuration and liveDuration > 0 and liveDuration or fallbackDuration
aura.expires = liveExpires
aura.estimated = nil
elseif not aura.expires then
aura.duration = fallbackDuration
if fallbackDuration and fallbackDuration > 0 then
aura.expires = now + fallbackDuration
aura.estimated = true
end
elseif currentStacks > previousStacks and fallbackDuration and fallbackDuration > 0 then
-- Stack-building raid debuffs commonly refresh their duration when a
-- new stack lands. Only use this estimate when ClassicAPI supplied no
-- authoritative expiration timestamp.
aura.duration = fallbackDuration
aura.expires = now + fallbackDuration
aura.estimated = true
end
end
end
if live then
local key, aura
for key, aura in pairs(live) do
local def = aura and aura.def
if not def or not self:IsOtherDebuffDefSelected(def) then
live[key] = nil
elseif SEEN[key] ~= generation then
aura.missingScans = (aura.missingScans or 0) + 1
if eventAuthoritative or (aura.missingScans >= 3 and now - (aura.lastSeen or 0) >= 0.40) then
live[key] = nil
end
else
aura.missingScans = nil
end
end
end
end
function BNP:RefreshOtherDebuffs()
if not self:AreOtherDebuffsEnabled() then
self.guidOtherDebuffs = {}
else
local guid, live, key, aura
for guid, live in pairs(self.guidOtherDebuffs or {}) do
for key, aura in pairs(live or {}) do
if not aura or not aura.def or not self:IsOtherDebuffSelected(aura.def.key) then
live[key] = nil
end
end
end
end
if self.RefreshDebuffVisibility then self:RefreshDebuffVisibility() end
end
function BNP:ActiveAuraHasSpellID(active, count, spellID)
if not spellID then return false end
local i
for i = 1, count do
local entry = active[i]
if entry and entry.aura and entry.aura.spellID == spellID then return true end
end
return false
end
+9 -3
View File
@@ -207,9 +207,15 @@ local function SuppressTotemPlateVisuals(plate, forceCapture)
state.indicator = indicator
end
-- Important PvP totems should remain clearly visible even when BNP's
-- non-target alpha option would normally fade the underlying nameplate.
if plate.SetAlpha and plate.GetAlpha and plate:GetAlpha() ~= 1 then plate:SetAlpha(1) end
-- Important PvP totems normally stay fully visible even when BNP's
-- non-target alpha option fades regular plates. Target Only is the explicit
-- exception: the totem icon is a child of the plate and must disappear with
-- every other non-target nameplate.
local targetOnly = BNP.IsTargetOnlyNameplatesEnabled and
BNP:IsTargetOnlyNameplatesEnabled()
if not targetOnly and plate.SetAlpha and plate.GetAlpha and plate:GetAlpha() ~= 1 then
plate:SetAlpha(1)
end
if forceCapture or not state.nextCapture or now >= state.nextCapture then
CaptureTotemPlateVisuals(plate, state, now)
+160
View File
@@ -0,0 +1,160 @@
BNP = BNP or {}
-- Optional non-CC debuffs from other players/classes.
-- The selectable list and grouping are based on the Cursive Raid class menu,
-- adapted to BNP's nameplate aura renderer. Crowd Control is intentionally
-- excluded here because BNP's dedicated CC tracker/menu already owns it.
-- These definitions remain separate from the local player's own-cast tracker.
BNP.OtherDebuffClassOrder = {
"warrior", "rogue", "hunter", "mage", "warlock",
"priest", "druid", "shaman", "paladin",
}
BNP.OtherDebuffClassLabels = {
warrior = "Warrior",
rogue = "Rogue",
hunter = "Hunter",
mage = "Mage",
warlock = "Warlock",
priest = "Priest",
druid = "Druid",
shaman = "Shaman",
paladin = "Paladin",
}
BNP.OtherDebuffClassColors = {
warrior = { 0.78, 0.61, 0.43 },
rogue = { 1.00, 0.96, 0.41 },
hunter = { 0.67, 0.83, 0.45 },
mage = { 0.41, 0.80, 0.94 },
warlock = { 0.58, 0.51, 0.79 },
priest = { 1.00, 1.00, 1.00 },
druid = { 1.00, 0.49, 0.04 },
shaman = { 0.00, 0.44, 0.87 },
paladin = { 0.96, 0.55, 0.73 },
}
BNP.OtherDebuffDefs = {
warrior = {
{ key="sunderarmor", label="Sunder Armor", category="armor", duration=30, maxStacks=5,
spellIDs={7386,7405,8380,11596,11597}, description="30s\nReduce 450 Armor up to 5 Times (2250)" },
{ key="demoshout", label="Demoralizing Shout", category="tank", duration=30,
spellIDs={1160,6190,11554,11555,11556}, description="30s\nReduces melee AP by 140" },
{ key="thunderclap", label="Thunder Clap", category="tank", duration=30,
spellIDs={6343,8198,8204,8205,11580,11581}, description="30s\nReduces attack speed by 10%" },
{ key="mortalstrike", label="Mortal Strike", category="healing", duration=10,
spellIDs={12294,21551,21552,21553}, description="10s\nReduces healing taken by 50%" },
},
rogue = {
{ key="exposearmor", label="Expose Armor", category="armor", duration=30,
spellIDs={8647,8649,8650,11197,11198}, description="30s\nReduce 340 Armor per Combo Point (1700)" },
{ key="woundpoison", label="Wound Poison", category="healing", duration=15, maxStacks=5,
spellIDs={13218}, description="15s\nReduces healing taken by 5% up to 5 Times (25%)" },
},
hunter = {
{ key="huntersmark", label="Hunter's Mark", category="utility", duration=120,
spellIDs={1130,14323,14324,14325}, description="120s\nIncreases Ranged AP by 110" },
},
mage = {
{ key="firevulnerability", label="Fire Vulnerability", category="spellvuln", duration=30, maxStacks=5,
spellIDs={22959}, description="30s\nIncreases Fire Damage by 3% up to 5 Times (15%)" },
{ key="winterschill", label="Winter's Chill", category="spellvuln", duration=15, maxStacks=5,
spellIDs={12579}, description="15s\nIncreases Frost Crit Chance by 2% up to 5 Times (10%)" },
{ key="ignite", label="Ignite", category="personal", duration=4,
spellIDs={12654}, description="4s\n40% of crit damage over 2 ticks" },
},
warlock = {
{ key="curseofrecklessness", label="Curse of Recklessness", category="armor", duration=120,
spellIDs={704,7658,7659,11717}, description="120s\nReduce Armor by 640" },
{ key="curseoftheelements", label="Curse of the Elements", category="spellvuln", duration=300,
spellIDs={1490,11721,11722}, description="300s\nReducing Fire and Frost Resistances by 75 and Increases Fire and Frost Damage by 10%" },
{ key="curseofshadow", label="Curse of Shadow", category="spellvuln", duration=300,
spellIDs={17862,17937}, description="300s\nReducing Shadow and Arcane Resistances by 75 and Increases Shadow and Arcane Damage by 10%" },
{ key="curseoftongues", label="Curse of Tongues", category="utility", duration=30,
spellIDs={1714,11719}, description="30s\nIncreases casting time by 50%" },
{ key="curseofweakness", label="Curse of Weakness", category="utility", duration=120,
spellIDs={702,1108,6205,7646,11707,11708}, description="120s\nReduces melee AP by 31" },
{ key="shadowvulnerability", label="Shadow Vulnerability", category="spellvuln", duration=10,
spellIDs={17794}, description="10s\nShadow Bolt and Drain Soul chance to Increases Shadow Damage by 20%" },
},
priest = {
{ key="shadowweaving", label="Shadow Weaving", category="spellvuln", duration=15, maxStacks=5,
spellIDs={15258}, description="15s\nIncreases Shadow Damage by 3% up to 5 Times (15%)" },
{ key="burningzeal", label="Burning Zeal (Priest T3.5)", category="spellvuln", duration=18,
spellIDs={52980}, description="18s\n852 Holy damage over 18s, +2% Holy damage taken (T3.5 Set Proc)" },
},
druid = {
{ key="faeriefire", label="Faerie Fire", category="armor", duration=40,
spellIDs={770,778,9749,9907,16855,17387,17388,17389,16857,17390,17391,17392}, description="40s\nReduce Armor by 505" },
{ key="demoroar", label="Demoralizing Roar", category="tank", duration=30,
spellIDs={99,1735,9490,9747,9898}, description="30s\nReduces melee AP by 108" },
},
shaman = {
-- Cursive's shared-debuff class list intentionally has no Shaman entries.
},
paladin = {
{ key="judgementoflight", label="Judgement of Light", category="utility", duration=10,
spellIDs={20185,20344,20345,20346}, description="10s\nMelee attacks heal attacker for 61" },
{ key="judgementofwisdom", label="Judgement of Wisdom", category="utility", duration=10,
spellIDs={20186,20354,20355}, description="10s\nAttacks restore 33 mana to attacker" },
{ key="judgementofthecrusader", label="Judgement of the Crusader", category="utility", duration=10,
spellIDs={21183,20188,20300,20301,20302,20303}, description="10s\nIncreases Holy damage taken by 140 and Increases melee and ranged AP" },
},
}
BNP.OtherDebuffByID = BNP.OtherDebuffByID or {}
BNP.OtherDebuffByKey = BNP.OtherDebuffByKey or {}
local classIndex, classKey, defs, i, def, j, spellID
for classIndex = 1, table.getn(BNP.OtherDebuffClassOrder) do
classKey = BNP.OtherDebuffClassOrder[classIndex]
defs = BNP.OtherDebuffDefs[classKey] or {}
for i = 1, table.getn(defs) do
def = defs[i]
def.class = classKey
def.otherDebuff = true
def.order = 1000 + (classIndex * 100) + i
BNP.OtherDebuffByKey[def.key] = def
for j = 1, table.getn(def.spellIDs or {}) do
spellID = def.spellIDs[j]
BNP.OtherDebuffByID[spellID] = def
end
end
end
-- Remove selections from older test builds (notably CC entries) that are no
-- longer part of Other Debuffs. This also prevents stale keys from keeping the
-- optional foreign-debuff scanner active.
if BNP_DB and type(BNP_DB.otherDebuffSelections) == "table" then
local savedKey
for savedKey in pairs(BNP_DB.otherDebuffSelections) do
if not BNP.OtherDebuffByKey[savedKey] then
BNP_DB.otherDebuffSelections[savedKey] = nil
end
end
end
function BNP:GetOtherDebuffDuration(def, spellID)
if not def then return nil end
if spellID and def.durations and def.durations[spellID] then
return def.durations[spellID]
end
return def.duration
end
function BNP:GetOtherDebuffTexture(def)
if not def then return "Interface\\Icons\\INV_Misc_QuestionMark" end
if def.texture then return def.texture end
if SpellInfo and def.spellIDs then
local i
for i = 1, table.getn(def.spellIDs) do
local _, _, texture = SpellInfo(def.spellIDs[i])
if texture then
def.texture = texture
return texture
end
end
end
return "Interface\\Icons\\INV_Misc_QuestionMark"
end