Add files via upload

This commit is contained in:
Wurmschwanz
2026-09-30 22:21:38 +02:00
committed by GitHub
parent 060cc4aacc
commit b20b99f279
4 changed files with 275 additions and 68 deletions
+7 -3
View File
@@ -1658,10 +1658,14 @@ function BNP:CreateOptions()
local questPlateIndicators = CreateCheck(totemsPage, "Enable Quest Icons", -368, function()
BNP_DB.questPlateIndicators = this:GetChecked() and true or false
if BNP.RequestQuestPlateObjectiveUpdate and BNP_DB.questPlateIndicators then
BNP:RequestQuestPlateObjectiveUpdate()
if BNP.SetQuestPlateRuntimeEnabled then
BNP:SetQuestPlateRuntimeEnabled(BNP_DB.questPlateIndicators, true)
else
if BNP_DB.questPlateIndicators and BNP.RequestQuestPlateObjectiveUpdate then
BNP:RequestQuestPlateObjectiveUpdate()
end
if BNP.RefreshQuestPlateIndicators then BNP:RefreshQuestPlateIndicators() end
end
if BNP.RefreshQuestPlateIndicators then BNP:RefreshQuestPlateIndicators() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end)
frame.questPlateIndicatorsCheck = questPlateIndicators
+43 -22
View File
@@ -1003,33 +1003,54 @@ end
local targetEvents = CreateFrame("Frame")
targetEvents:RegisterEvent("PLAYER_TARGET_CHANGED")
targetEvents:SetScript("OnEvent", function()
-- Target switches used to synchronously walk every known nameplate here.
-- Every visible plate already runs ApplyTargetAlpha/target visuals from its
-- normal OnUpdate, so that extra full pass duplicated the same work in one
-- event frame and could show up as a short hitch in crowded scenes.
--
-- Keep the switch responsive by refreshing only the old/new target frames
-- immediately. All other plates converge on the very next normal OnUpdate
-- without one large target-change CPU spike. Alpha-zero Target Only plates
-- remain :IsShown(), so their OnUpdate continues to run as well.
local previousTargetPlate = strictTargetCachePlate or classicTargetCachePlate
InvalidateTargetResolution()
local targetGUID = GetTargetGUID()
local _, classicTargetAvailable = GetClassicAPITargetPlate(targetGUID)
local holdUntil = GetTime() + ALPHA_IDENTITY_HOLD_TIME
local plate
local targetOnly = BNP.IsTargetOnlyNameplatesEnabled and
BNP:IsTargetOnlyNameplatesEnabled()
for plate in pairs(BNP.plates or {}) do
if classicTargetAvailable or targetOnly then
-- Target Only is intentionally fail-closed and therefore does not use
-- the legacy full-alpha identity hold during target switches.
plate.BNPAlphaIdentityHoldUntil = nil
ApplyTargetAlpha(plate)
if BNP.MaintainTargetScale then BNP:MaintainTargetScale(plate) end
else
plate.BNPAlphaIdentityHoldUntil = holdUntil
if plate.GetAlpha and plate.SetAlpha and plate:GetAlpha() ~= 1 then
plate:SetAlpha(1)
end
end
ApplyTargetFrameLevel(plate)
ApplyTargetArrows(plate)
ApplyTargetBorderColor(plate)
local currentTargetPlate, classicTargetAvailable = GetClassicAPITargetPlate(targetGUID)
if not currentTargetPlate then
currentTargetPlate = GetStrictTargetPlate(targetGUID)
end
if BNP.RefreshAuraPriorityAlpha then BNP:RefreshAuraPriorityAlpha() end
local function RefreshImmediateTargetPlate(plate)
if not plate or not BNP.plates or not BNP.plates[plate] then return end
if not plate.IsShown or not plate:IsShown() then return end
-- Exact ClassicAPI target identity means no transition hold is needed.
-- On the legacy path preserve the old neutral hold for the former target
-- while projected SuperWoW identity settles.
if classicTargetAvailable or
(BNP.IsTargetOnlyNameplatesEnabled and BNP:IsTargetOnlyNameplatesEnabled()) then
plate.BNPAlphaIdentityHoldUntil = nil
else
plate.BNPAlphaIdentityHoldUntil = GetTime() + ALPHA_IDENTITY_HOLD_TIME
end
ApplyTargetAlpha(plate)
if BNP.MaintainTargetScale then BNP:MaintainTargetScale(plate) end
ApplyTargetGlow(plate)
ApplyTargetArrows(plate)
ApplyTargetBorderColor(plate)
ApplyTargetFrameLevel(plate)
end
RefreshImmediateTargetPlate(previousTargetPlate)
if currentTargetPlate ~= previousTargetPlate then
RefreshImmediateTargetPlate(currentTargetPlate)
end
-- Aura/CC containers already mirror plate alpha from ApplyTargetAlpha and
-- their own renderer refresh. Avoid another synchronous all-plate pass here.
end)
function BNP:RefreshTargetFocus()
+4 -4
View File
@@ -252,11 +252,11 @@ if BNP.libnameplate then
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.
-- Health text is parented directly to the healthbar and therefore follows
-- target frame-level changes automatically. PLAYER_TARGET_CHANGED used to
-- force a synchronous refresh of every plate even though no health value had
-- changed; the regular 0.10s bucketed updater already keeps values current.
local healthEvents = CreateFrame("Frame")
healthEvents:RegisterEvent("PLAYER_TARGET_CHANGED")
healthEvents:RegisterEvent("PLAYER_ENTERING_WORLD")
healthEvents:SetScript("OnEvent", function()
if BNP.RefreshHealthPercent then
+221 -39
View File
@@ -12,11 +12,25 @@ local ICON_SPACING = 5
local MAX_ICONS_PER_PLATE = 8
local questObjectives = {}
local questObjectiveKeys = {}
local questIdCache = {}
local questMonsterSourceCache = {}
local questItemSourceCache = {}
local cachedDependencyName = nil
local questRevision = 0
local updatePending = true
local updatePending = false
local updateDelay = 0.50
local updateElapsed = 0
local questRuntimeEnabled = false
local questCallbacksRegistered = false
local eventFrame
local QuestRuntimeOnUpdate
local QUEST_EVENTS = {
"QUEST_LOG_UPDATE",
"QUEST_WATCH_UPDATE",
"UNIT_QUEST_LOG_CHANGED",
}
local function ClearTable(tbl)
local key
@@ -44,6 +58,21 @@ function BNP:GetQuestPlateDependencyName()
return nil
end
local function ResetQuestDataCaches()
ClearTable(questIdCache)
ClearTable(questMonsterSourceCache)
ClearTable(questItemSourceCache)
end
local function SyncQuestDataDependency()
local dependencyName = BNP:GetQuestPlateDependencyName()
if dependencyName ~= cachedDependencyName then
cachedDependencyName = dependencyName
ResetQuestDataCaches()
end
return dependencyName
end
local function GetQuestIdByTitle(title, level)
if not title then return nil end
local wantedTitle = Lower(title)
@@ -198,13 +227,21 @@ end
local function GetItemDropSource(questId, itemName)
if not questId or not itemName then return nil end
local wanted = Lower(itemName) or ""
local cacheKey = tostring(questId) .. "|" .. wanted
if questItemSourceCache[cacheKey] ~= nil then
return questItemSourceCache[cacheKey] or nil
end
local mobs
if QuestieReady() then mobs = GetDataFromQuestie(questId, itemName) end
if (not mobs or table.getn(mobs) == 0) and pfDB then
if (not mobs or table.getn(mobs) == 0) and PfQuestReady() then
mobs = GetDataFromPfDb(questId, itemName)
end
if mobs and table.getn(mobs) > 0 then return mobs end
return nil
questItemSourceCache[cacheKey] = (mobs and table.getn(mobs) > 0) and mobs or false
return questItemSourceCache[cacheKey] or nil
end
local function GetMonsterDataFromQuestie(questId)
@@ -248,6 +285,22 @@ local function GetMonsterDataFromPfDb(questId)
return nil
end
local function GetMonsterSource(questId)
if not questId then return nil end
if questMonsterSourceCache[questId] ~= nil then
return questMonsterSourceCache[questId] or nil
end
local mobs
if QuestieReady() then mobs = GetMonsterDataFromQuestie(questId) end
if (not mobs or table.getn(mobs) == 0) and PfQuestReady() then
mobs = GetMonsterDataFromPfDb(questId)
end
questMonsterSourceCache[questId] = (mobs and table.getn(mobs) > 0) and mobs or false
return questMonsterSourceCache[questId] or nil
end
local function IsMobMatchObjective(mobName, objectiveName)
local lm = Lower(mobName) or ""
local lo = Lower(objectiveName) or ""
@@ -260,20 +313,55 @@ local function IsMobMatchObjective(mobName, objectiveName)
return false
end
local function AddObjective(mobName, objectiveName, texture, remaining)
local function AddObjective(target, mobName, objectiveName, texture, remaining)
local key = Lower(mobName)
if not key or key == "" then return end
if not questObjectives[key] then questObjectives[key] = {} end
questObjectives[key][objectiveName] = {
if not target[key] then target[key] = {} end
target[key][objectiveName] = {
icon = texture,
text = tostring(remaining),
}
end
local function BuildObjectiveKey(objectives)
if not objectives then return "" end
local parts = {}
local objectiveName, data
for objectiveName, data in pairs(objectives) do
table.insert(parts, tostring(objectiveName) .. "=" .. tostring(data.icon or "") .. ":" .. tostring(data.text or ""))
end
table.sort(parts)
return table.concat(parts, "|")
end
local function BuildObjectiveKeys(objectives)
local keys = {}
local mobKey, mobObjectives
for mobKey, mobObjectives in pairs(objectives) do
keys[mobKey] = BuildObjectiveKey(mobObjectives)
end
return keys
end
local function FindChangedObjectiveKeys(oldKeys, newKeys)
local changed = {}
local key, value
for key, value in pairs(oldKeys) do
if newKeys[key] ~= value then changed[key] = true end
end
for key, value in pairs(newKeys) do
if oldKeys[key] ~= value then changed[key] = true end
end
return changed
end
function BNP:UpdateQuestPlateObjectives()
ClearTable(questObjectives)
-- A dependency may have become ready after an earlier negative lookup.
ClearTable(questIdCache)
if not questRuntimeEnabled or not self:AreQuestPlateIndicatorsEnabled() then return end
-- Quest IDs and source mappings are static. Keep them cached between
-- QUEST_LOG_UPDATE events; only invalidate if the backing dependency changes.
SyncQuestDataDependency()
local nextObjectives = {}
local oldSelection
if GetQuestLogSelection then oldSelection = GetQuestLogSelection() end
@@ -307,10 +395,7 @@ function BNP:UpdateQuestPlateObjectives()
local texture
if objectiveType == "monster" then
if questId and QuestieReady() then mobs = GetMonsterDataFromQuestie(questId) end
if (not mobs or table.getn(mobs) == 0) and questId and pfDB then
mobs = GetMonsterDataFromPfDb(questId)
end
if questId then mobs = GetMonsterSource(questId) end
if mobs and monsterCount > 1 then
local filtered = {}
@@ -336,7 +421,7 @@ function BNP:UpdateQuestPlateObjectives()
if mobs and texture then
local _, mobName
for _, mobName in pairs(mobs) do
AddObjective(mobName, objectiveName, texture, total - current)
AddObjective(nextObjectives, mobName, objectiveName, texture, total - current)
end
end
end
@@ -349,8 +434,16 @@ function BNP:UpdateQuestPlateObjectives()
SelectQuestLogEntry(oldSelection)
end
local nextObjectiveKeys = BuildObjectiveKeys(nextObjectives)
local changedKeys = FindChangedObjectiveKeys(questObjectiveKeys, nextObjectiveKeys)
questObjectives = nextObjectives
questObjectiveKeys = nextObjectiveKeys
questRevision = questRevision + 1
if self.RefreshQuestPlateIndicators then self:RefreshQuestPlateIndicators() end
-- Only touch nameplates whose objective display actually changed. A kill or
-- loot event normally affects one mob type, not every visible nameplate.
if self.RefreshQuestPlateIndicators then self:RefreshQuestPlateIndicators(changedKeys) end
end
local function HideQuestIcons(plate)
@@ -404,7 +497,8 @@ function BNP:UpdateQuestPlateIndicator(plate, force)
local size = self:GetQuestPlateIconSize()
local x = self:GetQuestPlateXOffset()
local y = self:GetQuestPlateYOffset()
local renderKey = tostring(questRevision) .. "|" .. tostring(unitName) .. "|" ..
local objectiveKey = questObjectiveKeys[Lower(unitName)] or ""
local renderKey = tostring(objectiveKey) .. "|" .. tostring(unitName) .. "|" ..
tostring(size) .. "|" .. tostring(x) .. "|" .. tostring(y)
if not force and plate.BNPQuestRenderKey == renderKey then return end
plate.BNPQuestRenderKey = renderKey
@@ -448,50 +542,138 @@ function BNP:UpdateQuestPlateIndicator(plate, force)
end
end
function BNP:RefreshQuestPlateIndicators()
function BNP:RefreshQuestPlateIndicators(changedKeys)
local plate
for plate in pairs(self.plates or {}) do
if plate and plate.IsShown and plate:IsShown() then
self:UpdateQuestPlateIndicator(plate, true)
else
HideQuestIcons(plate)
local shouldUpdate = true
if changedKeys then
local nameRegion = plate and plate.name
local unitName = nameRegion and nameRegion.GetText and nameRegion:GetText()
local mobKey = unitName and Lower(unitName) or nil
shouldUpdate = mobKey and changedKeys[mobKey]
end
if shouldUpdate then
if plate and plate.IsShown and plate:IsShown() then
self:UpdateQuestPlateIndicator(plate, true)
else
HideQuestIcons(plate)
end
end
end
end
function BNP:RequestQuestPlateObjectiveUpdate()
if not questRuntimeEnabled or not self:AreQuestPlateIndicatorsEnabled() then return end
updatePending = true
updateElapsed = 0
end
-- Nameplate integration: reuse BNP's existing 10 Hz plate update rather than
-- scanning WorldFrame a second time just for quest icons.
table.insert(BNP.libnameplate.OnShow, function(plate)
local function SetQuestEventsRegistered(enabled)
if not eventFrame then return end
local i
for i = 1, table.getn(QUEST_EVENTS) do
if enabled then
eventFrame:RegisterEvent(QUEST_EVENTS[i])
else
eventFrame:UnregisterEvent(QUEST_EVENTS[i])
end
end
end
local function RemoveCallback(list, callback)
local i
for i = table.getn(list), 1, -1 do
if list[i] == callback then
table.remove(list, i)
end
end
end
local QuestPlateOnShow = function(plate)
plate.BNPQuestRenderKey = nil
BNP:UpdateQuestPlateIndicator(plate, true)
end)
end
table.insert(BNP.libnameplate.OnUpdate, function(plate)
local QuestPlateOnUpdate = function(plate)
BNP:UpdateQuestPlateIndicator(plate, false)
end)
end
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("VARIABLES_LOADED")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
eventFrame:RegisterEvent("QUEST_LOG_UPDATE")
eventFrame:RegisterEvent("QUEST_WATCH_UPDATE")
eventFrame:RegisterEvent("UNIT_QUEST_LOG_CHANGED")
local function SetQuestNameplateCallbacksRegistered(enabled)
if enabled and not questCallbacksRegistered then
table.insert(BNP.libnameplate.OnShow, QuestPlateOnShow)
table.insert(BNP.libnameplate.OnUpdate, QuestPlateOnUpdate)
questCallbacksRegistered = true
elseif not enabled and questCallbacksRegistered then
RemoveCallback(BNP.libnameplate.OnShow, QuestPlateOnShow)
RemoveCallback(BNP.libnameplate.OnUpdate, QuestPlateOnUpdate)
questCallbacksRegistered = false
end
end
eventFrame:SetScript("OnEvent", function()
BNP:RequestQuestPlateObjectiveUpdate()
end)
function BNP:SetQuestPlateRuntimeEnabled(enabled, rebuild)
enabled = enabled and true or false
eventFrame:SetScript("OnUpdate", function()
if questRuntimeEnabled ~= enabled then
questRuntimeEnabled = enabled
SetQuestEventsRegistered(enabled)
SetQuestNameplateCallbacksRegistered(enabled)
if eventFrame then
eventFrame:SetScript("OnUpdate", enabled and QuestRuntimeOnUpdate or nil)
end
end
if enabled then
if rebuild then
ClearTable(questObjectives)
ClearTable(questObjectiveKeys)
cachedDependencyName = nil
ResetQuestDataCaches()
questRevision = questRevision + 1
end
self:RequestQuestPlateObjectiveUpdate()
if self.RefreshQuestPlateIndicators then self:RefreshQuestPlateIndicators() end
else
updatePending = false
updateElapsed = 0
ClearTable(questObjectives)
ClearTable(questObjectiveKeys)
cachedDependencyName = nil
ResetQuestDataCaches()
questRevision = questRevision + 1
if self.RefreshQuestPlateIndicators then self:RefreshQuestPlateIndicators() end
end
end
function BNP:SyncQuestPlateRuntime(rebuild)
self:SetQuestPlateRuntimeEnabled(self:AreQuestPlateIndicatorsEnabled(), rebuild)
end
QuestRuntimeOnUpdate = function()
if not updatePending then return end
updateElapsed = updateElapsed + arg1
if updateElapsed < updateDelay then return end
updatePending = false
updateElapsed = 0
BNP:UpdateQuestPlateObjectives()
end
eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("VARIABLES_LOADED")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
eventFrame:SetScript("OnEvent", function()
if event == "VARIABLES_LOADED" or event == "PLAYER_LOGIN" or event == "PLAYER_ENTERING_WORLD" then
if BNP:AreQuestPlateIndicatorsEnabled() then
BNP:SetQuestPlateRuntimeEnabled(true, true)
elseif questRuntimeEnabled then
BNP:SetQuestPlateRuntimeEnabled(false, false)
end
return
end
if questRuntimeEnabled then
BNP:RequestQuestPlateObjectiveUpdate()
end
end)