Delete modules directory
This commit is contained in:
-2279
File diff suppressed because it is too large
Load Diff
@@ -1,264 +0,0 @@
|
||||
BNP = BNP or {}
|
||||
|
||||
local casts = {}
|
||||
local BAR_HEIGHT = 6
|
||||
|
||||
local function GetCastbarHeight()
|
||||
if BNP.GetCastbarHeight then return BNP:GetCastbarHeight() end
|
||||
return BAR_HEIGHT
|
||||
end
|
||||
local BAR_GAP = 2
|
||||
|
||||
local function Enabled()
|
||||
if BNP.AreCastbarsEnabled then return BNP:AreCastbarsEnabled() end
|
||||
return not BNP_DB or BNP_DB.castbars ~= false
|
||||
end
|
||||
|
||||
local function GetPlateGUID(plate)
|
||||
if not plate or not plate.GetName then return nil end
|
||||
|
||||
local token = plate:GetName(1)
|
||||
if not token then return nil end
|
||||
|
||||
local exists, guid = UnitExists(token)
|
||||
if exists and guid then return guid end
|
||||
|
||||
-- On SuperWoW the plate token itself can already be the GUID.
|
||||
return token
|
||||
end
|
||||
|
||||
local function SpellData(spellID)
|
||||
if SpellInfo and spellID then
|
||||
local name, _, texture = SpellInfo(spellID)
|
||||
return name or "Casting", texture
|
||||
end
|
||||
return "Casting", nil
|
||||
end
|
||||
|
||||
local function CreateCastbar(plate)
|
||||
if plate.BNPCastbar then return plate.BNPCastbar end
|
||||
if not plate.healthbar then return nil end
|
||||
|
||||
local parent = plate.BNPScaleWrapper or plate
|
||||
local bar = CreateFrame("StatusBar", nil, parent)
|
||||
bar:SetHeight(GetCastbarHeight())
|
||||
-- Center the castbar under the complete Blizzard nameplate.
|
||||
-- Extend beneath the level badge instead of stopping at the healthbar edge.
|
||||
-- Compact row: icon + castbar together span the complete plate width.
|
||||
-- The icon begins at the healthbar's left edge; the bar continues directly
|
||||
-- beside it and ends beneath the level badge.
|
||||
bar:SetPoint("TOPLEFT", plate.healthbar, "BOTTOMLEFT", 15, -BAR_GAP)
|
||||
bar:SetPoint("TOPRIGHT", plate.healthbar, "BOTTOMRIGHT", 14, -BAR_GAP)
|
||||
bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
|
||||
bar:SetStatusBarColor(1.0, 0.8, 0.0)
|
||||
bar:SetMinMaxValues(0, 1)
|
||||
bar:SetValue(0)
|
||||
bar:SetFrameLevel((plate.healthbar:GetFrameLevel() or 1) + 1)
|
||||
bar:Hide()
|
||||
|
||||
local bg = bar:CreateTexture(nil, "BACKGROUND")
|
||||
bg:SetTexture("Interface\\TargetingFrame\\UI-StatusBar")
|
||||
bg:SetVertexColor(0.10, 0.10, 0.00, 0.85)
|
||||
bg:SetAllPoints(bar)
|
||||
|
||||
local text = bar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||
text:SetPoint("CENTER", bar, "CENTER", 0, 0)
|
||||
text:SetTextColor(1, 1, 1)
|
||||
bar.text = text
|
||||
|
||||
-- Spell icon to the left of the castbar.
|
||||
local icon = CreateFrame("Frame", nil, parent)
|
||||
icon:SetWidth(14)
|
||||
icon:SetHeight(14)
|
||||
icon:SetPoint("TOPLEFT", plate.healthbar, "BOTTOMLEFT", 0, -BAR_GAP)
|
||||
icon:SetFrameLevel(bar:GetFrameLevel() + 1)
|
||||
|
||||
local texture = icon:CreateTexture(nil, "ARTWORK")
|
||||
texture:SetAllPoints(icon)
|
||||
icon.texture = texture
|
||||
|
||||
local border = icon:CreateTexture(nil, "OVERLAY")
|
||||
border:SetTexture("Interface\\Buttons\\UI-Quickslot2")
|
||||
border:SetPoint("CENTER", icon, "CENTER", 0, 0)
|
||||
border:SetWidth(23)
|
||||
border:SetHeight(23)
|
||||
icon.border = border
|
||||
|
||||
icon:Hide()
|
||||
bar.icon = icon
|
||||
|
||||
plate.BNPCastbar = bar
|
||||
return bar
|
||||
end
|
||||
|
||||
local function Hide(plate)
|
||||
if plate and plate.BNPCastbar then
|
||||
plate.BNPCastbar:Hide()
|
||||
if plate.BNPCastbar.icon then
|
||||
plate.BNPCastbar.icon:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Exact SuperWoW event path used by ShaguTweaks' superwow module:
|
||||
-- START / CAST / CHANNEL create cast state; FAIL removes it.
|
||||
local eventFrame = CreateFrame("Frame")
|
||||
eventFrame:RegisterEvent("UNIT_CASTEVENT")
|
||||
eventFrame:SetScript("OnEvent", function()
|
||||
local guid = arg1
|
||||
local eventType = arg3
|
||||
local spellID = arg4
|
||||
local timerMS = tonumber(arg5) or 0
|
||||
|
||||
if not guid then return end
|
||||
|
||||
if eventType == "START" or eventType == "CAST" or eventType == "CHANNEL" then
|
||||
if timerMS <= 0 then
|
||||
-- Instant casts do not need a visible castbar.
|
||||
casts[guid] = nil
|
||||
return
|
||||
end
|
||||
|
||||
local name, texture = SpellData(spellID)
|
||||
local now = GetTime()
|
||||
local duration = timerMS / 1000
|
||||
|
||||
casts[guid] = {
|
||||
name = name,
|
||||
texture = texture,
|
||||
startTime = now,
|
||||
endTime = now + duration,
|
||||
duration = duration,
|
||||
channel = eventType == "CHANNEL",
|
||||
}
|
||||
elseif eventType == "FAIL" then
|
||||
casts[guid] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
local activeCastPlates = {}
|
||||
|
||||
local function MarkCastPlateActive(plate)
|
||||
if not plate then return end
|
||||
activeCastPlates[plate] = true
|
||||
end
|
||||
|
||||
local function MarkCastPlateInactive(plate)
|
||||
if not plate then return end
|
||||
activeCastPlates[plate] = nil
|
||||
Hide(plate)
|
||||
end
|
||||
|
||||
-- Lightweight discovery pass. This does NOT animate bars; it only checks
|
||||
-- visible plates for active cast state at 20 Hz.
|
||||
local discovery = CreateFrame("Frame")
|
||||
local discoveryElapsed = 0
|
||||
discovery:SetScript("OnUpdate", function()
|
||||
discoveryElapsed = discoveryElapsed + arg1
|
||||
if discoveryElapsed < 0.05 then return end
|
||||
discoveryElapsed = 0
|
||||
|
||||
if not Enabled() then
|
||||
local plate
|
||||
for plate in pairs(activeCastPlates) do
|
||||
MarkCastPlateInactive(plate)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local now = GetTime()
|
||||
local plate
|
||||
for plate in pairs(BNP.plates) do
|
||||
if plate:IsShown() then
|
||||
local guid = GetPlateGUID(plate)
|
||||
local cast = guid and casts[guid]
|
||||
|
||||
if cast and now < cast.endTime then
|
||||
MarkCastPlateActive(plate)
|
||||
elseif activeCastPlates[plate] then
|
||||
MarkCastPlateInactive(plate)
|
||||
end
|
||||
elseif activeCastPlates[plate] then
|
||||
MarkCastPlateInactive(plate)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Frame-perfect animation remains unchanged visually, but only active
|
||||
-- castbars are updated every rendered frame.
|
||||
local updater = CreateFrame("Frame")
|
||||
updater:SetScript("OnUpdate", function()
|
||||
if not Enabled() then return end
|
||||
|
||||
local now = GetTime()
|
||||
local plate
|
||||
for plate in pairs(activeCastPlates) do
|
||||
if not plate:IsShown() then
|
||||
MarkCastPlateInactive(plate)
|
||||
else
|
||||
local guid = GetPlateGUID(plate)
|
||||
local cast = guid and casts[guid]
|
||||
|
||||
if not cast or now >= cast.endTime then
|
||||
if guid and cast and now >= cast.endTime then
|
||||
casts[guid] = nil
|
||||
end
|
||||
MarkCastPlateInactive(plate)
|
||||
else
|
||||
local bar = CreateCastbar(plate)
|
||||
if bar then
|
||||
local current = now - cast.startTime
|
||||
if current < 0 then current = 0 end
|
||||
if current > cast.duration then current = cast.duration end
|
||||
|
||||
bar:SetMinMaxValues(0, cast.duration)
|
||||
|
||||
if cast.channel then
|
||||
bar:SetValue(cast.duration - current)
|
||||
else
|
||||
bar:SetValue(current)
|
||||
end
|
||||
|
||||
bar.text:SetText(cast.name)
|
||||
bar:SetAlpha(1)
|
||||
|
||||
if bar.icon then
|
||||
if cast.texture then
|
||||
bar.icon.texture:SetTexture(cast.texture)
|
||||
bar.icon:SetAlpha(1)
|
||||
bar.icon:Show()
|
||||
else
|
||||
bar.icon:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
bar:Show()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
function BNP:RefreshCastbarHeights()
|
||||
local plate
|
||||
local height = GetCastbarHeight()
|
||||
|
||||
for plate in pairs(BNP.plates or {}) do
|
||||
if plate and plate.BNPCastbar then
|
||||
plate.BNPCastbar:SetHeight(height)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:SetCastbarsEnabled(enabled)
|
||||
BNP_DB = BNP_DB or {}
|
||||
BNP_DB.castbars = enabled and true or false
|
||||
|
||||
if not enabled then
|
||||
local plate
|
||||
for plate in pairs(BNP.plates) do Hide(plate) end
|
||||
end
|
||||
|
||||
self:Print("Nameplate castbars " .. (enabled and "enabled." or "disabled."))
|
||||
end
|
||||
@@ -1,169 +0,0 @@
|
||||
BNP = BNP or {}
|
||||
|
||||
local fallbackColors = {
|
||||
WARRIOR = { 0.78, 0.61, 0.43 },
|
||||
MAGE = { 0.41, 0.80, 0.94 },
|
||||
ROGUE = { 1.00, 0.96, 0.41 },
|
||||
DRUID = { 1.00, 0.49, 0.04 },
|
||||
HUNTER = { 0.67, 0.83, 0.45 },
|
||||
SHAMAN = { 0.00, 0.44, 0.87 },
|
||||
PRIEST = { 1.00, 1.00, 1.00 },
|
||||
WARLOCK = { 0.58, 0.51, 0.79 },
|
||||
PALADIN = { 0.96, 0.55, 0.73 },
|
||||
}
|
||||
|
||||
local function GetColor(class)
|
||||
if RAID_CLASS_COLORS and RAID_CLASS_COLORS[class] then
|
||||
local c = RAID_CLASS_COLORS[class]
|
||||
return c.r, c.g, c.b
|
||||
end
|
||||
|
||||
local c = fallbackColors[class]
|
||||
if c then return c[1], c[2], c[3] end
|
||||
end
|
||||
|
||||
local function Enabled()
|
||||
if BNP.AreClassColorsEnabled then return BNP:AreClassColorsEnabled() end
|
||||
return not BNP_DB or BNP_DB.classColors ~= false
|
||||
end
|
||||
|
||||
local function ClearClassCache(plate)
|
||||
if not plate then return end
|
||||
plate.BNPClassColorApplied = nil
|
||||
plate.BNPClassColorToken = nil
|
||||
plate.BNPClassGUID = nil
|
||||
plate.BNPClassR = nil
|
||||
plate.BNPClassG = nil
|
||||
plate.BNPClassB = nil
|
||||
end
|
||||
|
||||
local function ResolvePlateUnit(plate)
|
||||
if not plate or not plate.GetName then return nil, nil end
|
||||
|
||||
local token = plate:GetName(1)
|
||||
if not token or not UnitExists(token) then
|
||||
return token, nil
|
||||
end
|
||||
|
||||
local exists, guid = UnitExists(token)
|
||||
if exists then
|
||||
return token, guid
|
||||
end
|
||||
|
||||
return token, nil
|
||||
end
|
||||
|
||||
local function ApplyClassColor(plate)
|
||||
if not Enabled() then
|
||||
ClearClassCache(plate)
|
||||
return false
|
||||
end
|
||||
|
||||
if not plate or not plate.healthbar then return false end
|
||||
|
||||
local token, guid = ResolvePlateUnit(plate)
|
||||
|
||||
-- BNP only owns PLAYER class colors.
|
||||
-- NPC/neutral/friendly-NPC colors stay completely under Blizzard control.
|
||||
if not token or not guid or not UnitIsPlayer(token) then
|
||||
ClearClassCache(plate)
|
||||
return false
|
||||
end
|
||||
|
||||
local _, class = UnitClass(token)
|
||||
local r, g, b = GetColor(class)
|
||||
if not r then
|
||||
ClearClassCache(plate)
|
||||
return false
|
||||
end
|
||||
|
||||
plate.BNPClassColorApplied = true
|
||||
plate.BNPClassColorToken = token
|
||||
plate.BNPClassGUID = guid
|
||||
plate.BNPClassR = r
|
||||
plate.BNPClassG = g
|
||||
plate.BNPClassB = b
|
||||
|
||||
plate.healthbar:SetStatusBarColor(r, g, b)
|
||||
return true
|
||||
end
|
||||
|
||||
BNP.ApplyClassColor = ApplyClassColor
|
||||
|
||||
local function EnforceClassColor(plate)
|
||||
if not Enabled() then return end
|
||||
if not plate or not plate.healthbar or not plate.BNPClassGUID then return end
|
||||
|
||||
local token, guid = ResolvePlateUnit(plate)
|
||||
|
||||
-- Never enforce a cached color after Blizzard recycled the frame.
|
||||
if not token or not guid or guid ~= plate.BNPClassGUID or not UnitIsPlayer(token) then
|
||||
ClearClassCache(plate)
|
||||
return
|
||||
end
|
||||
|
||||
local r, g, b = plate.healthbar:GetStatusBarColor()
|
||||
if math.abs(r - plate.BNPClassR) > 0.01
|
||||
or math.abs(g - plate.BNPClassG) > 0.01
|
||||
or math.abs(b - plate.BNPClassB) > 0.01 then
|
||||
plate.healthbar:SetStatusBarColor(
|
||||
plate.BNPClassR,
|
||||
plate.BNPClassG,
|
||||
plate.BNPClassB
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if BNP.libnameplate then
|
||||
table.insert(BNP.libnameplate.OnInit, function(plate)
|
||||
local current = plate or this
|
||||
if not current then return end
|
||||
|
||||
ClearClassCache(current)
|
||||
ApplyClassColor(current)
|
||||
|
||||
local bar = current.healthbar
|
||||
if bar and not current.BNPClassColorHooked then
|
||||
local oldValueChanged = bar:GetScript("OnValueChanged")
|
||||
bar:SetScript("OnValueChanged", function()
|
||||
if oldValueChanged then oldValueChanged() end
|
||||
EnforceClassColor(current)
|
||||
end)
|
||||
current.BNPClassColorHooked = true
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(BNP.libnameplate.OnShow, function(plate)
|
||||
local current = plate or this
|
||||
if not current then return end
|
||||
ClearClassCache(current)
|
||||
ApplyClassColor(current)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Blizzard may rewrite player colors during combat/threat updates.
|
||||
-- Re-apply only while the plate still resolves to the SAME player GUID.
|
||||
-- If a recycled frame now represents an NPC/neutral unit, drop the cache
|
||||
-- immediately and never touch its Blizzard color.
|
||||
local elapsed = 0
|
||||
local updater = CreateFrame("Frame")
|
||||
updater:SetScript("OnUpdate", function()
|
||||
elapsed = elapsed + arg1
|
||||
if elapsed < 0.10 then return end
|
||||
elapsed = 0
|
||||
|
||||
if not Enabled() then return end
|
||||
|
||||
local plate
|
||||
for plate in pairs(BNP.plates) do
|
||||
if plate:IsShown() and plate.healthbar then
|
||||
local token, guid = ResolvePlateUnit(plate)
|
||||
|
||||
if plate.BNPClassGUID then
|
||||
EnforceClassColor(plate)
|
||||
elseif token and guid and UnitIsPlayer(token) then
|
||||
ApplyClassColor(plate)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -1,299 +0,0 @@
|
||||
BNP = BNP or {}
|
||||
|
||||
local _, playerClass = UnitClass("player")
|
||||
local COMBO_CLASS = (playerClass == "ROGUE" or playerClass == "DRUID")
|
||||
|
||||
local MAX_POINTS = 5
|
||||
local POINT_GAP = 1
|
||||
|
||||
local function Enabled()
|
||||
return COMBO_CLASS and BNP.AreComboPointsEnabled and BNP:AreComboPointsEnabled()
|
||||
end
|
||||
|
||||
local function GetTargetGUID()
|
||||
local exists, guid = UnitExists("target")
|
||||
if exists and guid then return guid end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function GetComboCount()
|
||||
if not GetComboPoints then return 0 end
|
||||
local count = tonumber(GetComboPoints()) or 0
|
||||
if count < 0 then count = 0 end
|
||||
if count > MAX_POINTS then count = MAX_POINTS end
|
||||
return count
|
||||
end
|
||||
|
||||
local function GetSourcePoint()
|
||||
if getglobal then
|
||||
return getglobal("TargetFrameComboPoint1")
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function GetSourceSize()
|
||||
local source = GetSourcePoint()
|
||||
local w, h = 12, 16
|
||||
|
||||
if source then
|
||||
if source.GetWidth then w = tonumber(source:GetWidth()) or w end
|
||||
if source.GetHeight then h = tonumber(source:GetHeight()) or h end
|
||||
end
|
||||
|
||||
if w <= 0 then w = 12 end
|
||||
if h <= 0 then h = 16 end
|
||||
return w, h
|
||||
end
|
||||
|
||||
local function ApplyTargetFrameScale(holder)
|
||||
if not holder then return end
|
||||
|
||||
local source = GetSourcePoint()
|
||||
local parent = holder:GetParent()
|
||||
|
||||
local sourceScale = 1.0
|
||||
local parentScale = 1.0
|
||||
|
||||
-- Match the actual on-screen scale of Blizzard's TargetFrame combo points.
|
||||
if source and source.GetEffectiveScale then
|
||||
sourceScale = tonumber(source:GetEffectiveScale()) or 1.0
|
||||
elseif UIParent and UIParent.GetEffectiveScale then
|
||||
sourceScale = tonumber(UIParent:GetEffectiveScale()) or 1.0
|
||||
elseif UIParent and UIParent.GetScale then
|
||||
sourceScale = tonumber(UIParent:GetScale()) or 1.0
|
||||
end
|
||||
|
||||
if parent and parent.GetEffectiveScale then
|
||||
parentScale = tonumber(parent:GetEffectiveScale()) or 1.0
|
||||
end
|
||||
|
||||
if sourceScale <= 0 then sourceScale = 1.0 end
|
||||
if parentScale <= 0 then parentScale = 1.0 end
|
||||
|
||||
holder:SetScale(sourceScale / parentScale)
|
||||
end
|
||||
|
||||
local function CopySourceTextureAppearance(dst, src)
|
||||
if not dst or not src then return end
|
||||
|
||||
if src.GetTexture and dst.SetTexture then
|
||||
dst:SetTexture(src:GetTexture())
|
||||
end
|
||||
|
||||
if src.GetTexCoord and dst.SetTexCoord then
|
||||
local l, r, t, b = src:GetTexCoord()
|
||||
if l then dst:SetTexCoord(l, r, t, b) end
|
||||
end
|
||||
|
||||
if src.GetVertexColor and dst.SetVertexColor then
|
||||
local r, g, b, a = src:GetVertexColor()
|
||||
if r then dst:SetVertexColor(r, g, b, a or 1) end
|
||||
end
|
||||
|
||||
if src.GetBlendMode and dst.SetBlendMode then
|
||||
local mode = src:GetBlendMode()
|
||||
if mode then dst:SetBlendMode(mode) end
|
||||
end
|
||||
end
|
||||
|
||||
local function CreateExactPoint(parent)
|
||||
local source = GetSourcePoint()
|
||||
local pointW, pointH = GetSourceSize()
|
||||
|
||||
local point = CreateFrame("Frame", nil, parent)
|
||||
point:SetWidth(pointW)
|
||||
point:SetHeight(pointH)
|
||||
|
||||
local copied = false
|
||||
|
||||
-- Clone the actual texture regions from Blizzard's TargetFrame combo point.
|
||||
-- This guarantees the same client artwork/skin instead of guessing atlas cuts.
|
||||
if source and source.GetRegions then
|
||||
local regions = { source:GetRegions() }
|
||||
local i
|
||||
|
||||
for i = 1, table.getn(regions) do
|
||||
local src = regions[i]
|
||||
if src and src.GetObjectType and src:GetObjectType() == "Texture" then
|
||||
local tex = point:CreateTexture(nil, "ARTWORK")
|
||||
CopySourceTextureAppearance(tex, src)
|
||||
|
||||
local w = src.GetWidth and tonumber(src:GetWidth()) or pointW
|
||||
local h = src.GetHeight and tonumber(src:GetHeight()) or pointH
|
||||
if not w or w <= 0 then w = pointW end
|
||||
if not h or h <= 0 then h = pointH end
|
||||
|
||||
tex:SetWidth(w)
|
||||
tex:SetHeight(h)
|
||||
|
||||
-- Preserve the standard TargetFrame relative offsets where possible.
|
||||
local anchored = false
|
||||
if src.GetPoint then
|
||||
local pnt, rel, relp, x, y = src:GetPoint(1)
|
||||
if pnt then
|
||||
-- Source textures are normally anchored to their own combo frame.
|
||||
-- Re-anchor them to our clone with the same point/offset.
|
||||
tex:SetPoint(pnt, point, relp or pnt, x or 0, y or 0)
|
||||
anchored = true
|
||||
end
|
||||
end
|
||||
|
||||
if not anchored then
|
||||
tex:SetPoint("CENTER", point, "CENTER", 0, 0)
|
||||
end
|
||||
|
||||
copied = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback only if the client does not expose TargetFrameComboPoint1 regions.
|
||||
if not copied then
|
||||
local base = point:CreateTexture(nil, "BACKGROUND")
|
||||
base:SetTexture("Interface\\ComboFrame\\ComboPoint")
|
||||
base:SetWidth(12)
|
||||
base:SetHeight(16)
|
||||
base:SetPoint("TOPLEFT", point, "TOPLEFT", 0, 0)
|
||||
base:SetTexCoord(0, 0.375, 0, 1)
|
||||
|
||||
local highlight = point:CreateTexture(nil, "ARTWORK")
|
||||
highlight:SetTexture("Interface\\ComboFrame\\ComboPoint")
|
||||
highlight:SetWidth(8)
|
||||
highlight:SetHeight(16)
|
||||
highlight:SetPoint("TOPLEFT", point, "TOPLEFT", 2, 0)
|
||||
highlight:SetTexCoord(0.375, 0.5625, 0, 1)
|
||||
end
|
||||
|
||||
point:Hide()
|
||||
return point
|
||||
end
|
||||
|
||||
local function EnsurePoints(plate)
|
||||
if plate.BNPComboPoints then
|
||||
ApplyTargetFrameScale(plate.BNPComboPoints)
|
||||
return plate.BNPComboPoints
|
||||
end
|
||||
|
||||
if not plate or not plate.name then return nil end
|
||||
|
||||
local pointW, pointH = GetSourceSize()
|
||||
local parent = plate.BNPScaleWrapper or plate
|
||||
|
||||
local holder = CreateFrame("Frame", nil, parent)
|
||||
holder:SetWidth(MAX_POINTS * pointW + (MAX_POINTS - 1) * POINT_GAP)
|
||||
holder:SetHeight(pointH)
|
||||
|
||||
-- Keep the position the user already approved.
|
||||
holder:SetPoint("BOTTOM", plate.name, "TOP", 0, 2)
|
||||
holder:SetFrameLevel((plate:GetFrameLevel() or 1) + 12)
|
||||
holder.points = {}
|
||||
holder:Hide()
|
||||
|
||||
local i
|
||||
for i = 1, MAX_POINTS do
|
||||
local point = CreateExactPoint(holder)
|
||||
|
||||
if i == 1 then
|
||||
point:SetPoint("LEFT", holder, "LEFT", 0, 0)
|
||||
else
|
||||
point:SetPoint("LEFT", holder.points[i - 1], "RIGHT", POINT_GAP, 0)
|
||||
end
|
||||
|
||||
holder.points[i] = point
|
||||
end
|
||||
|
||||
ApplyTargetFrameScale(holder)
|
||||
plate.BNPComboPoints = holder
|
||||
return holder
|
||||
end
|
||||
|
||||
local function HidePoints(plate)
|
||||
local holder = plate and plate.BNPComboPoints
|
||||
if not holder then return end
|
||||
|
||||
local i
|
||||
for i = 1, MAX_POINTS do
|
||||
holder.points[i]:Hide()
|
||||
end
|
||||
holder:Hide()
|
||||
end
|
||||
|
||||
local function UpdatePlate(plate)
|
||||
if not plate or not plate:IsShown() then return end
|
||||
|
||||
if not Enabled() then
|
||||
HidePoints(plate)
|
||||
return
|
||||
end
|
||||
|
||||
local targetGUID = GetTargetGUID()
|
||||
local plateGUID = plate.GetName and plate:GetName(1) or nil
|
||||
|
||||
if not targetGUID or not plateGUID or targetGUID ~= plateGUID then
|
||||
HidePoints(plate)
|
||||
return
|
||||
end
|
||||
|
||||
local count = GetComboCount()
|
||||
if count <= 0 then
|
||||
HidePoints(plate)
|
||||
return
|
||||
end
|
||||
|
||||
local holder = EnsurePoints(plate)
|
||||
if not holder then return end
|
||||
ApplyTargetFrameScale(holder)
|
||||
|
||||
local i
|
||||
for i = 1, MAX_POINTS do
|
||||
if i <= count then
|
||||
holder.points[i]:Show()
|
||||
else
|
||||
holder.points[i]:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
holder:Show()
|
||||
end
|
||||
|
||||
function BNP:RefreshComboPoints()
|
||||
local plate
|
||||
for plate in pairs(BNP.plates or {}) do
|
||||
if plate:IsShown() then
|
||||
UpdatePlate(plate)
|
||||
else
|
||||
HidePoints(plate)
|
||||
end
|
||||
end
|
||||
|
||||
if BNP.RefreshAllAuraLayouts then
|
||||
BNP:RefreshAllAuraLayouts()
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(BNP.libnameplate.OnInit, function(plate)
|
||||
if COMBO_CLASS then
|
||||
EnsurePoints(plate)
|
||||
HidePoints(plate)
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(BNP.libnameplate.OnShow, function(plate)
|
||||
if COMBO_CLASS then
|
||||
HidePoints(plate)
|
||||
UpdatePlate(plate)
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(BNP.libnameplate.OnUpdate, function(plate)
|
||||
if COMBO_CLASS then UpdatePlate(plate) end
|
||||
end)
|
||||
|
||||
if COMBO_CLASS then
|
||||
local events = CreateFrame("Frame")
|
||||
events:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
events:RegisterEvent("PLAYER_COMBO_POINTS")
|
||||
events:SetScript("OnEvent", function()
|
||||
BNP:RefreshComboPoints()
|
||||
end)
|
||||
end
|
||||
@@ -1,146 +0,0 @@
|
||||
SLASH_BLIZZNAMEPLATESPLUS1 = "/bnp"
|
||||
SlashCmdList["BLIZZNAMEPLATESPLUS"] = function(msg)
|
||||
msg = string.lower(msg or "")
|
||||
|
||||
if msg == "" or msg == "config" or msg == "options" then
|
||||
if BNP.ToggleOptions then BNP:ToggleOptions() end
|
||||
elseif msg == "spellprobe" or msg == "spellbook" then
|
||||
if BNP.SpellbookProbe then BNP:SpellbookProbe() else BNP:Print("Spellbook probe is not loaded.") end
|
||||
elseif msg == "spellprobe corruption" then
|
||||
if BNP.SpellbookProbe then BNP:SpellbookProbe("corruption") end
|
||||
elseif msg == "spellprobe agony" then
|
||||
if BNP.SpellbookProbe then BNP:SpellbookProbe("agony") end
|
||||
elseif msg == "spellprobe siphon" then
|
||||
if BNP.SpellbookProbe then BNP:SpellbookProbe("siphon") end
|
||||
elseif msg == "unknown" or msg == "learn" then
|
||||
if BNP.PrintUnknownBeta then BNP:PrintUnknownBeta(false) else BNP:Print("Unknown collector is not loaded.") end
|
||||
elseif msg == "unknown verbose" or msg == "learn verbose" then
|
||||
if BNP.PrintUnknownBeta then BNP:PrintUnknownBeta(true) else BNP:Print("Unknown collector is not loaded.") end
|
||||
elseif msg == "unknown reset" or msg == "learn reset" then
|
||||
if BNP.ResetUnknownBeta then BNP:ResetUnknownBeta() end
|
||||
BNP.unknownDirectEvents = {}
|
||||
elseif msg == "auras" or msg == "targetauras" then
|
||||
if BNP.PrintTargetAuraProbe then
|
||||
BNP:PrintTargetAuraProbe()
|
||||
else
|
||||
BNP:Print("Target aura probe is not loaded.")
|
||||
end
|
||||
elseif msg == "raidcheck" then
|
||||
local exists, guid = UnitExists("target")
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff66ccffBNP RAIDCHECK|r")
|
||||
if not exists or not guid then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("No target / no target GUID.")
|
||||
return
|
||||
end
|
||||
DEFAULT_CHAT_FRAME:AddMessage("Target: "..tostring(UnitName("target") or "?").." | GUID: "..tostring(guid))
|
||||
local cache=BNP.guidAuras and BNP.guidAuras[guid]
|
||||
local n=0
|
||||
if cache then
|
||||
local key,aura
|
||||
for key,aura in pairs(cache) do
|
||||
if type(aura)=="table" and aura.spellID then
|
||||
n=n+1
|
||||
local mode = aura.castPrimary and "CAST" or (aura.liveConfirmed and "LIVE" or "?")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
"CACHE | "..tostring(key).." | ID "..tostring(aura.spellID).." | "..mode
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
if n==0 then DEFAULT_CHAT_FRAME:AddMessage("CACHE: EMPTY") end
|
||||
local pending = BNP.pendingAuras and BNP.pendingAuras[guid]
|
||||
if pending then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("-- PENDING FOR TARGET --")
|
||||
local pk,pv
|
||||
for pk,pv in pairs(pending) do
|
||||
if type(pv)=="table" then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
"PENDING | "..tostring(pk)..
|
||||
" | ID "..tostring(pv.spellID or "?")..
|
||||
" | age "..string.format("%.1fs", GetTime()-(pv.created or GetTime()))
|
||||
)
|
||||
end
|
||||
end
|
||||
else
|
||||
DEFAULT_CHAT_FRAME:AddMessage("PENDING: none")
|
||||
end
|
||||
|
||||
DEFAULT_CHAT_FRAME:AddMessage("-- LAST TRACKED CAST EVENTS --")
|
||||
local trace=BNP.raidTrace or {}
|
||||
local start=math.max(1,table.getn(trace)-14)
|
||||
local ti
|
||||
for ti=start,table.getn(trace) do
|
||||
local e=trace[ti]
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
tostring(e.stage)..
|
||||
" | "..tostring(e.key or "-")..
|
||||
" | ID "..tostring(e.spellID or "?")..
|
||||
" | GUID "..tostring(e.guid or "nil")..
|
||||
" | "..string.format("%.1fs ago",GetTime()-(e.time or GetTime()))
|
||||
)
|
||||
end
|
||||
|
||||
DEFAULT_CHAT_FRAME:AddMessage("-- LIVE TARGET AURAS (up to 64) --")
|
||||
local i
|
||||
for i=1,64 do
|
||||
local tex,stacks,dtype,id=UnitDebuff("target",i)
|
||||
if not tex then break end
|
||||
local name=(id and SpellInfo and SpellInfo(id)) or "?"
|
||||
DEFAULT_CHAT_FRAME:AddMessage("LIVE "..tostring(i).." | ID "..tostring(id or "?").." | "..tostring(name))
|
||||
end
|
||||
elseif msg == "direct" then
|
||||
if BNP.PrintUnknownDirectEvents then
|
||||
BNP:PrintUnknownDirectEvents()
|
||||
else
|
||||
BNP:Print("Direct event collector is not loaded.")
|
||||
end
|
||||
elseif msg == "spelldb" or msg == "audit" then
|
||||
if BNP.PrintSpellDBAudit then BNP:PrintSpellDBAudit() else BNP:Print("SpellDB audit is not loaded.") end
|
||||
elseif msg == "spelldb reset" or msg == "audit reset" then
|
||||
if BNP.ResetSpellDBAudit then BNP:ResetSpellDBAudit() end
|
||||
elseif msg == "guid" or msg == "guiddebug" or msg == "diag" then
|
||||
if BNP.PrintGUIDDiagnostic then BNP:PrintGUIDDiagnostic() else BNP:Print("GUID diagnostics are not loaded.") end
|
||||
elseif msg == "status" then
|
||||
local visible = 0
|
||||
local plate
|
||||
for plate in pairs(BNP.plates) do
|
||||
if plate:IsShown() then visible = visible + 1 end
|
||||
end
|
||||
BNP:Print("registered: " .. BNP.detected .. ", visible: " .. visible .. ", test spell: " .. tostring(BNP.corruptionName or "not found"))
|
||||
elseif msg == "cache" then
|
||||
local name = UnitName("target")
|
||||
if not name then
|
||||
BNP:Print("No target selected.")
|
||||
return
|
||||
end
|
||||
local remaining = BNP.FindCachedCorruption and BNP.FindCachedCorruption(name)
|
||||
BNP:Print("Cache for " .. name .. ": " .. tostring(remaining or "no Corruption found"))
|
||||
elseif msg == "castbars on" then
|
||||
if BNP.SetCastbarsEnabled then BNP:SetCastbarsEnabled(true) end
|
||||
elseif msg == "castbars off" then
|
||||
if BNP.SetCastbarsEnabled then BNP:SetCastbarsEnabled(false) end
|
||||
elseif msg == "scale" then
|
||||
BNP:Print("Current nameplate scale: " .. tostring(BNP:GetNameplateScale()))
|
||||
elseif msg == "scale reset" then
|
||||
if BNP.ResetNameplateScale then BNP:ResetNameplateScale() end
|
||||
elseif string.find(msg, "^scale%s+") then
|
||||
local _, _, value = string.find(msg, "^scale%s+([0-9%.]+)")
|
||||
if BNP.SetNameplateScale then BNP:SetNameplateScale(value) end
|
||||
elseif msg == "classcolors on" then
|
||||
BNP_DB = BNP_DB or {}
|
||||
BNP_DB.classColors = true
|
||||
BNP:Print("Class colors enabled.")
|
||||
elseif msg == "classcolors off" then
|
||||
BNP_DB = BNP_DB or {}
|
||||
BNP_DB.classColors = false
|
||||
BNP:Print("Class colors disabled.")
|
||||
elseif msg == "debug on" then
|
||||
BNP:SetDebug(true)
|
||||
elseif msg == "debug off" then
|
||||
BNP:SetDebug(false)
|
||||
elseif msg == "debug" then
|
||||
BNP:SetDebug(not BNP.debugEnabled)
|
||||
else
|
||||
BNP:Print("Commands: /bnp unknown, /bnp direct, /bnp spellprobe, /bnp spelldb, /bnp guid, /bnp status, /bnp castbars on/off, /bnp scale <0.5-1.5>, /bnp scale reset, /bnp classcolors on/off, /bnp debug on, /bnp debug off")
|
||||
end
|
||||
end
|
||||
@@ -1,43 +0,0 @@
|
||||
if not BNP.libnameplate then return end
|
||||
|
||||
local function CreateMarker(plate)
|
||||
if not plate then return end
|
||||
BNP:RegisterPlate(plate)
|
||||
|
||||
if not plate.BNPMarker then
|
||||
local marker = CreateFrame("Frame", nil, plate)
|
||||
marker:SetWidth(14)
|
||||
marker:SetHeight(14)
|
||||
marker:SetPoint("BOTTOM", plate, "TOP", 0, 34)
|
||||
marker:SetFrameLevel(30)
|
||||
|
||||
marker.texture = marker:CreateTexture(nil, "OVERLAY")
|
||||
marker.texture:SetAllPoints(marker)
|
||||
marker.texture:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
marker.texture:SetVertexColor(1, 0, 0, 1)
|
||||
|
||||
marker.text = marker:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||
marker.text:SetPoint("CENTER", marker, "CENTER", 0, 0)
|
||||
marker.text:SetText("+")
|
||||
marker.text:SetTextColor(1, 1, 1)
|
||||
plate.BNPMarker = marker
|
||||
end
|
||||
|
||||
if BNP.debugEnabled then plate.BNPMarker:Show() else plate.BNPMarker:Hide() end
|
||||
end
|
||||
|
||||
table.insert(BNP.libnameplate.OnInit, CreateMarker)
|
||||
table.insert(BNP.libnameplate.OnShow, CreateMarker)
|
||||
|
||||
function BNP:SetDebug(enabled)
|
||||
BNP.debugEnabled = enabled
|
||||
BNP_DB = BNP_DB or {}
|
||||
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
|
||||
end
|
||||
end
|
||||
BNP:Print("Debug-Marker " .. (enabled and "aktiviert" or "deaktiviert") .. ".")
|
||||
end
|
||||
@@ -1,324 +0,0 @@
|
||||
BNP = BNP or {}
|
||||
|
||||
local NON_TARGET_ALPHA = 0.55
|
||||
|
||||
local function GetPlateGUID(plate)
|
||||
if not plate or not plate.GetName then return nil end
|
||||
local token = plate:GetName(1)
|
||||
if not token then return nil end
|
||||
local exists, guid = UnitExists(token)
|
||||
if exists and guid then return guid end
|
||||
return token
|
||||
end
|
||||
|
||||
local function GetTargetGUID()
|
||||
local exists, guid = UnitExists("target")
|
||||
if exists and guid then return guid end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function IsForeignTagged(plate)
|
||||
if not plate or not plate.GetName then return false end
|
||||
local token = plate:GetName(1)
|
||||
if not token then return false end
|
||||
|
||||
local exists = UnitExists(token)
|
||||
if not exists then return false end
|
||||
|
||||
if UnitCanAttack and not UnitCanAttack("player", token) then
|
||||
return false
|
||||
end
|
||||
|
||||
if not UnitIsTapped or not UnitIsTapped(token) then
|
||||
return false
|
||||
end
|
||||
|
||||
if UnitIsTappedByPlayer and UnitIsTappedByPlayer(token) then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function DesiredAlpha(plate)
|
||||
-- No selected target: keep all nameplates fully visible.
|
||||
local targetGUID = GetTargetGUID()
|
||||
if not targetGUID then return 1 end
|
||||
|
||||
-- Current target always stays fully visible.
|
||||
local plateGUID = GetPlateGUID(plate)
|
||||
if plateGUID and plateGUID == targetGUID then
|
||||
return 1
|
||||
end
|
||||
|
||||
-- Only non-target plates use the configured alpha.
|
||||
if BNP.GetNonTargetAlpha then
|
||||
return BNP:GetNonTargetAlpha()
|
||||
end
|
||||
|
||||
return 1
|
||||
end
|
||||
|
||||
local function SetLevelSafe(frame, level)
|
||||
if frame and frame.SetFrameLevel and level then
|
||||
if frame:GetFrameLevel() ~= level then
|
||||
frame:SetFrameLevel(level)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function ApplyTargetFrameLevel(plate)
|
||||
if not plate or not plate:IsShown() then return end
|
||||
|
||||
-- IMPORTANT:
|
||||
-- Never change the FrameStrata of projected Vanilla nameplates.
|
||||
-- Their native client/world layer keeps them behind Blizzard UI frames.
|
||||
-- Target Plate on Top is therefore implemented only with FrameLevel.
|
||||
|
||||
if plate.BNPBaseFrameLevel == nil then
|
||||
plate.BNPBaseFrameLevel = plate:GetFrameLevel() or 0
|
||||
end
|
||||
|
||||
local wrapper = plate.BNPScaleWrapper
|
||||
if wrapper and plate.BNPBaseWrapperLevel == nil then
|
||||
plate.BNPBaseWrapperLevel = wrapper:GetFrameLevel() or plate.BNPBaseFrameLevel
|
||||
end
|
||||
|
||||
local target = false
|
||||
if BNP.IsTargetOnTopEnabled and BNP:IsTargetOnTopEnabled() then
|
||||
local targetGUID = GetTargetGUID()
|
||||
local plateGUID = GetPlateGUID(plate)
|
||||
target = targetGUID and plateGUID and targetGUID == plateGUID
|
||||
end
|
||||
|
||||
local plateLevel = plate.BNPBaseFrameLevel
|
||||
local wrapperLevel = plate.BNPBaseWrapperLevel or plateLevel
|
||||
|
||||
if target then
|
||||
-- Raise the target only relative to other nameplates while preserving the
|
||||
-- native projected-nameplate strata beneath the Blizzard interface.
|
||||
plateLevel = plateLevel + 50
|
||||
wrapperLevel = wrapperLevel + 50
|
||||
end
|
||||
|
||||
SetLevelSafe(plate, plateLevel)
|
||||
if wrapper then
|
||||
SetLevelSafe(wrapper, wrapperLevel)
|
||||
end
|
||||
end
|
||||
|
||||
local function ApplyForeignTagVisual(plate)
|
||||
if not plate or not plate:IsShown() then return end
|
||||
|
||||
local bar = plate.healthbar
|
||||
if not bar or not bar.SetStatusBarColor then return end
|
||||
|
||||
local foreign = IsForeignTagged(plate)
|
||||
|
||||
if foreign then
|
||||
if not plate.BNPForeignTagGrey then
|
||||
local r, g, b, a = bar:GetStatusBarColor()
|
||||
plate.BNPForeignTagOldR = r
|
||||
plate.BNPForeignTagOldG = g
|
||||
plate.BNPForeignTagOldB = b
|
||||
plate.BNPForeignTagOldA = a
|
||||
plate.BNPForeignTagGrey = true
|
||||
end
|
||||
|
||||
-- Neutral grey: visually distinct from Target Focus transparency.
|
||||
bar:SetStatusBarColor(0.45, 0.45, 0.45, plate.BNPForeignTagOldA or 1)
|
||||
elseif plate.BNPForeignTagGrey then
|
||||
-- Restore the color that BNP/Shagu/class-color logic had before the mob
|
||||
-- became foreign-tagged. Clear state so future color changes can be learned.
|
||||
bar:SetStatusBarColor(
|
||||
plate.BNPForeignTagOldR or 1,
|
||||
plate.BNPForeignTagOldG or 0,
|
||||
plate.BNPForeignTagOldB or 0,
|
||||
plate.BNPForeignTagOldA or 1
|
||||
)
|
||||
plate.BNPForeignTagGrey = nil
|
||||
plate.BNPForeignTagOldR = nil
|
||||
plate.BNPForeignTagOldG = nil
|
||||
plate.BNPForeignTagOldB = nil
|
||||
plate.BNPForeignTagOldA = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function GetGlowVisualParent(plate)
|
||||
if not plate then return nil end
|
||||
|
||||
local anchor = plate.healthbar or plate
|
||||
local visualParent = plate.BNPScaleWrapper
|
||||
|
||||
if not visualParent and anchor and anchor.GetParent then
|
||||
visualParent = anchor:GetParent()
|
||||
end
|
||||
|
||||
return visualParent or plate
|
||||
end
|
||||
|
||||
local function KeepShaguGlowLayering(plate)
|
||||
if not plate then return end
|
||||
|
||||
local anchor = plate.healthbar
|
||||
local visualParent = GetGlowVisualParent(plate)
|
||||
if not anchor or not visualParent then return end
|
||||
|
||||
-- ShaguPlates creates its health StatusBar at FrameLevel 4 specifically
|
||||
-- "to keep above glow". Reproduce that relationship relative to BNP's
|
||||
-- visual wrapper, including when Target Plate on Top changes its level.
|
||||
if anchor.SetFrameLevel and visualParent.GetFrameLevel then
|
||||
local wanted = (visualParent:GetFrameLevel() or 0) + 4
|
||||
if anchor:GetFrameLevel() ~= wanted then
|
||||
anchor:SetFrameLevel(wanted)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function EnsureTargetGlow(plate)
|
||||
if not plate then return nil end
|
||||
if plate.BNPTargetGlow then return plate.BNPTargetGlow end
|
||||
|
||||
local anchor = plate.healthbar or plate
|
||||
local visualParent = GetGlowVisualParent(plate)
|
||||
if not anchor or not visualParent then return nil end
|
||||
|
||||
-- Direct port of ShaguPlates' target glow:
|
||||
-- nameplate.glow = nameplate:CreateTexture(nil, "BACKGROUND")
|
||||
-- glow anchored to health CENTER
|
||||
-- width = health width + 60
|
||||
-- height = health height + 30
|
||||
--
|
||||
-- BNP's only intentional visual change is +3 px on X.
|
||||
local glow = visualParent:CreateTexture(nil, "BACKGROUND")
|
||||
glow:SetTexture("Interface\\AddOns\\BlizzNameplatesPlus\\media\\shagu_target_glow.tga")
|
||||
glow:SetPoint("CENTER", anchor, "CENTER", 3, 0)
|
||||
|
||||
local width = anchor.GetWidth and anchor:GetWidth() or 120
|
||||
local height = anchor.GetHeight and anchor:GetHeight() or 8
|
||||
glow:SetWidth(width + 60)
|
||||
glow:SetHeight(height + 30)
|
||||
|
||||
local r, g, b = 1, 1, 1
|
||||
if BNP.GetTargetGlowColor then
|
||||
r, g, b = BNP:GetTargetGlowColor()
|
||||
end
|
||||
glow:SetVertexColor(r, g, b, 1)
|
||||
glow:Hide()
|
||||
|
||||
plate.BNPTargetGlow = glow
|
||||
KeepShaguGlowLayering(plate)
|
||||
return glow
|
||||
end
|
||||
|
||||
local function ApplyTargetGlow(plate)
|
||||
if not plate or not plate:IsShown() then return end
|
||||
|
||||
local glow = EnsureTargetGlow(plate)
|
||||
if not glow then return end
|
||||
|
||||
local enabled = BNP.IsTargetFocusEnabled and BNP:IsTargetFocusEnabled()
|
||||
if not enabled then
|
||||
glow:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
local targetGUID = GetTargetGUID()
|
||||
local plateGUID = GetPlateGUID(plate)
|
||||
|
||||
if targetGUID and plateGUID and targetGUID == plateGUID then
|
||||
local anchor = plate.healthbar or plate
|
||||
local width = anchor.GetWidth and anchor:GetWidth() or 120
|
||||
local height = anchor.GetHeight and anchor:GetHeight() or 8
|
||||
|
||||
KeepShaguGlowLayering(plate)
|
||||
|
||||
glow:ClearAllPoints()
|
||||
glow:SetPoint("CENTER", anchor, "CENTER", 3, 0)
|
||||
glow:SetWidth(width + 60)
|
||||
glow:SetHeight(height + 30)
|
||||
|
||||
if BNP.GetTargetGlowColor then
|
||||
local r, g, b = BNP:GetTargetGlowColor()
|
||||
glow:SetVertexColor(r, g, b, 1)
|
||||
end
|
||||
|
||||
glow:SetAlpha(1)
|
||||
glow:Show()
|
||||
else
|
||||
glow:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
local function ApplyTargetAlpha(plate)
|
||||
if not plate or not plate:IsShown() then return end
|
||||
local wanted = DesiredAlpha(plate)
|
||||
if plate:GetAlpha() ~= wanted then
|
||||
plate:SetAlpha(wanted)
|
||||
end
|
||||
end
|
||||
|
||||
local function InstallAlphaGuard(plate)
|
||||
plate = plate or this
|
||||
if not plate or plate.BNPFullAlphaGuard then return end
|
||||
|
||||
local oldOnUpdate = plate:GetScript("OnUpdate")
|
||||
plate:SetScript("OnUpdate", function()
|
||||
if oldOnUpdate then oldOnUpdate() end
|
||||
local current = this or plate
|
||||
|
||||
-- Apply the optional Y-offset after Blizzard has had a chance to update
|
||||
-- the projected nameplate for this frame. This uses the OnUpdate BNP
|
||||
-- already needs for alpha/glow/target-level handling.
|
||||
if BNP.MaintainNameplateYOffset then
|
||||
BNP:MaintainNameplateYOffset(current)
|
||||
end
|
||||
|
||||
ApplyTargetAlpha(current)
|
||||
ApplyTargetGlow(current)
|
||||
ApplyForeignTagVisual(current)
|
||||
ApplyTargetFrameLevel(current)
|
||||
end)
|
||||
|
||||
plate.BNPFullAlphaGuard = true
|
||||
ApplyTargetAlpha(plate)
|
||||
end
|
||||
|
||||
if BNP.libnameplate then
|
||||
table.insert(BNP.libnameplate.OnInit, function(plate)
|
||||
InstallAlphaGuard(plate or this)
|
||||
end)
|
||||
|
||||
table.insert(BNP.libnameplate.OnShow, function(plate)
|
||||
local current = plate or this
|
||||
if not current then return end
|
||||
InstallAlphaGuard(current)
|
||||
ApplyTargetAlpha(current)
|
||||
ApplyTargetGlow(current)
|
||||
ApplyForeignTagVisual(current)
|
||||
ApplyTargetFrameLevel(current)
|
||||
end)
|
||||
end
|
||||
|
||||
function BNP:RefreshTargetFocus()
|
||||
local plate
|
||||
for plate in pairs(self.plates or {}) do
|
||||
ApplyTargetAlpha(plate)
|
||||
ApplyTargetGlow(plate)
|
||||
ApplyForeignTagVisual(plate)
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:RefreshNonTargetAlpha()
|
||||
local plate
|
||||
for plate in pairs(self.plates or {}) do
|
||||
ApplyTargetAlpha(plate)
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:RefreshTargetOnTop()
|
||||
local plate
|
||||
for plate in pairs(self.plates or {}) do
|
||||
ApplyTargetFrameLevel(plate)
|
||||
end
|
||||
end
|
||||
@@ -1,73 +0,0 @@
|
||||
-- SuperWoW GUID probe.
|
||||
-- This module does not alter aura tracking or rendering. It only inspects the
|
||||
-- extended UnitExists return value and the unit token exposed by nameplates.
|
||||
|
||||
local function CleanText(text)
|
||||
if not text then return nil end
|
||||
text = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "")
|
||||
text = string.gsub(text, "|r", "")
|
||||
return text
|
||||
end
|
||||
|
||||
local function SafePlateToken(plate)
|
||||
if not plate or not plate.GetName then return nil end
|
||||
|
||||
-- SuperWoW extends frame:GetName(index). ShaguTweaks already uses
|
||||
-- plate:GetName(1) for its SuperWoW nameplate castbar implementation.
|
||||
local ok, token = pcall(function() return plate:GetName(1) end)
|
||||
if ok then return token end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function UnitExistsData(unit)
|
||||
if not unit then return nil, nil end
|
||||
local ok, exists, guid = pcall(UnitExists, unit)
|
||||
if not ok then return nil, nil end
|
||||
return exists, guid
|
||||
end
|
||||
|
||||
function BNP:GetSuperWoWGUID(unit)
|
||||
local exists, guid = UnitExistsData(unit)
|
||||
if exists and guid then return guid end
|
||||
return nil
|
||||
end
|
||||
|
||||
function BNP:GetPlateSuperWoWData(plate)
|
||||
local token = SafePlateToken(plate)
|
||||
local exists, guid = UnitExistsData(token)
|
||||
return token, exists, guid
|
||||
end
|
||||
|
||||
function BNP:PrintGUIDDiagnostic()
|
||||
BNP:Print("=== SuperWoW GUID Diagnose ===")
|
||||
|
||||
local targetName = UnitName("target")
|
||||
local targetExists, targetGUID = UnitExistsData("target")
|
||||
BNP:Print("Target: " .. tostring(targetName or "kein Ziel"))
|
||||
BNP:Print("UnitExists(target): " .. tostring(targetExists) .. " | GUID: " .. tostring(targetGUID))
|
||||
|
||||
local visible = 0
|
||||
local sameName = 0
|
||||
local guidMatches = 0
|
||||
local index = 0
|
||||
|
||||
for plate in pairs(BNP.plates) do
|
||||
if plate and plate:IsShown() then
|
||||
visible = visible + 1
|
||||
local plateName = plate.name and plate.name.GetText and CleanText(plate.name:GetText()) or nil
|
||||
|
||||
if not targetName or plateName == targetName then
|
||||
sameName = sameName + 1
|
||||
index = index + 1
|
||||
local token, exists, guid = BNP:GetPlateSuperWoWData(plate)
|
||||
if targetGUID and guid and targetGUID == guid then guidMatches = guidMatches + 1 end
|
||||
|
||||
BNP:Print("Plate " .. index .. ": " .. tostring(plateName))
|
||||
BNP:Print(" Token: " .. tostring(token) .. " | Exists: " .. tostring(exists))
|
||||
BNP:Print(" GUID: " .. tostring(guid) .. " | Target-Match: " .. tostring(targetGUID and guid == targetGUID or false))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
BNP:Print("Sichtbar: " .. visible .. " | gleicher Name: " .. sameName .. " | GUID-Matches: " .. guidMatches)
|
||||
end
|
||||
@@ -1,224 +0,0 @@
|
||||
BNP = BNP or {}
|
||||
|
||||
local function Enabled()
|
||||
return BNP.IsHealthTextEnabled and BNP:IsHealthTextEnabled()
|
||||
end
|
||||
|
||||
local function CreateHealthText(plate)
|
||||
if plate.BNPHealthPercent then return plate.BNPHealthPercent end
|
||||
if not plate or not plate.healthbar then return nil end
|
||||
|
||||
-- Parent the text directly to the Blizzard health StatusBar. This keeps it
|
||||
-- above the bar texture and automatically follows every frame-level change
|
||||
-- made for Target Glow / Target Plate on Top. No separate holder level can
|
||||
-- become stale during target switches or nameplate recycling.
|
||||
local anchor = plate.healthbar
|
||||
local text = anchor:CreateFontString(nil, "OVERLAY")
|
||||
text:SetFont("Fonts\\FRIZQT__.TTF", 9, "OUTLINE")
|
||||
text:SetPoint("CENTER", anchor, "CENTER", 3, 0)
|
||||
text:SetTextColor(1, 1, 1)
|
||||
text:Hide()
|
||||
|
||||
plate.BNPHealthPercent = text
|
||||
return text
|
||||
end
|
||||
|
||||
local function GetPercentFromBar(plate)
|
||||
if not plate or not plate.healthbar then return nil end
|
||||
|
||||
local minValue, maxValue = plate.healthbar:GetMinMaxValues()
|
||||
local value = plate.healthbar:GetValue()
|
||||
|
||||
if value and minValue and maxValue and maxValue > minValue then
|
||||
local pct = ((value - minValue) / (maxValue - minValue)) * 100
|
||||
return math.floor(pct + 0.5)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function GetPercentFromUnit(plate)
|
||||
if not plate or not plate.GetName then return nil end
|
||||
|
||||
local token = plate:GetName(1)
|
||||
if not token or not UnitExists(token) then return nil end
|
||||
if not UnitHealth or not UnitHealthMax then return nil end
|
||||
|
||||
local health = UnitHealth(token)
|
||||
local maxHealth = UnitHealthMax(token)
|
||||
|
||||
if health and maxHealth and maxHealth > 0 then
|
||||
return math.floor((health / maxHealth) * 100 + 0.5)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function GetHealthFromUnit(plate)
|
||||
if not plate or not plate.GetName then return nil, nil end
|
||||
|
||||
local token = plate:GetName(1)
|
||||
if not token or not UnitExists(token) then return nil, nil end
|
||||
if not UnitHealth or not UnitHealthMax then return nil, nil end
|
||||
|
||||
local health = tonumber(UnitHealth(token))
|
||||
local maxHealth = tonumber(UnitHealthMax(token))
|
||||
|
||||
if health and maxHealth and maxHealth > 0 then
|
||||
return health, maxHealth
|
||||
end
|
||||
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local function GetHealthFromBar(plate)
|
||||
if not plate or not plate.healthbar then return nil, nil end
|
||||
|
||||
local minValue, maxValue = plate.healthbar:GetMinMaxValues()
|
||||
local value = plate.healthbar:GetValue()
|
||||
|
||||
value = tonumber(value)
|
||||
minValue = tonumber(minValue)
|
||||
maxValue = tonumber(maxValue)
|
||||
|
||||
if value and minValue and maxValue and maxValue > minValue then
|
||||
return value - minValue, maxValue - minValue
|
||||
end
|
||||
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local function FormatHealth(value)
|
||||
value = tonumber(value)
|
||||
if not value then return nil end
|
||||
if value < 0 then value = 0 end
|
||||
|
||||
-- Keep boss/player values readable inside the compact Blizzard healthbar.
|
||||
if value >= 1000000 then
|
||||
local shown = math.floor((value / 1000000) * 10 + 0.5) / 10
|
||||
return tostring(shown) .. "m"
|
||||
elseif value >= 10000 then
|
||||
return tostring(math.floor(value / 1000 + 0.5)) .. "k"
|
||||
elseif value >= 1000 then
|
||||
local shown = math.floor((value / 1000) * 10 + 0.5) / 10
|
||||
return tostring(shown) .. "k"
|
||||
end
|
||||
|
||||
return tostring(math.floor(value + 0.5))
|
||||
end
|
||||
|
||||
local function UpdatePlate(plate)
|
||||
if not plate or not plate.healthbar then return end
|
||||
|
||||
local text = plate.BNPHealthPercent
|
||||
|
||||
if not Enabled() or not plate:IsShown() then
|
||||
if text then text:Hide() end
|
||||
return
|
||||
end
|
||||
|
||||
text = text or CreateHealthText(plate)
|
||||
if not text then return end
|
||||
|
||||
local mode = BNP:GetHealthTextMode()
|
||||
local health, maxHealth = GetHealthFromUnit(plate)
|
||||
|
||||
-- Fallback for clients/units where the SuperWoW unit token is temporarily
|
||||
-- unavailable. This may be normalized health on stock Vanilla.
|
||||
if health == nil or maxHealth == nil then
|
||||
health, maxHealth = GetHealthFromBar(plate)
|
||||
end
|
||||
|
||||
local pct = nil
|
||||
if health and maxHealth and maxHealth > 0 then
|
||||
pct = math.floor((health / maxHealth) * 100 + 0.5)
|
||||
else
|
||||
pct = GetPercentFromBar(plate)
|
||||
if pct == nil then pct = GetPercentFromUnit(plate) end
|
||||
end
|
||||
|
||||
if pct ~= nil then
|
||||
if pct < 0 then pct = 0 end
|
||||
if pct > 100 then pct = 100 end
|
||||
end
|
||||
|
||||
local hpText = FormatHealth(health)
|
||||
local display = nil
|
||||
|
||||
if mode == "percent" then
|
||||
if pct ~= nil then display = tostring(pct) .. "%" end
|
||||
elseif mode == "hp" then
|
||||
display = hpText
|
||||
elseif mode == "both" then
|
||||
if hpText and pct ~= nil then
|
||||
display = hpText .. " | " .. tostring(pct) .. "%"
|
||||
elseif hpText then
|
||||
display = hpText
|
||||
elseif pct ~= nil then
|
||||
display = tostring(pct) .. "%"
|
||||
end
|
||||
end
|
||||
|
||||
if not display or display == "" then
|
||||
text:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
if text.BNPLastHealthText ~= display then
|
||||
text:SetText(display)
|
||||
text.BNPLastHealthText = display
|
||||
end
|
||||
text:Show()
|
||||
end
|
||||
|
||||
-- Intentionally simple fallback: only reads values and updates text.
|
||||
-- No SetScript calls on healthbar, no SetAlpha, no color changes.
|
||||
local updater = CreateFrame("Frame")
|
||||
local elapsed = 0
|
||||
updater:SetScript("OnUpdate", function()
|
||||
elapsed = elapsed + arg1
|
||||
if elapsed < 0.10 then return end
|
||||
elapsed = 0
|
||||
|
||||
local plate
|
||||
for plate in pairs(BNP.plates or {}) do
|
||||
if plate:IsShown() then
|
||||
UpdatePlate(plate)
|
||||
elseif plate.BNPHealthPercent then
|
||||
plate.BNPHealthPercent:Hide()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function BNP:RefreshHealthPercent()
|
||||
local plate
|
||||
for plate in pairs(self.plates or {}) do
|
||||
if plate and plate:IsShown() then
|
||||
UpdatePlate(plate)
|
||||
elseif plate and plate.BNPHealthPercent then
|
||||
plate.BNPHealthPercent:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Refresh immediately when Blizzard reuses a nameplate instead of waiting for
|
||||
-- the next 0.10 s fallback pass.
|
||||
if BNP.libnameplate then
|
||||
table.insert(BNP.libnameplate.OnShow, function(plate)
|
||||
local current = plate or this
|
||||
if current then UpdatePlate(current) end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Target switches can change nameplate frame levels in the same frame. Because
|
||||
-- the text now belongs to the healthbar this is only a value/visibility refresh,
|
||||
-- not a layering workaround.
|
||||
local healthEvents = CreateFrame("Frame")
|
||||
healthEvents:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
healthEvents:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
healthEvents:SetScript("OnEvent", function()
|
||||
if BNP.RefreshHealthPercent then
|
||||
BNP:RefreshHealthPercent()
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
BNP = BNP or {}
|
||||
|
||||
local MIN_SCALE = 0.50
|
||||
local MAX_SCALE = 1.50
|
||||
local DEFAULT_SCALE = 1.00
|
||||
|
||||
local function Clamp(value)
|
||||
if value < MIN_SCALE then return MIN_SCALE end
|
||||
if value > MAX_SCALE then return MAX_SCALE end
|
||||
return value
|
||||
end
|
||||
|
||||
function BNP:GetNameplateScale()
|
||||
BNP_DB = BNP_DB or {}
|
||||
return Clamp(tonumber(BNP_DB.nameplateScale) or DEFAULT_SCALE)
|
||||
end
|
||||
|
||||
local function EffectiveScale()
|
||||
local uiScale = 1.0
|
||||
if UIParent and UIParent.GetScale then
|
||||
uiScale = UIParent:GetScale() or 1.0
|
||||
end
|
||||
return uiScale * BNP:GetNameplateScale()
|
||||
end
|
||||
|
||||
local function CaptureAnchorState(object, plate, wrapper)
|
||||
if not object or not object.GetPoint then return nil end
|
||||
|
||||
local state = {
|
||||
object = object,
|
||||
points = {},
|
||||
}
|
||||
|
||||
local count = 1
|
||||
if object.GetNumPoints then
|
||||
count = object:GetNumPoints() or 1
|
||||
if count < 1 then count = 1 end
|
||||
end
|
||||
|
||||
local i
|
||||
for i = 1, count do
|
||||
local point, relativeTo, relativePoint, x, y = object:GetPoint(i)
|
||||
if point then
|
||||
if relativeTo == plate then
|
||||
relativeTo = wrapper
|
||||
end
|
||||
|
||||
state.points[table.getn(state.points) + 1] = {
|
||||
point = point,
|
||||
relativeTo = relativeTo or wrapper,
|
||||
relativePoint = relativePoint or point,
|
||||
x = x or 0,
|
||||
y = y or 0,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
|
||||
local function AnchorStateMatches(state)
|
||||
if not state or not state.object or not state.object.GetPoint then return false end
|
||||
|
||||
local object = state.object
|
||||
local expectedCount = table.getn(state.points)
|
||||
local currentCount = expectedCount
|
||||
|
||||
if object.GetNumPoints then
|
||||
currentCount = object:GetNumPoints() or 0
|
||||
end
|
||||
|
||||
if currentCount ~= expectedCount then
|
||||
return false
|
||||
end
|
||||
|
||||
local i
|
||||
for i = 1, expectedCount do
|
||||
local expected = state.points[i]
|
||||
local point, relativeTo, relativePoint, x, y = object:GetPoint(i)
|
||||
|
||||
if point ~= expected.point
|
||||
or relativeTo ~= expected.relativeTo
|
||||
or relativePoint ~= expected.relativePoint
|
||||
or math.abs((x or 0) - expected.x) > 0.01
|
||||
or math.abs((y or 0) - expected.y) > 0.01 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function RestoreAnchorState(state, wrapper)
|
||||
if not state or not state.object then return end
|
||||
|
||||
local object = state.object
|
||||
|
||||
if object.GetParent and object.SetParent and object:GetParent() ~= wrapper then
|
||||
object:SetParent(wrapper)
|
||||
end
|
||||
|
||||
-- Do nothing in the common case. Blizzard normally only rewrites a subset
|
||||
-- of nameplate anchors while moving, most notably the level FontString.
|
||||
if AnchorStateMatches(state) then return end
|
||||
|
||||
if not object.ClearAllPoints or not object.SetPoint then return end
|
||||
|
||||
object:ClearAllPoints()
|
||||
|
||||
local i
|
||||
for i = 1, table.getn(state.points) do
|
||||
local p = state.points[i]
|
||||
object:SetPoint(
|
||||
p.point,
|
||||
p.relativeTo or wrapper,
|
||||
p.relativePoint or p.point,
|
||||
p.x,
|
||||
p.y
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local function ApplyWrapperOffset(plate, wrapper)
|
||||
if not plate or not wrapper then return end
|
||||
|
||||
local offset = 0
|
||||
if BNP.GetNameplateYOffset then
|
||||
offset = BNP:GetNameplateYOffset()
|
||||
end
|
||||
|
||||
-- Move only the child visual container. The WorldFrame-projected Blizzard
|
||||
-- nameplate itself keeps its original anchor and can continue to be
|
||||
-- repositioned by the client every frame.
|
||||
wrapper:ClearAllPoints()
|
||||
wrapper:SetPoint("TOPLEFT", plate, "TOPLEFT", 0, offset)
|
||||
wrapper:SetPoint("BOTTOMRIGHT", plate, "BOTTOMRIGHT", 0, offset)
|
||||
end
|
||||
|
||||
local function EnsureScaleWrapper(plate)
|
||||
if not plate or plate.BNPScaleWrapper then return end
|
||||
|
||||
local originalWidth = plate:GetWidth()
|
||||
local originalHeight = plate:GetHeight()
|
||||
|
||||
local wrapper = CreateFrame("Frame", nil, plate)
|
||||
wrapper:SetAllPoints(plate)
|
||||
wrapper.plate = plate
|
||||
wrapper.originalWidth = originalWidth
|
||||
wrapper.originalHeight = originalHeight
|
||||
wrapper.BNPAnchorStates = {}
|
||||
|
||||
plate.BNPScaleWrapper = wrapper
|
||||
|
||||
-- Capture the ORIGINAL Blizzard anchors before changing parentage.
|
||||
-- Those stored anchors become BNP's authoritative visual layout whenever
|
||||
-- Y-offset is enabled.
|
||||
if plate.healthbar then
|
||||
local state = CaptureAnchorState(plate.healthbar, plate, wrapper)
|
||||
if state then
|
||||
table.insert(wrapper.BNPAnchorStates, state)
|
||||
end
|
||||
|
||||
plate.healthbar:SetParent(wrapper)
|
||||
RestoreAnchorState(state, wrapper)
|
||||
plate.healthbar:SetFrameLevel(1)
|
||||
end
|
||||
|
||||
local regions = { plate:GetRegions() }
|
||||
local i, object
|
||||
for i, object in pairs(regions) do
|
||||
if object and object.SetParent then
|
||||
local state = CaptureAnchorState(object, plate, wrapper)
|
||||
if state then
|
||||
table.insert(wrapper.BNPAnchorStates, state)
|
||||
end
|
||||
|
||||
object:SetParent(wrapper)
|
||||
RestoreAnchorState(state, wrapper)
|
||||
end
|
||||
end
|
||||
|
||||
ApplyWrapperOffset(plate, wrapper)
|
||||
end
|
||||
|
||||
function BNP:ApplyNameplateScale(plate)
|
||||
if not plate then return end
|
||||
EnsureScaleWrapper(plate)
|
||||
|
||||
local wrapper = plate.BNPScaleWrapper
|
||||
if not wrapper then return end
|
||||
|
||||
local scale = EffectiveScale()
|
||||
wrapper:SetScale(scale)
|
||||
ApplyWrapperOffset(plate, wrapper)
|
||||
|
||||
-- ShaguTweaks also adjusts the original frame bounds so Blizzard's world
|
||||
-- positioning remains correct while the child visuals honor UI scale.
|
||||
plate:SetWidth(wrapper.originalWidth * scale)
|
||||
plate:SetHeight(wrapper.originalHeight * scale)
|
||||
end
|
||||
|
||||
function BNP:ApplyNameplateScaleAll()
|
||||
local plate
|
||||
for plate in pairs(self.plates) do
|
||||
self:ApplyNameplateScale(plate)
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:ApplyNameplateYOffsetAll()
|
||||
local plate
|
||||
for plate in pairs(self.plates or {}) do
|
||||
if plate then
|
||||
self:ApplyNameplateScale(plate)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:MaintainNameplateYOffset(plate)
|
||||
if not plate or not plate:IsShown() then return end
|
||||
if not self.GetNameplateYOffset or self:GetNameplateYOffset() <= 0 then return end
|
||||
|
||||
EnsureScaleWrapper(plate)
|
||||
|
||||
local wrapper = plate.BNPScaleWrapper
|
||||
if not wrapper then return end
|
||||
|
||||
-- Never rediscover regions through plate:GetRegions() here. Once BNP moves
|
||||
-- Blizzard regions into the wrapper they are no longer guaranteed to be
|
||||
-- returned by plate:GetRegions(). Keep and maintain the original region
|
||||
-- objects captured during wrapper creation instead.
|
||||
local states = wrapper.BNPAnchorStates or {}
|
||||
local i
|
||||
for i = 1, table.getn(states) do
|
||||
RestoreAnchorState(states[i], wrapper)
|
||||
end
|
||||
|
||||
ApplyWrapperOffset(plate, wrapper)
|
||||
end
|
||||
|
||||
function BNP:SetNameplateScale(value)
|
||||
value = tonumber(value)
|
||||
if not value then
|
||||
self:Print("Usage: /bnp scale 0.5-1.5")
|
||||
return
|
||||
end
|
||||
|
||||
value = Clamp(value)
|
||||
BNP_DB = BNP_DB or {}
|
||||
BNP_DB.nameplateScale = value
|
||||
self:ApplyNameplateScaleAll()
|
||||
|
||||
self:Print("Nameplate scale multiplier set to " .. tostring(value) .. ".")
|
||||
end
|
||||
|
||||
function BNP:ResetNameplateScale()
|
||||
BNP_DB = BNP_DB or {}
|
||||
BNP_DB.nameplateScale = DEFAULT_SCALE
|
||||
self:ApplyNameplateScaleAll()
|
||||
self:Print("Nameplate scale multiplier reset to 1.0 (Shagu/UI scale behavior).")
|
||||
end
|
||||
|
||||
if BNP.libnameplate then
|
||||
table.insert(BNP.libnameplate.OnInit, function(plate)
|
||||
local current = plate or this
|
||||
if not current then return end
|
||||
BNP:ApplyNameplateScale(current)
|
||||
end)
|
||||
|
||||
table.insert(BNP.libnameplate.OnShow, function(plate)
|
||||
local current = plate or this
|
||||
if not current then return end
|
||||
BNP:ApplyNameplateScale(current)
|
||||
end)
|
||||
end
|
||||
@@ -1,67 +0,0 @@
|
||||
BNP = BNP or {}
|
||||
|
||||
-- ShaguTweaks Darkened UI compatibility -------------------------------------
|
||||
-- ShaguTweaks darkens a nameplate once and marks it with plate.darkened=true.
|
||||
-- Blizzard can later recycle that same plate and restore its original gold
|
||||
-- textures while the flag remains true. Shagu then skips the recycled plate.
|
||||
--
|
||||
-- BNP does not implement its own dark mode here. It simply asks ShaguTweaks
|
||||
-- to run its own DarkenFrame() again whenever Blizzard reuses a nameplate.
|
||||
|
||||
local pending = {}
|
||||
|
||||
local function ShaguDarkModeActive()
|
||||
return ShaguTweaks
|
||||
and ShaguTweaks.DarkMode
|
||||
and ShaguTweaks.DarkenFrame
|
||||
end
|
||||
|
||||
local function ReapplyShaguDarkMode(plate)
|
||||
if not plate or not plate:IsShown() then return end
|
||||
if not ShaguDarkModeActive() then return end
|
||||
|
||||
-- Clear Shagu's one-time marker because this Blizzard plate may have been
|
||||
-- recycled for another unit and had its textures reset.
|
||||
plate.darkened = nil
|
||||
|
||||
-- Use ShaguTweaks' own implementation/configuration as the single source of
|
||||
-- truth. Its blacklist already protects StatusBars, spell icons, portraits,
|
||||
-- etc. from being incorrectly darkened.
|
||||
ShaguTweaks.DarkenFrame(plate)
|
||||
plate.darkened = true
|
||||
end
|
||||
|
||||
local function QueueReapply(plate)
|
||||
if not plate then return end
|
||||
|
||||
-- Immediate pass handles normal OnShow.
|
||||
ReapplyShaguDarkMode(plate)
|
||||
|
||||
-- Blizzard may restore some nameplate texture state immediately after
|
||||
-- OnShow. Do one delayed pass and then stop; no permanent scanner is needed.
|
||||
pending[plate] = GetTime() + 0.08
|
||||
end
|
||||
|
||||
if BNP.libnameplate then
|
||||
table.insert(BNP.libnameplate.OnInit, function(plate)
|
||||
QueueReapply(plate or this)
|
||||
end)
|
||||
|
||||
table.insert(BNP.libnameplate.OnShow, function(plate)
|
||||
QueueReapply(plate or this)
|
||||
end)
|
||||
end
|
||||
|
||||
local updater = CreateFrame("Frame")
|
||||
updater:SetScript("OnUpdate", function()
|
||||
if not next(pending) then return end
|
||||
|
||||
local now = GetTime()
|
||||
local plate, due
|
||||
for plate, due in pairs(pending) do
|
||||
if now >= due then
|
||||
pending[plate] = nil
|
||||
ReapplyShaguDarkMode(plate)
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -1,177 +0,0 @@
|
||||
BNP = BNP or {}
|
||||
|
||||
local wanted = {
|
||||
corruption = { "Corruption", "Verderbnis" },
|
||||
agony = { "Curse of Agony", "Fluch der Pein" },
|
||||
siphon = { "Siphon Life", "Lebensentzug" },
|
||||
shadow_word_pain = { "Shadow Word: Pain", "Schattenwort: Schmerz" },
|
||||
}
|
||||
|
||||
local function isWanted(name, list)
|
||||
if not name then return false end
|
||||
local lower = string.lower(name)
|
||||
local i
|
||||
for i = 1, table.getn(list) do
|
||||
if lower == string.lower(list[i]) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function normalizeNumber(value)
|
||||
if not value then return nil end
|
||||
value = string.gsub(value, ",", ".")
|
||||
return tonumber(value)
|
||||
end
|
||||
|
||||
-- WoW 1.12 uses Lua 5.0. Use string.find captures instead of string.match.
|
||||
local function durationFromLine(text)
|
||||
if not text then return nil end
|
||||
local lower = string.lower(text)
|
||||
local _, _, num
|
||||
|
||||
-- German: 14,95 Sek. / 14.95 Sekunden
|
||||
_, _, num = string.find(lower, "([0-9]+[%,%.]?[0-9]*)%s*sek")
|
||||
if num then return normalizeNumber(num) end
|
||||
|
||||
-- English: 14.95 sec / seconds
|
||||
_, _, num = string.find(lower, "([0-9]+[%,%.]?[0-9]*)%s*sec")
|
||||
if num then return normalizeNumber(num) end
|
||||
|
||||
-- Generic short form: "over 14.95 s" / "14,95 s"
|
||||
_, _, num = string.find(lower, "over%s+([0-9]+[%,%.]?[0-9]*)%s+s")
|
||||
if num then return normalizeNumber(num) end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function getTooltip()
|
||||
local tooltip = getglobal("BNPSpellProbeTooltip")
|
||||
if tooltip then return tooltip end
|
||||
|
||||
tooltip = CreateFrame("GameTooltip", "BNPSpellProbeTooltip", UIParent, "GameTooltipTemplate")
|
||||
tooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
||||
return tooltip
|
||||
end
|
||||
|
||||
local function scanSpell(slot)
|
||||
local tooltip = getTooltip()
|
||||
tooltip:ClearLines()
|
||||
tooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
||||
tooltip:SetSpell(slot, BOOKTYPE_SPELL)
|
||||
|
||||
local lines = {}
|
||||
local bestDuration = nil
|
||||
local i
|
||||
for i = 1, tooltip:NumLines() do
|
||||
local fs = getglobal("BNPSpellProbeTooltipTextLeft" .. i)
|
||||
local text = fs and fs:GetText()
|
||||
if text and text ~= "" then
|
||||
table.insert(lines, text)
|
||||
local parsed = durationFromLine(text)
|
||||
if parsed and parsed > 0 then
|
||||
bestDuration = parsed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return bestDuration, lines
|
||||
end
|
||||
|
||||
local function findSpell(wantedNames)
|
||||
local slot = 1
|
||||
local found = nil
|
||||
while true do
|
||||
local name, rank = GetSpellName(slot, BOOKTYPE_SPELL)
|
||||
if not name then break end
|
||||
if isWanted(name, wantedNames) then
|
||||
found = { slot = slot, name = name, rank = rank }
|
||||
-- keep scanning so the highest learned rank wins
|
||||
end
|
||||
slot = slot + 1
|
||||
end
|
||||
return found
|
||||
end
|
||||
|
||||
|
||||
BNP.spellbookDurations = BNP.spellbookDurations or {}
|
||||
|
||||
function BNP:RefreshSpellbookDurations()
|
||||
local id
|
||||
for id, names in pairs(wanted) do
|
||||
local spell = findSpell(names)
|
||||
if spell then
|
||||
local ok, duration = pcall(scanSpell, spell.slot)
|
||||
if ok and duration and duration > 0 then
|
||||
BNP.spellbookDurations[id] = duration
|
||||
BNP.spellbookDurations[string.lower(spell.name)] = duration
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:GetSpellbookDurationForDef(def)
|
||||
if not def then return nil end
|
||||
BNP.spellbookDurations = BNP.spellbookDurations or {}
|
||||
|
||||
if def.key and BNP.spellbookDurations[def.key] then
|
||||
return BNP.spellbookDurations[def.key]
|
||||
end
|
||||
|
||||
if def.names then
|
||||
local i
|
||||
for i = 1, table.getn(def.names) do
|
||||
local key = string.lower(def.names[i])
|
||||
if BNP.spellbookDurations[key] then
|
||||
return BNP.spellbookDurations[key]
|
||||
end
|
||||
end
|
||||
|
||||
-- Generic on-demand scan: works for every class, not only the original
|
||||
-- Warlock/Priest probe list.
|
||||
local spell = findSpell(def.names)
|
||||
if spell then
|
||||
local ok, duration = pcall(scanSpell, spell.slot)
|
||||
if ok and duration and duration > 0 then
|
||||
if def.key then BNP.spellbookDurations[def.key] = duration end
|
||||
BNP.spellbookDurations[string.lower(spell.name)] = duration
|
||||
return duration
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function BNP:SpellbookProbe(key)
|
||||
self:RefreshSpellbookDurations()
|
||||
local keys = {}
|
||||
if key and key ~= "" and wanted[key] then
|
||||
table.insert(keys, key)
|
||||
else
|
||||
table.insert(keys, "corruption")
|
||||
table.insert(keys, "agony")
|
||||
table.insert(keys, "siphon")
|
||||
table.insert(keys, "shadow_word_pain")
|
||||
end
|
||||
|
||||
self:Print("=== Spellbook Tooltip Probe ===")
|
||||
local k
|
||||
for k = 1, table.getn(keys) do
|
||||
local id = keys[k]
|
||||
local spell = findSpell(wanted[id])
|
||||
if not spell then
|
||||
self:Print(id .. ": not found in spellbook")
|
||||
else
|
||||
local ok, duration, lines = pcall(scanSpell, spell.slot)
|
||||
if not ok then
|
||||
self:Print(spell.name .. ": tooltip scan ERROR: " .. tostring(duration))
|
||||
else
|
||||
self:Print(spell.name .. " " .. tostring(spell.rank or "") .. " | Slot " .. spell.slot .. " | Duration: " .. tostring(duration or "NOT DETECTED"))
|
||||
local i
|
||||
for i = 1, table.getn(lines) do
|
||||
self:Print(" " .. i .. ": " .. lines[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,181 +0,0 @@
|
||||
BNP = BNP or {}
|
||||
|
||||
-- Tank Mode for SuperWoW GUID nameplates.
|
||||
-- GREEN = hostile NPC currently targets the player.
|
||||
-- RED = hostile NPC currently targets another unit.
|
||||
-- Idle hostile NPCs keep their normal Blizzard color.
|
||||
|
||||
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
|
||||
|
||||
local playerGUID = nil
|
||||
|
||||
local function GetPlayerGUID()
|
||||
local exists, guid = UnitExists("player")
|
||||
if exists and guid then playerGUID = guid end
|
||||
return playerGUID
|
||||
end
|
||||
|
||||
local function GetPlateGUID(plate)
|
||||
if not plate or not plate.GetName then return nil end
|
||||
local guid = plate:GetName(1)
|
||||
if guid and guid ~= "" then return guid end
|
||||
return nil
|
||||
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
|
||||
end
|
||||
|
||||
local function GetBar(plate)
|
||||
return plate and (plate.healthbar or plate.healthBar) or nil
|
||||
end
|
||||
|
||||
local function IsHostileNPC(guid)
|
||||
if not guid or not UnitExists(guid) then return false end
|
||||
if UnitIsPlayer(guid) then return false end
|
||||
return UnitCanAttack("player", guid) and true or false
|
||||
end
|
||||
|
||||
local function IsForeignTaggedGUID(guid)
|
||||
if not guid or not UnitExists(guid) then return false end
|
||||
if UnitCanAttack and not UnitCanAttack("player", guid) then return false end
|
||||
if not UnitIsTapped or not UnitIsTapped(guid) then return false end
|
||||
if UnitIsTappedByPlayer and UnitIsTappedByPlayer(guid) then return false end
|
||||
return true
|
||||
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.
|
||||
plate.BNPTankActive = nil
|
||||
plate.BNPTankR = nil
|
||||
plate.BNPTankG = nil
|
||||
plate.BNPTankB = nil
|
||||
end
|
||||
|
||||
local function ClearTankState(plate)
|
||||
if not plate then return end
|
||||
plate.BNPTankActive = nil
|
||||
plate.BNPTankR = nil
|
||||
plate.BNPTankG = nil
|
||||
plate.BNPTankB = nil
|
||||
end
|
||||
|
||||
local function ApplyTankColor(plate, bar, r, g, b)
|
||||
bar:SetStatusBarColor(r, g, b, 1)
|
||||
plate.BNPTankActive = true
|
||||
plate.BNPTankR = r
|
||||
plate.BNPTankG = g
|
||||
plate.BNPTankB = b
|
||||
end
|
||||
|
||||
local function EnforceTankColor(plate)
|
||||
if not plate or not plate.BNPTankActive then return end
|
||||
local bar = GetBar(plate)
|
||||
if not bar or not plate.BNPTankR then return end
|
||||
|
||||
local r, g, b = bar:GetStatusBarColor()
|
||||
if math.abs(r - plate.BNPTankR) > 0.01
|
||||
or math.abs(g - plate.BNPTankG) > 0.01
|
||||
or math.abs(b - plate.BNPTankB) > 0.01 then
|
||||
bar:SetStatusBarColor(plate.BNPTankR, plate.BNPTankG, plate.BNPTankB, 1)
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:UpdateTankModePlate(plate)
|
||||
if not plate or not plate:IsShown() then return end
|
||||
|
||||
local bar = GetBar(plate)
|
||||
if not bar then return end
|
||||
|
||||
if not self:IsTankModeEnabled() then
|
||||
RestoreNormalColor(plate, bar)
|
||||
return
|
||||
end
|
||||
|
||||
local guid = GetPlateGUID(plate)
|
||||
if not IsHostileNPC(guid) then
|
||||
-- NPC reaction colors belong to Blizzard. Never restore a stale cached
|
||||
-- Tank Mode color onto neutral/friendly/recycled nameplates.
|
||||
ClearTankState(plate)
|
||||
return
|
||||
end
|
||||
|
||||
-- Foreign-tag grey has priority over Tank Mode. Previously Tank Mode wrote
|
||||
-- RED every 0.10s while fullalpha.lua restored GREY every frame, producing
|
||||
-- the visible grey/red flicker. Clear any cached tank color and let the
|
||||
-- foreign-tag renderer own the bar until the mob is ours/untapped again.
|
||||
if IsForeignTaggedGUID(guid) then
|
||||
ClearTankState(plate)
|
||||
return
|
||||
end
|
||||
|
||||
local myGUID = GetPlayerGUID()
|
||||
if not myGUID then return end
|
||||
|
||||
local targetGUID = GetTargetGUID(guid)
|
||||
|
||||
if targetGUID and targetGUID == myGUID then
|
||||
ApplyTankColor(plate, bar, GREEN_R, GREEN_G, GREEN_B)
|
||||
elseif targetGUID then
|
||||
ApplyTankColor(plate, bar, RED_R, RED_G, RED_B)
|
||||
else
|
||||
RestoreNormalColor(plate, bar)
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:RefreshTankMode()
|
||||
local plate
|
||||
for plate in pairs(self.plates or {}) do
|
||||
if plate and plate:IsShown() then
|
||||
self:UpdateTankModePlate(plate)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:UpdateTankMode()
|
||||
self:RefreshTankMode()
|
||||
end
|
||||
|
||||
function BNP:InstallTankMode()
|
||||
if self.BNPTankModeInstalled then return end
|
||||
if not self.libnameplate or not self.libnameplate.OnUpdate then return end
|
||||
|
||||
GetPlayerGUID()
|
||||
|
||||
table.insert(self.libnameplate.OnInit, function(plate)
|
||||
local current = plate or this
|
||||
if not current then return end
|
||||
|
||||
ClearTankState(current)
|
||||
|
||||
local bar = GetBar(current)
|
||||
if bar and not current.BNPTankColorHooked then
|
||||
local old = bar:GetScript("OnValueChanged")
|
||||
bar:SetScript("OnValueChanged", function()
|
||||
if old then old() end
|
||||
EnforceTankColor(current)
|
||||
end)
|
||||
current.BNPTankColorHooked = true
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(self.libnameplate.OnShow, function(plate)
|
||||
local current = plate or this
|
||||
if not current then return end
|
||||
ClearTankState(current)
|
||||
end)
|
||||
|
||||
table.insert(self.libnameplate.OnUpdate, function(plate, elapsed)
|
||||
local current = plate or this
|
||||
if not current then return end
|
||||
BNP:UpdateTankModePlate(current)
|
||||
EnforceTankColor(current)
|
||||
end)
|
||||
|
||||
self.BNPTankModeInstalled = true
|
||||
end
|
||||
@@ -1,498 +0,0 @@
|
||||
BNP = BNP or {}
|
||||
BNP.unknownDirectEvents = BNP.unknownDirectEvents or {}
|
||||
|
||||
-- Beta spell collector -------------------------------------------------------
|
||||
-- Purpose: help testers report missing Turtle/custom spell IDs without
|
||||
-- editing Lua files. It records unknown own cast IDs and unknown debuff IDs
|
||||
-- that newly appear shortly after one of the player's casts.
|
||||
--
|
||||
-- It is intentionally diagnostic only: it never changes tracking/rendering.
|
||||
|
||||
BNP.unknownBeta = BNP.unknownBeta or {
|
||||
entries = {},
|
||||
order = {},
|
||||
recentCasts = {},
|
||||
seenByGUID = {},
|
||||
activeUntil = 0,
|
||||
}
|
||||
|
||||
local _, playerClass = UnitClass("player")
|
||||
|
||||
local CLASS_AURAS = {
|
||||
WARLOCK = BNP.WarlockAuras,
|
||||
PRIEST = BNP.PriestAuras,
|
||||
WARRIOR = BNP.WarriorAuras,
|
||||
ROGUE = BNP.RogueAuras,
|
||||
HUNTER = BNP.HunterAuras,
|
||||
MAGE = BNP.MageAuras,
|
||||
DRUID = BNP.DruidAuras,
|
||||
SHAMAN = BNP.ShamanAuras,
|
||||
PALADIN = BNP.PaladinAuras,
|
||||
}
|
||||
local AURA_DEFS = CLASS_AURAS[playerClass] or {}
|
||||
|
||||
local function GetPlayerGUID()
|
||||
local exists, guid = UnitExists("player")
|
||||
if exists and guid then return guid end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function GetUnitGUID(unit)
|
||||
local exists, guid = UnitExists(unit)
|
||||
if exists and guid then return guid end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function IsExplicitKnownID(spellID)
|
||||
if not spellID then return false end
|
||||
|
||||
-- Dark Harvest is handled by dedicated Warlock logic.
|
||||
if spellID == 52552 then return true end
|
||||
|
||||
local i, j, def
|
||||
for i = 1, table.getn(AURA_DEFS) do
|
||||
def = AURA_DEFS[i]
|
||||
|
||||
if def.spellIDs then
|
||||
for j = 1, table.getn(def.spellIDs) do
|
||||
if def.spellIDs[j] == spellID then return true end
|
||||
end
|
||||
end
|
||||
|
||||
if def.durations and def.durations[spellID] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function SpellName(spellID)
|
||||
if not spellID or not SpellInfo then return "?" end
|
||||
local name = SpellInfo(spellID)
|
||||
return name or "?"
|
||||
end
|
||||
|
||||
local function AddEntry(kind, spellID, spellName, guid, trigger, confidence)
|
||||
if not spellID then return end
|
||||
|
||||
local key = tostring(kind) .. ":" .. tostring(spellID)
|
||||
local data = BNP.unknownBeta
|
||||
local old = data.entries[key]
|
||||
|
||||
if old then
|
||||
old.count = (old.count or 1) + 1
|
||||
old.lastSeen = GetTime()
|
||||
if confidence == "TARGET" then old.confidence = confidence end
|
||||
if trigger and not old.trigger then old.trigger = trigger end
|
||||
return
|
||||
end
|
||||
|
||||
local entry = {
|
||||
kind = kind,
|
||||
spellID = spellID,
|
||||
name = spellName or SpellName(spellID),
|
||||
class = playerClass or "?",
|
||||
guid = guid,
|
||||
trigger = trigger,
|
||||
confidence = confidence or "?",
|
||||
count = 1,
|
||||
firstSeen = GetTime(),
|
||||
lastSeen = GetTime(),
|
||||
}
|
||||
|
||||
data.entries[key] = entry
|
||||
table.insert(data.order, key)
|
||||
end
|
||||
|
||||
local function RememberCast(spellID, targetGUID, eventType)
|
||||
local data = BNP.unknownBeta
|
||||
local now = GetTime()
|
||||
data.activeUntil = now + 8.0
|
||||
local cast = {
|
||||
time = now,
|
||||
spellID = spellID,
|
||||
name = SpellName(spellID),
|
||||
targetGUID = targetGUID,
|
||||
eventType = eventType,
|
||||
}
|
||||
|
||||
table.insert(data.recentCasts, cast)
|
||||
while table.getn(data.recentCasts) > 20 do
|
||||
table.remove(data.recentCasts, 1)
|
||||
end
|
||||
|
||||
-- Unknown own CAST/CHANNEL IDs are useful too. Some trap/proc systems expose
|
||||
-- the effect as a separate cast event before the aura is seen.
|
||||
if spellID and not IsExplicitKnownID(spellID) then
|
||||
AddEntry("CAST", spellID, cast.name, targetGUID, nil, "OWN")
|
||||
end
|
||||
end
|
||||
|
||||
local function FindRecentTrigger(guid, now)
|
||||
local casts = BNP.unknownBeta.recentCasts
|
||||
local i
|
||||
for i = table.getn(casts), 1, -1 do
|
||||
local cast = casts[i]
|
||||
local age = now - cast.time
|
||||
|
||||
if age > 6 then
|
||||
break
|
||||
end
|
||||
|
||||
if cast.targetGUID and guid and cast.targetGUID == guid then
|
||||
return cast, "TARGET"
|
||||
end
|
||||
|
||||
-- Short global fallback catches effects such as traps where the placement
|
||||
-- cast may not carry the eventual victim GUID.
|
||||
if age <= 1.5 then
|
||||
return cast, "RECENT"
|
||||
end
|
||||
end
|
||||
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local function ScanUnit(unit, guid, now)
|
||||
if not unit or not guid then return end
|
||||
|
||||
local seen = BNP.unknownBeta.seenByGUID[guid]
|
||||
if not seen then
|
||||
seen = {}
|
||||
BNP.unknownBeta.seenByGUID[guid] = seen
|
||||
end
|
||||
|
||||
local current = {}
|
||||
local i
|
||||
for i = 1, 32 do
|
||||
local texture, stacks, dtype, auraSpellID = UnitDebuff(unit, i)
|
||||
if not texture then break end
|
||||
|
||||
if auraSpellID then
|
||||
current[auraSpellID] = true
|
||||
|
||||
if not seen[auraSpellID] and not IsExplicitKnownID(auraSpellID) then
|
||||
local trigger, confidence = FindRecentTrigger(guid, now)
|
||||
if trigger then
|
||||
AddEntry(
|
||||
"AURA",
|
||||
auraSpellID,
|
||||
SpellName(auraSpellID),
|
||||
guid,
|
||||
tostring(trigger.name) .. " [" .. tostring(trigger.spellID) .. "]",
|
||||
confidence
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Keep only currently present aura IDs so a later re-application can be
|
||||
-- detected again without growing this table forever.
|
||||
BNP.unknownBeta.seenByGUID[guid] = current
|
||||
end
|
||||
|
||||
|
||||
-- Indirect Effect Audit: traps/procs may never expose a UnitDebuff ID.
|
||||
BNP.unknownBeta.combat = BNP.unknownBeta.combat or {}
|
||||
local EFFECT_WINDOW = 8.0
|
||||
|
||||
|
||||
local function IsUsefulEffectLine(raw)
|
||||
if not raw or raw == "" then return false end
|
||||
local lower = string.lower(raw)
|
||||
|
||||
-- Ignore obvious player-action/noise lines.
|
||||
if string.find(lower, "you cast ", 1, true) then return false end
|
||||
if string.find(lower, "auto shot", 1, true) then return false end
|
||||
if string.find(lower, "login", 1, true) then return false end
|
||||
|
||||
-- Keep lines that actually describe a harmful aura/effect or its periodic damage.
|
||||
if string.find(lower, "afflicted by", 1, true) then return true end
|
||||
if string.find(lower, "is afflicted", 1, true) then return true end
|
||||
if string.find(lower, "is affected by", 1, true) then return true end
|
||||
if string.find(lower, "suffers", 1, true) then return true end
|
||||
if string.find(lower, "periodic", 1, true) then return true end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function AddCombatEffect(raw)
|
||||
if not IsUsefulEffectLine(raw) then return end
|
||||
local now=GetTime()
|
||||
local casts=BNP.unknownBeta.recentCasts
|
||||
local trigger=nil
|
||||
local i
|
||||
for i=table.getn(casts),1,-1 do
|
||||
local c=casts[i]
|
||||
if now-c.time > EFFECT_WINDOW then break end
|
||||
if c.spellID ~= 75 then trigger=c break end
|
||||
end
|
||||
if not trigger then return end
|
||||
if string.find(string.lower(raw),"auto shot",1,true) then return end
|
||||
|
||||
local key=tostring(trigger.spellID)..":"..raw
|
||||
local old=BNP.unknownBeta.combat[key]
|
||||
if old then old.count=old.count+1 return end
|
||||
BNP.unknownBeta.combat[key]={
|
||||
raw=raw, triggerID=trigger.spellID, triggerName=trigger.name, count=1
|
||||
}
|
||||
end
|
||||
|
||||
local combatFrame=CreateFrame("Frame")
|
||||
local combatEvents={
|
||||
"CHAT_MSG_COMBAT_SELF_HITS",
|
||||
"CHAT_MSG_SPELL_SELF_DAMAGE",
|
||||
"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
|
||||
|
||||
local eventType = arg3
|
||||
if eventType ~= "CAST" and eventType ~= "CHANNEL" then return end
|
||||
|
||||
RememberCast(arg4, arg2, eventType)
|
||||
end)
|
||||
|
||||
local scanner = CreateFrame("Frame")
|
||||
local elapsed = 0
|
||||
scanner:SetScript("OnUpdate", function()
|
||||
elapsed = elapsed + arg1
|
||||
if elapsed < 0.35 then return end
|
||||
elapsed = 0
|
||||
|
||||
local now = GetTime()
|
||||
if not BNP.unknownBeta.activeUntil or now > BNP.unknownBeta.activeUntil then
|
||||
return
|
||||
end
|
||||
|
||||
local targetGUID = GetUnitGUID("target")
|
||||
if targetGUID then
|
||||
ScanUnit("target", targetGUID, now)
|
||||
end
|
||||
|
||||
local plate
|
||||
for plate in pairs(BNP.plates) do
|
||||
if plate:IsShown() and plate.GetName then
|
||||
local token = plate:GetName(1)
|
||||
if token then
|
||||
local guid = GetUnitGUID(token)
|
||||
if guid then
|
||||
ScanUnit(token, guid, now)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function BNP:PrintUnknownBeta(verbose)
|
||||
local data = self.unknownBeta
|
||||
self:Print("=== BNP Beta: Unknown Auras (" .. tostring(playerClass or "?") .. ") ===")
|
||||
|
||||
local auraCount = 0
|
||||
local i
|
||||
for i = 1, table.getn(data.order) do
|
||||
local entry = data.entries[data.order[i]]
|
||||
if entry and entry.kind == "AURA" then
|
||||
auraCount = auraCount + 1
|
||||
local line =
|
||||
tostring(auraCount) ..
|
||||
" | AURA | ID " .. tostring(entry.spellID) ..
|
||||
" | " .. tostring(entry.name) ..
|
||||
" | x" .. tostring(entry.count or 1)
|
||||
|
||||
if entry.confidence then
|
||||
line = line .. " | " .. tostring(entry.confidence)
|
||||
end
|
||||
|
||||
self:Print(line)
|
||||
|
||||
if entry.trigger then
|
||||
self:Print(" Trigger: " .. tostring(entry.trigger))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local combatCount = 0
|
||||
local ck
|
||||
for ck in pairs(data.combat or {}) do combatCount = combatCount + 1 end
|
||||
|
||||
if combatCount > 0 then
|
||||
self:Print("--- Indirect Effects ---")
|
||||
local key, e
|
||||
for key, e in pairs(data.combat) do
|
||||
self:Print("EFFECT | Trigger " .. tostring(e.triggerName) .. " [" .. tostring(e.triggerID) .. "] | x" .. tostring(e.count))
|
||||
self:Print(" " .. tostring(e.raw))
|
||||
end
|
||||
end
|
||||
|
||||
if auraCount == 0 and combatCount == 0 then
|
||||
self:Print("No unknown negative auras/effects found since login.")
|
||||
end
|
||||
|
||||
if verbose then
|
||||
self:Print("--- Verbose: unknown player cast IDs ---")
|
||||
local castCount = 0
|
||||
for i = 1, table.getn(data.order) do
|
||||
local entry = data.entries[data.order[i]]
|
||||
if entry and entry.kind == "CAST" then
|
||||
castCount = castCount + 1
|
||||
self:Print(
|
||||
tostring(castCount) ..
|
||||
" | CAST | ID " .. tostring(entry.spellID) ..
|
||||
" | " .. tostring(entry.name) ..
|
||||
" | x" .. tostring(entry.count or 1)
|
||||
)
|
||||
end
|
||||
end
|
||||
if castCount == 0 then
|
||||
self:Print("No unknown cast IDs.")
|
||||
end
|
||||
else
|
||||
self:Print("A screenshot of this output is enough. Raw data: /bnp unknown verbose")
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:ResetUnknownBeta()
|
||||
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.")
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Target Aura Probe ----------------------------------------------------------
|
||||
-- Diagnostic helper for proc/talent auras that the normal unknown collector
|
||||
-- may miss because they were already present in its seen-state.
|
||||
function BNP:PrintTargetAuraProbe()
|
||||
local exists, guid = UnitExists("target")
|
||||
if not exists or not guid then
|
||||
self:Print("Target Aura Probe: no target selected.")
|
||||
return
|
||||
end
|
||||
|
||||
self:Print("=== BNP Target Negative Auras ===")
|
||||
self:Print("Target: " .. tostring(UnitName("target") or "?") .. " | GUID " .. tostring(guid))
|
||||
|
||||
local count = 0
|
||||
local i
|
||||
for i = 1, 32 do
|
||||
local texture, stacks, dtype, auraSpellID = UnitDebuff("target", i)
|
||||
if not texture then break end
|
||||
|
||||
count = count + 1
|
||||
local name = auraSpellID and SpellName(auraSpellID) or "?"
|
||||
self:Print(
|
||||
tostring(count) ..
|
||||
" | ID " .. tostring(auraSpellID or "?") ..
|
||||
" | " .. tostring(name) ..
|
||||
" | stacks " .. tostring(stacks or 0) ..
|
||||
" | type " .. tostring(dtype or "?")
|
||||
)
|
||||
end
|
||||
|
||||
if count == 0 then
|
||||
self:Print("No negative target auras found.")
|
||||
end
|
||||
end
|
||||
|
||||
-- Direct player spell collector --------------------------------------------
|
||||
-- Captures successful player spell events that do not create a persistent
|
||||
-- aura and therefore never appear in the normal unknown-aura collector.
|
||||
|
||||
local function GetLocalPlayerGUID()
|
||||
local exists, guid = UnitExists("player")
|
||||
if exists and guid then return guid end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function RememberDirectEvent(eventType, spellID, targetGUID, extra)
|
||||
if not spellID then return end
|
||||
|
||||
local name = nil
|
||||
if SpellInfo then name = SpellInfo(spellID) end
|
||||
name = name or ("Spell " .. tostring(spellID))
|
||||
|
||||
local key = tostring(eventType) .. ":" .. tostring(spellID)
|
||||
local entry = BNP.unknownDirectEvents[key]
|
||||
|
||||
if not entry then
|
||||
entry = {
|
||||
eventType = eventType,
|
||||
spellID = spellID,
|
||||
name = name,
|
||||
targetGUID = targetGUID,
|
||||
count = 0,
|
||||
extra = extra,
|
||||
last = GetTime(),
|
||||
}
|
||||
BNP.unknownDirectEvents[key] = entry
|
||||
end
|
||||
|
||||
entry.count = (entry.count or 0) + 1
|
||||
entry.targetGUID = targetGUID or entry.targetGUID
|
||||
entry.extra = extra or entry.extra
|
||||
entry.last = GetTime()
|
||||
end
|
||||
|
||||
local directFrame = CreateFrame("Frame")
|
||||
directFrame:RegisterEvent("UNIT_CASTEVENT")
|
||||
directFrame:SetScript("OnEvent", function()
|
||||
local casterGUID = arg1
|
||||
local targetGUID = arg2
|
||||
local eventType = arg3
|
||||
local spellID = arg4
|
||||
local playerGUID = GetLocalPlayerGUID()
|
||||
|
||||
if not playerGUID or casterGUID ~= playerGUID then return end
|
||||
if not spellID then return end
|
||||
|
||||
if eventType == "START" or eventType == "CAST" or eventType == "CHANNEL" then
|
||||
RememberDirectEvent(eventType, spellID, targetGUID, arg5)
|
||||
end
|
||||
end)
|
||||
|
||||
function BNP:PrintUnknownDirectEvents()
|
||||
local list = {}
|
||||
local key, entry
|
||||
|
||||
for key, entry in pairs(self.unknownDirectEvents or {}) do
|
||||
table.insert(list, entry)
|
||||
end
|
||||
|
||||
table.sort(list, function(a, b)
|
||||
return (a.last or 0) > (b.last or 0)
|
||||
end)
|
||||
|
||||
if table.getn(list) == 0 then
|
||||
self:Print("--- Direct Player Events: none captured ---")
|
||||
return
|
||||
end
|
||||
|
||||
self:Print("--- Direct Player Events ---")
|
||||
|
||||
local i, e
|
||||
for i = 1, table.getn(list) do
|
||||
e = list[i]
|
||||
self:Print(
|
||||
tostring(i) .. " | " ..
|
||||
tostring(e.eventType) .. " | ID " ..
|
||||
tostring(e.spellID) .. " | " ..
|
||||
tostring(e.name) .. " | x" ..
|
||||
tostring(e.count or 1)
|
||||
)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user