Add files via upload
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
## Notes: Lightweight personal debuffs and class colors for Blizzard nameplates. Requires SuperWoW and ClassicAPI.
|
||||
## Author: Wurmschwanz; nameplate discovery inspired by Shagu's Vanilla projects
|
||||
## OptionalDeps: ShaguTweaks
|
||||
## Version: 2.0.0
|
||||
## Version: 2.0.1
|
||||
## SavedVariablesPerCharacter: BNP_DB
|
||||
|
||||
core\core.lua
|
||||
|
||||
@@ -18,6 +18,8 @@ BNP.defaults = BNP.defaults or {
|
||||
debuffPosition = "top",
|
||||
ccPosition = "top",
|
||||
tankMode = false,
|
||||
tankNoTarget = false,
|
||||
tankNoTargetColor = { r = 0.00, g = 0.45, b = 1.00 },
|
||||
invertTankColors = false,
|
||||
invertTankAggroColor = { r = 1.00, g = 0.00, b = 0.00 },
|
||||
invertTankNoAggroColor = { r = 0.00, g = 1.00, b = 0.00 },
|
||||
@@ -124,6 +126,7 @@ function BNP:InitConfig()
|
||||
if BNP_DB.ccYOffset == nil then BNP_DB.ccYOffset = self.defaults.ccYOffset end
|
||||
if BNP_DB.debuffPosition == nil then BNP_DB.debuffPosition = self.defaults.debuffPosition end
|
||||
if BNP_DB.ccPosition == nil then BNP_DB.ccPosition = BNP_DB.debuffPosition or self.defaults.ccPosition end
|
||||
if BNP_DB.tankNoTarget == nil then BNP_DB.tankNoTarget = self.defaults.tankNoTarget end
|
||||
if BNP_DB.tankMode == nil then BNP_DB.tankMode = self.defaults.tankMode end
|
||||
if BNP_DB.invertTankColors == nil then BNP_DB.invertTankColors = self.defaults.invertTankColors end
|
||||
|
||||
@@ -137,6 +140,7 @@ function BNP:InitConfig()
|
||||
if color.g == nil then color.g = fallback.g end
|
||||
if color.b == nil then color.b = fallback.b end
|
||||
end
|
||||
InitTankColor("tankNoTargetColor", self.defaults.tankNoTargetColor)
|
||||
InitTankColor("invertTankAggroColor", self.defaults.invertTankAggroColor)
|
||||
InitTankColor("invertTankNoAggroColor", self.defaults.invertTankNoAggroColor)
|
||||
|
||||
@@ -390,6 +394,28 @@ local function ClampTankColor(value, fallback)
|
||||
return value
|
||||
end
|
||||
|
||||
function BNP:IsTankNoTargetEnabled()
|
||||
return BNP_DB and BNP_DB.tankNoTarget and true or false
|
||||
end
|
||||
|
||||
function BNP:GetTankNoTargetColor()
|
||||
local fallback = self.defaults.tankNoTargetColor
|
||||
local color = BNP_DB and BNP_DB.tankNoTargetColor or fallback
|
||||
return ClampTankColor(color and color.r, fallback.r),
|
||||
ClampTankColor(color and color.g, fallback.g),
|
||||
ClampTankColor(color and color.b, fallback.b)
|
||||
end
|
||||
|
||||
function BNP:SetTankNoTargetColor(r, g, b)
|
||||
if not BNP_DB then return end
|
||||
BNP_DB.tankNoTargetColor = {
|
||||
r = ClampTankColor(r, 0),
|
||||
g = ClampTankColor(g, 0.45),
|
||||
b = ClampTankColor(b, 1),
|
||||
}
|
||||
if self.UpdateTankMode then self:UpdateTankMode() end
|
||||
end
|
||||
|
||||
function BNP:GetInvertTankAggroColor()
|
||||
local fallback = self.defaults.invertTankAggroColor or { r = 1, g = 0, b = 0 }
|
||||
local color = BNP_DB and BNP_DB.invertTankAggroColor or fallback
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
BNP = BNP or {}
|
||||
BNP.version = "2.0.0"
|
||||
BNP.version = "2.0.1"
|
||||
BNP.plates = BNP.plates or {}
|
||||
BNP.detected = 0
|
||||
BNP.debugEnabled = false
|
||||
|
||||
+62
-21
@@ -1145,7 +1145,7 @@ function BNP:CreateOptions()
|
||||
end
|
||||
|
||||
frame.pages = {
|
||||
nameplates = CreatePage(510),
|
||||
nameplates = CreatePage(542),
|
||||
personal = CreatePage(345),
|
||||
auras = CreatePage(520),
|
||||
totems = CreatePage(610),
|
||||
@@ -1454,22 +1454,59 @@ function BNP:CreateOptions()
|
||||
0.00, 1.00, 0.00)
|
||||
frame.invertNoAggroColorSwatch = invertNoAggroColor
|
||||
|
||||
-- NAMES & LEVEL ---------------------------------------------------------
|
||||
CreateSection(nameplatesPage, "Names & Level", -202)
|
||||
-- Separate scope keeps the large options builder below Lua 5.0's local limit.
|
||||
do
|
||||
local noTarget = CreateCheck(nameplatesPage, "Inactive Combat", -198, function()
|
||||
BNP_DB.tankNoTarget = this:GetChecked() and true or false
|
||||
if BNP.UpdateTankMode then BNP:UpdateTankMode() end
|
||||
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
|
||||
end, 22)
|
||||
noTarget:SetScript("OnEnter", function()
|
||||
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetText("Inactive Combat Color", 1, 0.82, 0)
|
||||
GameTooltip:AddLine("Uses a third color only for enemies in combat that are temporarily unable to attack (fear, sheep, stuns or fleeing). Idle enemies are excluded.", 1, 1, 1, true)
|
||||
GameTooltip:AddLine("Reuses tracked CCs, even with a retained target. Direct fleeing status is used when available; otherwise no target in combat is the fallback. Independent of Invert Tank Colors.", 0.8, 0.8, 0.8, true)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
noTarget:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||||
frame.tankNoTargetCheck = noTarget
|
||||
frame.tankNoTargetColorSwatch = CreateColorSwatch(nameplatesPage, "Inactive Color", -198, 214,
|
||||
function() return BNP:GetTankNoTargetColor() end,
|
||||
function(r, g, b) BNP:SetTankNoTargetColor(r, g, b) end,
|
||||
0.00, 0.45, 1.00,
|
||||
"Choose the color for temporarily inactive enemies in combat.",
|
||||
"Restores the inactive combat color to blue.")
|
||||
end
|
||||
|
||||
local hidePlayerNames = CreateCheck(nameplatesPage, "Hide Player Names", -226, function()
|
||||
-- Fixed label columns keep all Tank Mode color boxes aligned, regardless
|
||||
-- of label length. Scope avoids adding long-lived options-builder locals.
|
||||
do
|
||||
local _, swatch
|
||||
for _, swatch in ipairs({ frame.invertAggroColorSwatch,
|
||||
frame.invertNoAggroColorSwatch, frame.tankNoTargetColorSwatch }) do
|
||||
if swatch and swatch.BNPLabel then
|
||||
swatch.BNPLabel:SetWidth(110)
|
||||
swatch.BNPLabel:SetJustifyH("LEFT")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- NAMES & LEVEL ---------------------------------------------------------
|
||||
CreateSection(nameplatesPage, "Names & Level", -234)
|
||||
|
||||
local hidePlayerNames = CreateCheck(nameplatesPage, "Hide Player Names", -258, function()
|
||||
BNP_DB.hidePlayerNames = this:GetChecked() and true or false
|
||||
if BNP.RefreshNameVisibility then BNP:RefreshNameVisibility() end
|
||||
end, 22)
|
||||
frame.hidePlayerNamesCheck = hidePlayerNames
|
||||
|
||||
local hideNPCNames = CreateCheck(nameplatesPage, "Hide NPC Names", -226, function()
|
||||
local hideNPCNames = CreateCheck(nameplatesPage, "Hide NPC Names", -258, function()
|
||||
BNP_DB.hideNPCNames = this:GetChecked() and true or false
|
||||
if BNP.RefreshNameVisibility then BNP:RefreshNameVisibility() end
|
||||
end, 214)
|
||||
frame.hideNPCNamesCheck = hideNPCNames
|
||||
|
||||
local hideNameplateLevel = CreateCheck(nameplatesPage, "Hide Level", -250, function()
|
||||
local hideNameplateLevel = CreateCheck(nameplatesPage, "Hide Level", -282, function()
|
||||
BNP_DB.hideNameplateLevel = this:GetChecked() and true or false
|
||||
if BNP.RefreshNameplateLevelVisibility then BNP:RefreshNameplateLevelVisibility() end
|
||||
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
|
||||
@@ -1483,7 +1520,7 @@ function BNP:CreateOptions()
|
||||
hideNameplateLevel:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||||
frame.hideNameplateLevelCheck = hideNameplateLevel
|
||||
|
||||
local customNameColor = CreateCheck(nameplatesPage, "Custom Name Color", -250, function()
|
||||
local customNameColor = CreateCheck(nameplatesPage, "Custom Name Color", -282, function()
|
||||
BNP_DB.customNameColor = this:GetChecked() and true or false
|
||||
if BNP.RefreshNameAppearance then BNP:RefreshNameAppearance() end
|
||||
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
|
||||
@@ -1497,7 +1534,7 @@ function BNP:CreateOptions()
|
||||
customNameColor:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||||
frame.customNameColorCheck = customNameColor
|
||||
|
||||
local nameColorSwatch, nameColorLabel = CreateColorSwatch(nameplatesPage, "Name Color", -276, 214,
|
||||
local nameColorSwatch, nameColorLabel = CreateColorSwatch(nameplatesPage, "Name Color", -308, 214,
|
||||
function() return BNP:GetNameColor() end,
|
||||
function(r, g, b) BNP:SetNameColor(r, g, b) end,
|
||||
1.00, 1.00, 1.00,
|
||||
@@ -1514,7 +1551,7 @@ function BNP:CreateOptions()
|
||||
|
||||
-- Name text controls are independent from Nameplate Scale. The size setting
|
||||
-- only touches the Blizzard name FontString; the Y offset only moves it.
|
||||
local nameFontSize = CreateSlider(nameplatesPage, "Name Font Size", 8, 24, 1, -306, 28, 150)
|
||||
local nameFontSize = CreateSlider(nameplatesPage, "Name Font Size", 8, 24, 1, -338, 28, 150)
|
||||
nameFontSize:SetScript("OnValueChanged", function()
|
||||
if not BNP_DB or frame.BNPSyncingNameControls then return end
|
||||
local value = math.floor(this:GetValue() + 0.5)
|
||||
@@ -1524,7 +1561,7 @@ function BNP:CreateOptions()
|
||||
end)
|
||||
frame.nameFontSizeSlider = nameFontSize
|
||||
|
||||
local nameFontYOffset = CreateSlider(nameplatesPage, "Name Y Offset", -50, 50, 1, -306, 220, 150)
|
||||
local nameFontYOffset = CreateSlider(nameplatesPage, "Name Y Offset", -50, 50, 1, -338, 220, 150)
|
||||
nameFontYOffset:SetScript("OnValueChanged", function()
|
||||
if not BNP_DB or frame.BNPSyncingNameControls then return end
|
||||
local value = RoundSignedInteger(this:GetValue())
|
||||
@@ -1535,15 +1572,15 @@ function BNP:CreateOptions()
|
||||
frame.nameFontYOffsetSlider = nameFontYOffset
|
||||
|
||||
-- HEALTH BAR & TEXT -----------------------------------------------------
|
||||
CreateSection(nameplatesPage, "Health Bar & Text", -336)
|
||||
CreateSection(nameplatesPage, "Health Bar & Text", -368)
|
||||
|
||||
local healthTextLabel = nameplatesPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
healthTextLabel:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 28, -358)
|
||||
healthTextLabel:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 28, -390)
|
||||
healthTextLabel:SetText("Display")
|
||||
healthTextLabel:SetTextColor(1.00, 0.82, 0.00)
|
||||
|
||||
local healthTextDropdown = CreateFrame("Frame", "BNPHealthTextDropdown", nameplatesPage, "UIDropDownMenuTemplate")
|
||||
healthTextDropdown:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 10, -370)
|
||||
healthTextDropdown:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 10, -402)
|
||||
UIDropDownMenu_SetWidth(150, healthTextDropdown)
|
||||
|
||||
local healthModeLabels = {
|
||||
@@ -1579,12 +1616,12 @@ function BNP:CreateOptions()
|
||||
frame.SetHealthTextMode = SetHealthTextMode
|
||||
|
||||
local healthOutlineLabel = nameplatesPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
healthOutlineLabel:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 220, -358)
|
||||
healthOutlineLabel:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 220, -390)
|
||||
healthOutlineLabel:SetText("Outline")
|
||||
healthOutlineLabel:SetTextColor(1.00, 0.82, 0.00)
|
||||
|
||||
local healthOutlineDropdown = CreateFrame("Frame", "BNPHealthOutlineDropdown", nameplatesPage, "UIDropDownMenuTemplate")
|
||||
healthOutlineDropdown:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 200, -370)
|
||||
healthOutlineDropdown:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 200, -402)
|
||||
UIDropDownMenu_SetWidth(150, healthOutlineDropdown)
|
||||
|
||||
local healthOutlineLabels = {
|
||||
@@ -1617,7 +1654,7 @@ function BNP:CreateOptions()
|
||||
frame.healthTextOutlineDropdown = healthOutlineDropdown
|
||||
frame.SetHealthTextOutline = SetHealthTextOutline
|
||||
|
||||
local healthFontSize = CreateSlider(nameplatesPage, "Health Font Size", 8, 20, 1, -416, 28, 150)
|
||||
local healthFontSize = CreateSlider(nameplatesPage, "Health Font Size", 8, 20, 1, -448, 28, 150)
|
||||
healthFontSize:SetScript("OnValueChanged", function()
|
||||
if not BNP_DB or frame.BNPSyncingHealthTextControls then return end
|
||||
local value = math.floor(this:GetValue() + 0.5)
|
||||
@@ -1627,7 +1664,7 @@ function BNP:CreateOptions()
|
||||
end)
|
||||
frame.healthTextFontSizeSlider = healthFontSize
|
||||
|
||||
local blackHealthbarBackground = CreateCheck(nameplatesPage, "Black Health Background", -412, function()
|
||||
local blackHealthbarBackground = CreateCheck(nameplatesPage, "Black Health Background", -444, function()
|
||||
BNP_DB.blackHealthbarBackground = this:GetChecked() and true or false
|
||||
if BNP.RefreshHealthbarBackground then BNP:RefreshHealthbarBackground() end
|
||||
end, 214)
|
||||
@@ -1642,9 +1679,9 @@ function BNP:CreateOptions()
|
||||
|
||||
-- COMBO POINTS (Rogue / Druid only) ------------------------------------
|
||||
if comboOptionsClass then
|
||||
CreateSection(nameplatesPage, "Combo Points", -442)
|
||||
CreateSection(nameplatesPage, "Combo Points", -474)
|
||||
|
||||
local comboPoints = CreateCheck(nameplatesPage, "Combo Points", -466, function()
|
||||
local comboPoints = CreateCheck(nameplatesPage, "Combo Points", -498, function()
|
||||
BNP_DB.comboPoints = this:GetChecked() and true or false
|
||||
if BNP.RefreshComboPoints then BNP:RefreshComboPoints() end
|
||||
if BNP.RefreshAllAuraLayouts then BNP:RefreshAllAuraLayouts() end
|
||||
@@ -1653,7 +1690,7 @@ function BNP:CreateOptions()
|
||||
end, 22)
|
||||
frame.comboPointsCheck = comboPoints
|
||||
|
||||
local darkComboPointBorder = CreateCheck(nameplatesPage, "Dark CP Border", -466, function()
|
||||
local darkComboPointBorder = CreateCheck(nameplatesPage, "Dark CP Border", -498, function()
|
||||
BNP_DB.darkComboPointBorder = this:GetChecked() and true or false
|
||||
if BNP.RefreshComboPointBorderStyle then BNP:RefreshComboPointBorderStyle() end
|
||||
if BNP.RefreshComboPoints then BNP:RefreshComboPoints() end
|
||||
@@ -1668,7 +1705,7 @@ function BNP:CreateOptions()
|
||||
darkComboPointBorder:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||||
frame.darkComboPointBorderCheck = darkComboPointBorder
|
||||
|
||||
local comboYOffset = CreateSlider(nameplatesPage, "Combo Point Y Offset", -50, 50, 1, -466, 278, 105)
|
||||
local comboYOffset = CreateSlider(nameplatesPage, "Combo Point Y Offset", -50, 50, 1, -498, 278, 105)
|
||||
comboYOffset:SetScript("OnValueChanged", function()
|
||||
if not BNP_DB then return end
|
||||
local value = RoundSignedInteger(this:GetValue())
|
||||
@@ -2783,6 +2820,8 @@ function BNP:CreateOptions()
|
||||
swatch.BNPLabel:SetTextColor(enabled and 1 or 0.5, enabled and 0.82 or 0.5, enabled and 0 or 0.5)
|
||||
end
|
||||
end
|
||||
SetCheckEnabled(self.tankNoTargetCheck, BNP:IsTankModeEnabled())
|
||||
SetTankSwatchEnabled(self.tankNoTargetColorSwatch, BNP:IsTankModeEnabled() and BNP:IsTankNoTargetEnabled())
|
||||
SetTankSwatchEnabled(self.invertAggroColorSwatch, invertTankEnabled)
|
||||
SetTankSwatchEnabled(self.invertNoAggroColorSwatch, invertTankEnabled)
|
||||
|
||||
@@ -3117,7 +3156,9 @@ function BNP:SyncOptions()
|
||||
frame.castbarTestCheck:SetChecked(self.IsCastbarTestMode and self:IsCastbarTestMode() or false)
|
||||
end
|
||||
frame.tankCheck:SetChecked(self:IsTankModeEnabled())
|
||||
if frame.tankNoTargetCheck then frame.tankNoTargetCheck:SetChecked(self:IsTankNoTargetEnabled()) end
|
||||
if frame.invertTankColorsCheck then frame.invertTankColorsCheck:SetChecked(self:AreTankModeColorsInverted()) end
|
||||
if frame.tankNoTargetColorSwatch and frame.tankNoTargetColorSwatch.RefreshColor then frame.tankNoTargetColorSwatch:RefreshColor() end
|
||||
if frame.invertAggroColorSwatch and frame.invertAggroColorSwatch.RefreshColor then frame.invertAggroColorSwatch:RefreshColor() end
|
||||
if frame.invertNoAggroColorSwatch and frame.invertNoAggroColorSwatch.RefreshColor then frame.invertNoAggroColorSwatch:RefreshColor() end
|
||||
if frame.comboPointsCheck and comboOptionsClass then
|
||||
|
||||
@@ -860,9 +860,11 @@ function BNP:StartAuraRecorder()
|
||||
recorder.active = false
|
||||
UnregisterRecorderEvents()
|
||||
updateFrame:SetScript("OnUpdate", nil)
|
||||
if self.SetUnknownDiagnosticsActive then self:SetUnknownDiagnosticsActive(false) end
|
||||
end
|
||||
|
||||
UnregisterRecorderEvents()
|
||||
if self.ResetUnknownBeta then self:ResetUnknownBeta(true) end
|
||||
recorder.lines = {}
|
||||
recorder.charCount = 0
|
||||
recorder.limitHit = false
|
||||
@@ -879,6 +881,7 @@ function BNP:StartAuraRecorder()
|
||||
recorder.traceGUID = nil
|
||||
recorder.traceName = nil
|
||||
recorder.active = true
|
||||
if self.SetUnknownDiagnosticsActive then self:SetUnknownDiagnosticsActive(true) end
|
||||
|
||||
local registeredCount = 0
|
||||
local i
|
||||
@@ -959,6 +962,7 @@ function BNP:StopAuraRecorder(reason)
|
||||
recorder.stoppedAt = GetTime()
|
||||
UnregisterRecorderEvents()
|
||||
updateFrame:SetScript("OnUpdate", nil)
|
||||
if self.SetUnknownDiagnosticsActive then self:SetUnknownDiagnosticsActive(false) end
|
||||
DumpInternalDiagnostics()
|
||||
AppendLine("=== END REPORT | reason=" .. CleanValue(reason or "user") .. " | elapsed=" ..
|
||||
string.format("%.3f", recorder.stoppedAt - (recorder.startedAt or recorder.stoppedAt)) .. "s ===", true)
|
||||
|
||||
+72
-26
@@ -2892,18 +2892,19 @@ local function BuildGlobalCCDefs()
|
||||
end
|
||||
BuildGlobalCCDefs()
|
||||
|
||||
local function ResolveGlobalCCDef(spellID, texture)
|
||||
-- Prefer reliable aura identity over icon matching. Several unrelated auras
|
||||
-- reuse the same Blizzard icon (for example Resurrection Sickness and
|
||||
-- Howl of Terror), so texture matching must never override a known spell.
|
||||
local function ResolveGlobalCCDef(spellID, auraName)
|
||||
-- Foreign CCs must be identified by exact spell identity, never by icon.
|
||||
-- Blizzard reuses many spell textures across unrelated buffs/debuffs, so a
|
||||
-- texture fallback can turn harmless effects into ghost CC icons. ClassicAPI
|
||||
-- supplies either the aura spell ID or the exact aura name on supported paths.
|
||||
if spellID and GLOBAL_CC_BY_ID[spellID] then return GLOBAL_CC_BY_ID[spellID] end
|
||||
if spellID and GLOBAL_CC_NEGATIVE[spellID] then return nil end
|
||||
|
||||
local auraName = spellID and SpellInfo and SpellInfo(spellID) or nil
|
||||
local _, ccDef
|
||||
if (not auraName or auraName == "") and spellID and SpellInfo then
|
||||
auraName = SpellInfo(spellID)
|
||||
end
|
||||
|
||||
-- A known spell name is authoritative. If it is not one of our CCs, cache
|
||||
-- the negative result and do not reinterpret it from a shared icon texture.
|
||||
local _, ccDef
|
||||
if auraName and auraName ~= "" then
|
||||
for _, ccDef in ipairs(GLOBAL_CC_DEFS) do
|
||||
if NameMatches(ccDef, auraName) then
|
||||
@@ -2911,31 +2912,63 @@ local function ResolveGlobalCCDef(spellID, texture)
|
||||
return ccDef
|
||||
end
|
||||
end
|
||||
|
||||
if spellID then GLOBAL_CC_NEGATIVE[spellID] = true end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Texture is only a compatibility fallback for aura scans where ClassicAPI
|
||||
-- cannot provide a usable spell ID/name.
|
||||
if texture then
|
||||
local lowerTexture = string.lower(texture)
|
||||
for _, ccDef in ipairs(GLOBAL_CC_DEFS) do
|
||||
if ccDef.textureMatch and string.find(lowerTexture, ccDef.textureMatch) then
|
||||
return ccDef
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if spellID then GLOBAL_CC_NEGATIVE[spellID] = true end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Reuse the already-read UNIT_AURA snapshot for Tank Mode. This does not
|
||||
-- depend on aura display settings and never triggers an additional API scan.
|
||||
function BNP:CacheTankControlSnapshot(plate, guid, snapshot)
|
||||
if not self.IsTankNoTargetEnabled or not self:IsTankNoTargetEnabled()
|
||||
or not self:IsTankModeEnabled() then
|
||||
plate.BNPTankControlGUID = nil
|
||||
plate.BNPTankControlled = nil
|
||||
return
|
||||
end
|
||||
local controlled = false
|
||||
local i
|
||||
for i = 1, snapshot.count do
|
||||
local entry = snapshot.entries[i]
|
||||
local def = ResolveGlobalCCDef(entry[4], entry[10])
|
||||
if def and self.TankDisablingCC and self.TankDisablingCC[def.key] then
|
||||
controlled = true
|
||||
break
|
||||
end
|
||||
end
|
||||
plate.BNPTankControlGUID = guid
|
||||
plate.BNPTankControlled = controlled
|
||||
end
|
||||
|
||||
-- One initial read when enabling the option on an already visible plate.
|
||||
-- Subsequent updates come from the existing UNIT_AURA watcher only.
|
||||
function BNP:PrimeTankControlSnapshot(plate, guid, now)
|
||||
local token = plate.BNPClassicUnitToken
|
||||
if not token or not self._ClassicGetNamePlateForUnit or not self._ClassicUnitGUID then return end
|
||||
if self._ClassicGetNamePlateForUnit(token) ~= plate or self._ClassicUnitGUID(token) ~= guid then return end
|
||||
self:CacheTankControlSnapshot(plate, guid, CaptureLiveDebuffSnapshot(token, now))
|
||||
end
|
||||
|
||||
local function WantsForeignCCs()
|
||||
return BNP.AreCrowdControlEnabled and BNP:AreCrowdControlEnabled()
|
||||
and BNP.ShowOtherPlayersCCs and BNP:ShowOtherPlayersCCs()
|
||||
end
|
||||
|
||||
-- "Show CCs from Other Players" must only show a foreign CC when ClassicAPI
|
||||
-- gives us a caster we can positively identify as player-controlled. Treating
|
||||
-- missing/unresolved source metadata as "probably a player" can turn unrelated
|
||||
-- NPC auras into ghost CC icons (for example a Paladin-looking seal icon with no
|
||||
-- Paladin present). Own player/pet CCs are handled by the normal own-aura path.
|
||||
function BNP:_IsAllowedForeignCCSource(source)
|
||||
if not source or source == "" then return false end
|
||||
if self:_IsOwnClassicAuraSource(source) then return false end
|
||||
|
||||
if not UnitExists or not UnitExists(source) then return false end
|
||||
if not UnitPlayerControlled then return false end
|
||||
return UnitPlayerControlled(source) and true or false
|
||||
end
|
||||
|
||||
local FOREIGN_CC_SEEN = {}
|
||||
local foreignCCSeenGeneration = 0
|
||||
|
||||
@@ -2952,17 +2985,23 @@ local function SyncForeignCCs(unit, guid, now, snapshot, eventAuthoritative)
|
||||
local i
|
||||
local maxIndex = snapshot and snapshot.count or 64
|
||||
for i = 1, maxIndex do
|
||||
local texture, stacks, dtype, auraSpellID
|
||||
local texture, stacks, dtype, auraSpellID, liveDuration, liveExpires, source, auraName
|
||||
if snapshot then
|
||||
local entry = snapshot.entries[i]
|
||||
if not entry then break end
|
||||
texture, stacks, dtype, auraSpellID = entry[1], entry[2], entry[3], entry[4]
|
||||
texture, stacks, dtype, auraSpellID, source, auraName =
|
||||
entry[1], entry[2], entry[3], entry[4], entry[7], entry[10]
|
||||
else
|
||||
texture, stacks, dtype, auraSpellID = BNP:_ReadClassicDebuff(unit, i)
|
||||
local isStealable, nameplateShowPersonal
|
||||
texture, stacks, dtype, auraSpellID, liveDuration, liveExpires, source,
|
||||
isStealable, nameplateShowPersonal, auraName = BNP:_ReadClassicDebuff(unit, i)
|
||||
if not texture then break end
|
||||
end
|
||||
|
||||
local ccDef = ResolveGlobalCCDef(auraSpellID, texture)
|
||||
local ccDef = nil
|
||||
if BNP:_IsAllowedForeignCCSource(source) then
|
||||
ccDef = ResolveGlobalCCDef(auraSpellID, auraName)
|
||||
end
|
||||
if ccDef then
|
||||
FOREIGN_CC_SEEN[ccDef.key] = seenGeneration
|
||||
if not live then
|
||||
@@ -3798,6 +3837,7 @@ function BNP:_HandleClassicUnitAura(token, plate)
|
||||
end
|
||||
|
||||
local snapshot = CaptureLiveDebuffSnapshot(token, now)
|
||||
self:CacheTankControlSnapshot(plate, guid, snapshot)
|
||||
|
||||
-- Pending own casts and AoE applications consume the same snapshot instead
|
||||
-- of re-walking the aura descriptor list.
|
||||
@@ -3822,6 +3862,12 @@ function BNP:_HandleClassicUnitAura(token, plate)
|
||||
|
||||
plate.BNPClassicAuraLastEvent = now
|
||||
UpdatePlate(plate, now)
|
||||
-- Reflect confirmed CC appearance/removal in this event, without waiting
|
||||
-- for the 0.10s nameplate poller. The snapshot above is already cached.
|
||||
if self.UpdateTankModePlate and self.IsTankModeEnabled and self:IsTankModeEnabled()
|
||||
and self:IsTankNoTargetEnabled() then
|
||||
self:UpdateTankModePlate(plate)
|
||||
end
|
||||
|
||||
-- Tiny diagnostics only; no per-event tables are created.
|
||||
if not self.classicAuraStats then self.classicAuraStats = { events = 0, scans = 0 } end
|
||||
|
||||
@@ -539,6 +539,18 @@ discovery:SetScript("OnUpdate", function()
|
||||
return
|
||||
end
|
||||
|
||||
-- No active timed cast and no short success/fail feedback means there is
|
||||
-- nothing to discover. UNIT_CASTEVENT wakes this path automatically by
|
||||
-- creating an entry in casts/castFeedback, so idle gameplay no longer walks
|
||||
-- every visible nameplate 20 times per second.
|
||||
if not castbarTestMode and not next(casts) and not next(castFeedback) then
|
||||
local plate
|
||||
for plate in pairs(activeCastPlates) do
|
||||
MarkCastPlateInactive(plate)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local now = GetTime()
|
||||
|
||||
-- Drop expired feedback even if its nameplate disappeared before the short
|
||||
|
||||
@@ -441,9 +441,11 @@ table.insert(BNP.libnameplate.OnShow, function(plate)
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(BNP.libnameplate.OnUpdate, function(plate)
|
||||
if COMBO_CLASS then UpdatePlate(plate) end
|
||||
end)
|
||||
if COMBO_CLASS then
|
||||
table.insert(BNP.libnameplate.OnUpdate, function(plate)
|
||||
UpdatePlate(plate)
|
||||
end)
|
||||
end
|
||||
|
||||
if COMBO_CLASS then
|
||||
local events = CreateFrame("Frame")
|
||||
|
||||
+15
-3
@@ -4,6 +4,16 @@ local function CreateMarker(plate)
|
||||
if not plate then return end
|
||||
BNP:RegisterPlate(plate)
|
||||
|
||||
-- Debug is disabled for normal gameplay. Do not allocate an extra Frame,
|
||||
-- Texture and FontString on every nameplate unless the user actually enables
|
||||
-- the marker. Existing markers are simply hidden when debug is off.
|
||||
if not BNP.debugEnabled then
|
||||
if plate.BNPMarker and plate.BNPMarker.IsShown and plate.BNPMarker:IsShown() then
|
||||
plate.BNPMarker:Hide()
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if not plate.BNPMarker then
|
||||
local marker = CreateFrame("Frame", nil, plate)
|
||||
marker:SetWidth(14)
|
||||
@@ -23,7 +33,7 @@ local function CreateMarker(plate)
|
||||
plate.BNPMarker = marker
|
||||
end
|
||||
|
||||
if BNP.debugEnabled then plate.BNPMarker:Show() else plate.BNPMarker:Hide() end
|
||||
plate.BNPMarker:Show()
|
||||
end
|
||||
|
||||
table.insert(BNP.libnameplate.OnInit, CreateMarker)
|
||||
@@ -35,8 +45,10 @@ function BNP:SetDebug(enabled)
|
||||
BNP_DB.debugEnabled = enabled
|
||||
local plate
|
||||
for plate in pairs(BNP.plates) do
|
||||
if plate.BNPMarker then
|
||||
if enabled then plate.BNPMarker:Show() else plate.BNPMarker:Hide() end
|
||||
if enabled then
|
||||
CreateMarker(plate)
|
||||
elseif plate.BNPMarker then
|
||||
plate.BNPMarker:Hide()
|
||||
end
|
||||
end
|
||||
BNP:Print("Debug-Marker " .. (enabled and "aktiviert" or "deaktiviert") .. ".")
|
||||
|
||||
+39
-21
@@ -865,6 +865,13 @@ local function ApplyTargetArrows(plate)
|
||||
end
|
||||
|
||||
|
||||
local function SetDetachedObjectAlpha(object, wantedAlpha)
|
||||
if object and object.SetAlpha and
|
||||
(not object.GetAlpha or object:GetAlpha() ~= wantedAlpha) then
|
||||
object:SetAlpha(wantedAlpha)
|
||||
end
|
||||
end
|
||||
|
||||
local function ApplyTargetOnlyPresentation(plate, alpha, targetOnly)
|
||||
if not plate then return end
|
||||
|
||||
@@ -872,15 +879,9 @@ local function ApplyTargetOnlyPresentation(plate, alpha, targetOnly)
|
||||
-- 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)
|
||||
SetDetachedObjectAlpha(plate.BNPAuraContainer, detachedAlpha)
|
||||
SetDetachedObjectAlpha(plate.BNPCCContainer, detachedAlpha)
|
||||
SetDetachedObjectAlpha(plate.BNPImmunityContainer, detachedAlpha)
|
||||
|
||||
-- BNP routes nameplate clicks through BNPScaleWrapper. Alpha-0 frames can
|
||||
-- still receive mouse input in the WoW UI, so disable the wrapper hit area
|
||||
@@ -888,7 +889,10 @@ local function ApplyTargetOnlyPresentation(plate, alpha, targetOnly)
|
||||
local wrapper = plate.BNPScaleWrapper
|
||||
if wrapper and wrapper.EnableMouse then
|
||||
local shouldEnableMouse = (not targetOnly) or alpha > 0
|
||||
wrapper:EnableMouse(shouldEnableMouse)
|
||||
if wrapper.BNPTargetOnlyMouseEnabled ~= shouldEnableMouse then
|
||||
wrapper:EnableMouse(shouldEnableMouse)
|
||||
wrapper.BNPTargetOnlyMouseEnabled = shouldEnableMouse
|
||||
end
|
||||
|
||||
if not shouldEnableMouse and BNP.BNPMouseoverOwner == wrapper then
|
||||
BNP.BNPMouseoverOwner = nil
|
||||
@@ -950,19 +954,33 @@ local function InstallAlphaGuard(plate)
|
||||
BNP:MaintainNameplateYOffset(current)
|
||||
end
|
||||
|
||||
-- Blizzard and the scale/Y-offset wrapper may restore the native raid icon
|
||||
-- anchor while the plate is being projected. Reapply the optional custom
|
||||
-- Raid Mark position afterwards so it stays stable without touching its
|
||||
-- texture, visibility or size.
|
||||
if BNP.MaintainRaidMarkPosition then BNP:MaintainRaidMarkPosition(current) end
|
||||
|
||||
-- Alpha / Target Only remains per-frame because projected Vanilla plates
|
||||
-- can be recycled between normal events and Blizzard may rewrite alpha.
|
||||
-- The heavier visual guards below do not need render-rate polling: target
|
||||
-- changes refresh the old/new target immediately through PLAYER_TARGET_CHANGED,
|
||||
-- while a 20 Hz safety pass is more than enough to repair occasional client
|
||||
-- rewrites without doing the same work 60-165 times per second per plate.
|
||||
ApplyTargetAlpha(current)
|
||||
if BNP.MaintainTargetScale then BNP:MaintainTargetScale(current) end
|
||||
ApplyTargetGlow(current)
|
||||
ApplyTargetArrows(current)
|
||||
ApplyTargetBorderColor(current)
|
||||
-- Foreign-tag grey is deliberately still enforced every frame. Blizzard can
|
||||
-- rewrite healthbar colors between events and even a short throttle can
|
||||
-- reintroduce a visible grey/red flash on tagged mobs. The expensive owner
|
||||
-- query inside ApplyForeignTagVisual is already cached to 10 Hz.
|
||||
ApplyForeignTagVisual(current)
|
||||
ApplyTargetFrameLevel(current)
|
||||
|
||||
current.BNPVisualGuardElapsed = (current.BNPVisualGuardElapsed or 0) + (arg1 or 0)
|
||||
if current.BNPVisualGuardElapsed >= 0.05 then
|
||||
current.BNPVisualGuardElapsed = 0
|
||||
|
||||
-- Blizzard and the scale/Y-offset wrapper may restore the native raid icon
|
||||
-- anchor while the plate is being projected. Reapply the optional custom
|
||||
-- Raid Mark position on the throttled safety pass.
|
||||
if BNP.MaintainRaidMarkPosition then BNP:MaintainRaidMarkPosition(current) end
|
||||
if BNP.MaintainTargetScale then BNP:MaintainTargetScale(current) end
|
||||
ApplyTargetGlow(current)
|
||||
ApplyTargetArrows(current)
|
||||
ApplyTargetBorderColor(current)
|
||||
ApplyTargetFrameLevel(current)
|
||||
end
|
||||
end)
|
||||
|
||||
plate.BNPFullAlphaGuard = true
|
||||
|
||||
+21
-2
@@ -817,6 +817,17 @@ local function UpdatePlate(plate)
|
||||
local guid = BNP.GetStablePlateAuraGUID and BNP.GetStablePlateAuraGUID(plate, now) or GetPlateGUID(plate)
|
||||
if not guid then HideContainer(plate) return end
|
||||
|
||||
local cache = BNP.guidImmunities and BNP.guidImmunities[guid]
|
||||
|
||||
-- PvE fast path: once a stable GUID has been positively resolved as an
|
||||
-- existing non-player unit, do not repeat ClassicAPI/UnitBuff player checks
|
||||
-- every 0.10s for that same NPC. A recycled plate gets a different GUID and
|
||||
-- automatically falls back to normal detection. Cast-event cache always wins.
|
||||
if plate.BNPImmunityNonPlayerGUID == guid and not cache then
|
||||
HideContainer(plate)
|
||||
return
|
||||
end
|
||||
|
||||
local targetGUID = GetTargetGUID()
|
||||
local unit = nil
|
||||
if targetGUID and targetGUID == guid then
|
||||
@@ -840,12 +851,20 @@ local function UpdatePlate(plate)
|
||||
|
||||
-- Lazy creation: NPC nameplates with no immunity state never need a
|
||||
-- container or its four icon frames. Existing cast-event state still gets
|
||||
-- rendered even during a short token-resolution gap.
|
||||
local cache = BNP.guidImmunities and BNP.guidImmunities[guid]
|
||||
-- rendered even during a short token-resolution gap. Cache a negative player
|
||||
-- result only when UnitExists positively confirms that the projected unit is
|
||||
-- valid; transient/unresolved tokens therefore never get stuck as NPCs.
|
||||
if not classicCanScan and not legacyCanScan and not cache then
|
||||
local unitExists = false
|
||||
if unit and UnitExists then
|
||||
local ok, exists = pcall(UnitExists, unit)
|
||||
unitExists = ok and exists and true or false
|
||||
end
|
||||
if unitExists then plate.BNPImmunityNonPlayerGUID = guid end
|
||||
HideContainer(plate)
|
||||
return
|
||||
end
|
||||
plate.BNPImmunityNonPlayerGUID = nil
|
||||
|
||||
if not plate.BNPImmunityContainer then CreateContainer(plate) end
|
||||
if classicCanScan then ScanClassicAPIBuffs(classicUnit, guid, now) end
|
||||
|
||||
@@ -58,6 +58,12 @@ end
|
||||
|
||||
function BNP:MaintainRaidMarkPosition(plate)
|
||||
if not plate or not plate.IsShown or not plate:IsShown() then return end
|
||||
local icon = plate.raidicon
|
||||
if not icon then return end
|
||||
-- This path runs from the per-frame plate guard. Most plates have no active
|
||||
-- raid mark, so avoid GetPoint/anchor comparisons until Blizzard actually
|
||||
-- shows the icon. The first visible frame will still be corrected instantly.
|
||||
if icon.IsShown and not icon:IsShown() then return end
|
||||
self:ApplyRaidMarkPosition(plate)
|
||||
end
|
||||
|
||||
|
||||
+149
-5
@@ -4,11 +4,75 @@ BNP = BNP or {}
|
||||
-- Default: GREEN = hostile NPC currently targets the player, RED = another unit.
|
||||
-- Normal Tank Mode is fixed: GREEN = aggro, RED = no aggro.
|
||||
-- Invert Tank Colors uses two independently user-selectable colors.
|
||||
-- Idle hostile NPCs keep their normal Blizzard color.
|
||||
-- Optional third color for in-combat NPCs temporarily unable to attack.
|
||||
-- The existing two-color behavior is preserved while this option is disabled.
|
||||
|
||||
local GREEN_R, GREEN_G, GREEN_B = 0.00, 1.00, 0.00
|
||||
local RED_R, RED_G, RED_B = 1.00, 0.00, 0.00
|
||||
|
||||
-- Only effects that prevent attacking; roots, snares, silence and disarm
|
||||
-- alone do not qualify. Shared with the existing aura snapshot reader.
|
||||
BNP.TankDisablingCC = {
|
||||
fear=true, howl_of_terror=true, banish=true, death_coil=true,
|
||||
freezing_trap_effect=true, scatter_shot=true, wyvern_sting=true,
|
||||
intimidation=true, scare_beast=true, improved_concussive_shot=true,
|
||||
polymorph=true, impact=true, psychic_scream=true, blackout=true,
|
||||
shackle_undead=true, hammer_of_justice=true, repentance=true, turn_undead=true,
|
||||
gouge=true, kidney_shot=true, cheap_shot=true, sap=true, blind=true,
|
||||
bash=true, pounce=true, hibernate=true, intimidating_shout=true,
|
||||
charge_stun=true, intercept_stun=true, concussion_blow=true, revenge_stun=true,
|
||||
}
|
||||
|
||||
local function HasFlag(flags, mask)
|
||||
-- Lua 5.0 compatible, without requiring a bit library.
|
||||
return math.mod(math.floor(flags / mask), 2) == 1
|
||||
end
|
||||
|
||||
local function HasCachedControl(plate, guid, now)
|
||||
-- Authoritative UNIT_AURA snapshot, independent of CC icon visibility.
|
||||
if plate.BNPClassicAuraEventDriven and plate.BNPTankControlGUID ~= guid
|
||||
and BNP.PrimeTankControlSnapshot then
|
||||
BNP:PrimeTankControlSnapshot(plate, guid, now)
|
||||
end
|
||||
if plate.BNPTankControlGUID == guid and plate.BNPClassicAuraEventDriven then
|
||||
return plate.BNPTankControlled and true or false
|
||||
end
|
||||
local cache = BNP.guidAuras and BNP.guidAuras[guid]
|
||||
local key, aura
|
||||
if cache then
|
||||
for key, aura in pairs(cache) do
|
||||
if BNP.TankDisablingCC[key] and aura.expires and aura.expires > now then return true end
|
||||
end
|
||||
end
|
||||
cache = BNP.guidLiveCCs and BNP.guidLiveCCs[guid]
|
||||
if cache then
|
||||
for key, aura in pairs(cache) do
|
||||
if BNP.TankDisablingCC[key] and (aura.eventDriven or now - (aura.lastSeen or 0) <= 0.60) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function IsTemporarilyInactive(plate, guid, now, noTarget)
|
||||
-- One optional descriptor read covers combat, stun, confusion and fleeing,
|
||||
-- including low-health fleeing with a retained target. No tooltip/aura scan.
|
||||
local flags
|
||||
if GetUnitField then
|
||||
local ok, value = pcall(GetUnitField, guid, "flags")
|
||||
if ok and type(value) == "number" then flags = value end
|
||||
end
|
||||
local inCombat
|
||||
if flags then inCombat = HasFlag(flags, 524288)
|
||||
else inCombat = UnitAffectingCombat and UnitAffectingCombat(guid) end
|
||||
if not inCombat then return false end
|
||||
if flags and (HasFlag(flags, 262144) or HasFlag(flags, 4194304) or HasFlag(flags, 8388608)) then
|
||||
return true
|
||||
end
|
||||
return HasCachedControl(plate, guid, now) or noTarget
|
||||
end
|
||||
|
||||
local playerGUID = nil
|
||||
|
||||
local function GetPlayerGUID()
|
||||
@@ -19,6 +83,14 @@ end
|
||||
|
||||
local function GetPlateGUID(plate)
|
||||
if not plate or not plate.GetName then return nil end
|
||||
-- The ClassicAPI token is stable across camera projection changes. Only
|
||||
-- use it while the API maps it back to this exact frame.
|
||||
local token = plate.BNPClassicUnitToken
|
||||
if token and BNP._ClassicUnitGUID and BNP._ClassicGetNamePlateForUnit
|
||||
and BNP._ClassicGetNamePlateForUnit(token) == plate then
|
||||
local stableGUID = BNP._ClassicUnitGUID(token)
|
||||
if stableGUID then return stableGUID end
|
||||
end
|
||||
local guid = plate:GetName(1)
|
||||
if guid and guid ~= "" then return guid end
|
||||
return nil
|
||||
@@ -27,8 +99,8 @@ end
|
||||
local function GetTargetGUID(guid)
|
||||
if not guid then return nil end
|
||||
local exists, targetGUID = UnitExists(guid .. "target")
|
||||
if exists then return targetGUID end
|
||||
return nil
|
||||
if exists then return targetGUID, false end
|
||||
return nil, true
|
||||
end
|
||||
|
||||
local function GetBar(plate)
|
||||
@@ -55,6 +127,22 @@ end
|
||||
local function RestoreNormalColor(plate, bar)
|
||||
-- Do not restore a cached RGB value. Outside an active Tank Mode state,
|
||||
-- Blizzard owns the NPC healthbar color completely.
|
||||
-- Only undo the new third color for this same live NPC. Never replay a
|
||||
-- cached reaction color onto a recycled, player-controlled or tagged plate.
|
||||
local guid = plate.BNPTankNoTargetActive and bar and GetPlateGUID(plate)
|
||||
if guid and guid == plate.BNPTankUnitGUID
|
||||
and IsHostileNPC(guid) and not IsForeignTaggedGUID(guid) then
|
||||
local reaction = UnitReaction and UnitReaction(guid, "player")
|
||||
if reaction then
|
||||
if reaction > 4 then bar:SetStatusBarColor(0, 1, 0, 1)
|
||||
elseif reaction == 4 then bar:SetStatusBarColor(1, 1, 0, 1)
|
||||
else bar:SetStatusBarColor(1, 0, 0, 1) end
|
||||
end
|
||||
end
|
||||
plate.BNPTankNoTargetActive = nil
|
||||
plate.BNPTankNoTargetSince = nil
|
||||
plate.BNPTankUnitGUID = nil
|
||||
plate.BNPTankColorGUID = nil
|
||||
plate.BNPTankActive = nil
|
||||
plate.BNPTankR = nil
|
||||
plate.BNPTankG = nil
|
||||
@@ -63,6 +151,10 @@ end
|
||||
|
||||
local function ClearTankState(plate)
|
||||
if not plate then return end
|
||||
plate.BNPTankNoTargetActive = nil
|
||||
plate.BNPTankNoTargetSince = nil
|
||||
plate.BNPTankUnitGUID = nil
|
||||
plate.BNPTankColorGUID = nil
|
||||
plate.BNPTankActive = nil
|
||||
plate.BNPTankR = nil
|
||||
plate.BNPTankG = nil
|
||||
@@ -71,6 +163,7 @@ end
|
||||
|
||||
local function ApplyTankColor(plate, bar, r, g, b)
|
||||
bar:SetStatusBarColor(r, g, b, 1)
|
||||
plate.BNPTankColorGUID = GetPlateGUID(plate)
|
||||
plate.BNPTankActive = true
|
||||
plate.BNPTankR = r
|
||||
plate.BNPTankG = g
|
||||
@@ -79,6 +172,11 @@ end
|
||||
|
||||
local function EnforceTankColor(plate)
|
||||
if not plate or not plate.BNPTankActive then return end
|
||||
-- Never paint a previous unit's cached color onto a recycled plate.
|
||||
if not BNP:IsTankModeEnabled() or plate.BNPTankColorGUID ~= GetPlateGUID(plate) then
|
||||
ClearTankState(plate)
|
||||
return
|
||||
end
|
||||
local bar = GetBar(plate)
|
||||
if not bar or not plate.BNPTankR then return end
|
||||
|
||||
@@ -118,10 +216,36 @@ function BNP:UpdateTankModePlate(plate)
|
||||
return
|
||||
end
|
||||
|
||||
local thirdColor = self:IsTankNoTargetEnabled()
|
||||
if thirdColor then
|
||||
if plate.BNPTankUnitGUID ~= guid then ClearTankState(plate) end
|
||||
plate.BNPTankUnitGUID = guid
|
||||
if UnitIsDead and UnitIsDead(guid) then
|
||||
RestoreNormalColor(plate, bar)
|
||||
return
|
||||
end
|
||||
elseif plate.BNPTankNoTargetSince or plate.BNPTankNoTargetActive then
|
||||
RestoreNormalColor(plate, bar)
|
||||
end
|
||||
|
||||
local myGUID = GetPlayerGUID()
|
||||
if not myGUID then return end
|
||||
|
||||
local targetGUID = GetTargetGUID(guid)
|
||||
local targetGUID, noTarget = GetTargetGUID(guid)
|
||||
local now = thirdColor and GetTime()
|
||||
if thirdColor and IsTemporarilyInactive(plate, guid, now, noTarget) then
|
||||
local r, g, b = self:GetTankNoTargetColor()
|
||||
ApplyTankColor(plate, bar, r, g, b)
|
||||
plate.BNPTankNoTargetActive = true
|
||||
return
|
||||
end
|
||||
plate.BNPTankNoTargetSince = nil
|
||||
if thirdColor and not targetGUID then
|
||||
-- No active control and no readable target: relinquish the third color.
|
||||
RestoreNormalColor(plate, bar)
|
||||
return
|
||||
end
|
||||
plate.BNPTankNoTargetActive = nil
|
||||
local inverted = self:AreTankModeColorsInverted()
|
||||
|
||||
if targetGUID and targetGUID == myGUID then
|
||||
@@ -153,6 +277,14 @@ function BNP:RefreshTankMode()
|
||||
end
|
||||
|
||||
function BNP:UpdateTankMode()
|
||||
local enabled = self:IsTankModeEnabled() and self:IsTankNoTargetEnabled()
|
||||
if not enabled then
|
||||
local plate
|
||||
for plate in pairs(self.plates or {}) do
|
||||
plate.BNPTankControlGUID = nil
|
||||
plate.BNPTankControlled = nil
|
||||
end
|
||||
end
|
||||
self:RefreshTankMode()
|
||||
end
|
||||
|
||||
@@ -175,6 +307,13 @@ function BNP:InstallTankMode()
|
||||
if old then old() end
|
||||
EnforceTankColor(current)
|
||||
end)
|
||||
-- Blizzard may repaint on camera movement without changing HP. Keep
|
||||
-- only the cached color in sync here; combat/aura reads stay throttled.
|
||||
local oldUpdate = bar:GetScript("OnUpdate")
|
||||
bar:SetScript("OnUpdate", function()
|
||||
if oldUpdate then oldUpdate() end
|
||||
EnforceTankColor(current)
|
||||
end)
|
||||
current.BNPTankColorHooked = true
|
||||
end
|
||||
end)
|
||||
@@ -183,6 +322,9 @@ function BNP:InstallTankMode()
|
||||
local current = plate or this
|
||||
if not current then return end
|
||||
ClearTankState(current)
|
||||
current.BNPTankControlGUID = nil
|
||||
current.BNPTankControlled = nil
|
||||
BNP:UpdateTankModePlate(current)
|
||||
end)
|
||||
|
||||
table.insert(self.libnameplate.OnUpdate, function(plate, elapsed)
|
||||
@@ -192,7 +334,9 @@ function BNP:InstallTankMode()
|
||||
-- Tank Mode is optional. Avoid all GUID/target/tap queries while it is off.
|
||||
-- Only clean an old BNP-owned state if one is still present.
|
||||
if not BNP:IsTankModeEnabled() then
|
||||
if current.BNPTankActive then ClearTankState(current) end
|
||||
if current.BNPTankActive or current.BNPTankNoTargetSince then
|
||||
RestoreNormalColor(current, GetBar(current))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
+37
-8
@@ -247,12 +247,9 @@ local combatEvents={
|
||||
"CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE",
|
||||
"CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE",
|
||||
}
|
||||
local cei
|
||||
for cei=1,table.getn(combatEvents) do combatFrame:RegisterEvent(combatEvents[cei]) end
|
||||
combatFrame:SetScript("OnEvent",function() AddCombatEffect(arg1) end)
|
||||
|
||||
local castFrame = CreateFrame("Frame")
|
||||
castFrame:RegisterEvent("UNIT_CASTEVENT")
|
||||
castFrame:SetScript("OnEvent", function()
|
||||
local playerGUID = GetPlayerGUID()
|
||||
if not playerGUID or arg1 ~= playerGUID then return end
|
||||
@@ -265,7 +262,7 @@ end)
|
||||
|
||||
local scanner = CreateFrame("Frame")
|
||||
local elapsed = 0
|
||||
scanner:SetScript("OnUpdate", function()
|
||||
local function UnknownScannerOnUpdate()
|
||||
elapsed = elapsed + arg1
|
||||
if elapsed < 0.35 then return end
|
||||
elapsed = 0
|
||||
@@ -292,7 +289,7 @@ scanner:SetScript("OnUpdate", function()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function BNP:PrintUnknownBeta(verbose)
|
||||
local data = self.unknownBeta
|
||||
@@ -362,14 +359,17 @@ function BNP:PrintUnknownBeta(verbose)
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:ResetUnknownBeta()
|
||||
function BNP:ResetUnknownBeta(silent)
|
||||
self.unknownBeta.entries = {}
|
||||
self.unknownBeta.order = {}
|
||||
self.unknownBeta.recentCasts = {}
|
||||
self.unknownBeta.seenByGUID = {}
|
||||
self.unknownBeta.combat = {}
|
||||
self.unknownBeta.activeUntil = 0
|
||||
self:Print("Unknown spell/effect list cleared.")
|
||||
self.unknownDirectEvents = {}
|
||||
if not silent then
|
||||
self:Print("Unknown spell/effect list cleared.")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -449,7 +449,6 @@ local function RememberDirectEvent(eventType, spellID, targetGUID, extra)
|
||||
end
|
||||
|
||||
local directFrame = CreateFrame("Frame")
|
||||
directFrame:RegisterEvent("UNIT_CASTEVENT")
|
||||
directFrame:SetScript("OnEvent", function()
|
||||
local casterGUID = arg1
|
||||
local targetGUID = arg2
|
||||
@@ -465,6 +464,36 @@ directFrame:SetScript("OnEvent", function()
|
||||
end
|
||||
end)
|
||||
|
||||
-- Keep the legacy unknown-spell collector completely dormant during normal
|
||||
-- gameplay. It is diagnostic-only and can otherwise turn into a permanent
|
||||
-- 0.35s aura scan while the player is continuously casting in combat. The
|
||||
-- Missing Spell / Aura Recorder enables it only for the duration of an active
|
||||
-- recording, where the extra diagnostics are actually useful.
|
||||
local unknownDiagnosticsActive = false
|
||||
function BNP:SetUnknownDiagnosticsActive(enabled)
|
||||
enabled = enabled and true or false
|
||||
if unknownDiagnosticsActive == enabled then return end
|
||||
unknownDiagnosticsActive = enabled
|
||||
|
||||
if enabled then
|
||||
local i
|
||||
for i = 1, table.getn(combatEvents) do
|
||||
combatFrame:RegisterEvent(combatEvents[i])
|
||||
end
|
||||
castFrame:RegisterEvent("UNIT_CASTEVENT")
|
||||
directFrame:RegisterEvent("UNIT_CASTEVENT")
|
||||
elapsed = 0
|
||||
scanner:SetScript("OnUpdate", UnknownScannerOnUpdate)
|
||||
else
|
||||
combatFrame:UnregisterAllEvents()
|
||||
castFrame:UnregisterAllEvents()
|
||||
directFrame:UnregisterAllEvents()
|
||||
scanner:SetScript("OnUpdate", nil)
|
||||
elapsed = 0
|
||||
BNP.unknownBeta.activeUntil = 0
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:PrintUnknownDirectEvents()
|
||||
local list = {}
|
||||
local key, entry
|
||||
|
||||
Reference in New Issue
Block a user