Files
coa-weakauras/WeakAuras/Libs/LibGetFrame-1.0/LibGetFrame-1.0.lua
T
2025-02-15 14:50:44 +01:00

730 lines
20 KiB
Lua

local MAJOR_VERSION = "LibGetFrame-1.0"
local MINOR_VERSION = 63
if not LibStub then
error(MAJOR_VERSION .. " requires LibStub.")
end
local lib = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
if not lib then
return
end
lib.callbacks = lib.callbacks or LibStub("CallbackHandler-1.0"):New(lib)
lib.timer = lib.timer or LibStub("AceTimer-3.0")
local callbacks = lib.callbacks
local GetPlayerInfoByGUID, UnitExists, UnitIsUnit, SecureButton_GetUnit, IsAddOnLoaded =
GetPlayerInfoByGUID, UnitExists, UnitIsUnit, SecureButton_GetUnit, IsAddOnLoaded
local tinsert, CopyTable, wipe = tinsert, CopyTable, wipe
function lib.Mixin(object, ...)
for i = 1, select("#", ...) do
local mixin = select(i, ...);
for k, v in pairs(mixin) do
object[k] = v;
end
end
return object;
end
local maxDepth = 50
local defaultFramePriorities = {
-- raid frames
"^Vd1", -- vuhdo
"^Vd2", -- vuhdo
"^Vd3", -- vuhdo
"^Vd4", -- vuhdo
"^Vd5", -- vuhdo
"^Vd", -- vuhdo
"^HealBot_HealUnit", -- healbot
"^hbPet_HealUnit", -- healbot
"^HealBot", -- healbot
"^GridLayout", -- grid
"^Grid2Layout", -- grid2
"^NugRaid%d+UnitButton%d+", -- Aptechka
"^PlexusLayout", -- plexus
"^ElvUF_Raid%d*Group", -- elv
"^ElvUF_RaidGroup", -- elv
"^oUF_bdGrid", -- bdgrid
"^oUF_.-Raid", -- generic oUF
"^LimeGroup", -- lime
"^InvenRaidFrames3Group%dUnitButton", -- InvenRaidFrames3
"^SUFHeaderraid", -- suf
"^LUFHeaderraid", -- luf
"^AshToAshUnit%d+Unit%d+", -- AshToAsh
"^Cell", -- Cell
-- party frames
"^AleaUI_GroupHeader", -- Alea
"^SUFHeaderparty", -- suf
"^LUFHeaderparty", -- luf
"^ElvUF_PartyGroup", -- elv
"^oUF_.-Party", -- generic oUF
"^PitBull4_Groups_Party", -- pitbull4
"^CompactRaid", -- blizz
"^CompactParty", -- blizz
"^PartyFrame", -- blizz
"^PartyMemberFrame", -- blizz
-- player frame
"^InvenUnitFrames_Player",
"^SUFUnitplayer",
"^LUFUnitplayer",
"^PitBull4_Frames_Player",
"^ElvUF_Player",
"^oUF_.-Player",
"^PlayerFrame",
}
local getDefaultFramePriorities = function()
return CopyTable(defaultFramePriorities)
end
lib.getDefaultFramePriorities = getDefaultFramePriorities
local defaultPlayerFrames = {
"^InvenUnitFrames_Player",
"SUFUnitplayer",
"LUFUnitplayer",
"PitBull4_Frames_Player",
"ElvUF_Player",
"oUF_.-Player",
"oUF_PlayerPlate",
"PlayerFrame",
}
local getDefaultPlayerFrames = function()
return CopyTable(defaultPlayerFrames)
end
lib.getDefaultPlayerFrames = getDefaultPlayerFrames
local defaultTargetFrames = {
"^InvenUnitFrames_Target",
"SUFUnittarget",
"LUFUnittarget",
"PitBull4_Frames_Target",
"ElvUF_Target",
"oUF_.-Target",
"TargetFrame",
"^hbExtra_HealUnit",
}
local getDefaultTargetFrames = function()
return CopyTable(defaultTargetFrames)
end
lib.getDefaultTargetFrames = getDefaultTargetFrames
local defaultTargettargetFrames = {
"^InvenUnitFrames_TargetTarget",
"SUFUnittargetarget",
"LUFUnittargetarget",
"PitBull4_Frames_Target's target",
"ElvUF_TargetTarget",
"oUF_.-TargetTarget",
"oUF_ToT",
"TargetTargetFrame",
}
local getDefaultTargettargetFrames = function()
return CopyTable(defaultTargettargetFrames)
end
lib.getDefaultTargettargetFrames = getDefaultTargettargetFrames
local defaultPartyFrames = {
"^InvenUnitFrames_Party%d",
"^AleaUI_GroupHeader",
"^SUFHeaderparty",
"^LUFHeaderparty",
"^ElvUF_PartyGroup",
"^oUF_.-Party",
"^PitBull4_Groups_Party",
"^PartyFrame",
"^CompactParty",
"^PartyMemberFrame",
}
local getDefaultPartyFrames = function()
return CopyTable(defaultPartyFrames)
end
lib.getDefaultPartyFrames = getDefaultPartyFrames
local defaultPartyTargetFrames = {
"SUFChildpartytarget%d",
}
local getDefaultPartyTargetFrames = function()
return CopyTable(defaultPartyTargetFrames)
end
lib.getDefaultPartyTargetFrames = getDefaultPartyTargetFrames
local defaultFocusFrames = {
"^InvenUnitFrames_Focus",
"ElvUF_FocusTarget",
"LUFUnitfocus",
"FocusFrame",
"^hbExtra_HealUnit",
}
local getDefaultFocusFrames = function()
return CopyTable(defaultFocusFrames)
end
lib.getDefaultFocusFrames = getDefaultFocusFrames
local defaultRaidFrames = {
"^Vd",
"^HealBot_HealUnit",
"^hbPet_HealUnit",
"^HealBot",
"^GridLayout",
"^Grid2Layout",
"^PlexusLayout",
"^InvenRaidFrames3Group%dUnitButton",
"^ElvUF_Raid%d*Group",
"^ElvUF_RaidGroup",
"^oUF_.-Raid",
"^AshToAsh",
"^Cell",
"^LimeGroup",
"^SUFHeaderraid",
"^LUFHeaderraid",
"^CompactRaid",
"^RaidPullout",
}
local getDefaultRaidFrames = function()
return CopyTable(defaultRaidFrames)
end
lib.getDefaultRaidFrames = getDefaultRaidFrames
--
local CacheMonitorMixin = {}
function CacheMonitorMixin:Init(makeDiff)
self.data = {}
self.cache = {}
if makeDiff then
self.makeDiff = makeDiff
self.added = {}
self.updated = {}
self.removed = {}
end
end
-- fill cache, added, updated
function CacheMonitorMixin:Add(key, ...)
local args = select("#", ...)
if args > 1 then
if self.makeDiff then
if type(self.data[key]) == "table" then
for i = 1, args do
local arg = select(i, ...)
if self.data[key][i] ~= arg then
self.updated[key] = self.data[key]
break
end
end
else
self.added[key] = true
end
end
self.cache[key] = {...}
else
local value = ...
if self.makeDiff then
if self.data[key] ~= value then
if self.data[key] == nil then
self.added[key] = true
else
self.updated[key] = self.data[key]
end
end
end
self.cache[key] = value
end
end
function CacheMonitorMixin:CalcRemoved()
if not self.makeDiff then return end
for key, value in pairs(self.data) do
if self.cache[key] == nil then
self.removed[key] = value
end
end
end
function CacheMonitorMixin:WriteCache()
local tmp = self.data
self.data = self.cache
self.cache = tmp
wipe(self.cache)
end
function CacheMonitorMixin:Reset()
if self.makeDiff then
wipe(self.updated)
wipe(self.removed)
wipe(self.added)
end
end
--
local FrameToFrameName = {} -- frame adress => frame name
local FrameToUnit = {} -- frame adress => unitToken
lib.Mixin(FrameToFrameName, CacheMonitorMixin)
lib.Mixin(FrameToUnit, CacheMonitorMixin)
FrameToFrameName:Init()
FrameToUnit:Init(true)
local profiling = false
local profileData
local function doNothing()
end
local StartProfiling = doNothing
local StopProfiling = doNothing
local function _StartProfiling(id)
if not profileData[id] then
profileData[id] = {}
profileData[id].count = 1
profileData[id].start = debugprofilestop()
profileData[id].elapsed = 0
profileData[id].spike = 0
return
end
if profileData[id].count == 0 then
profileData[id].count = 1
profileData[id].start = debugprofilestop()
else
profileData[id].count = profileData[id].count + 1
end
end
local function _StopProfiling(id)
profileData[id].count = profileData[id].count - 1
if profileData[id].count == 0 then
local elapsed = debugprofilestop() - profileData[id].start
profileData[id].elapsed = profileData[id].elapsed + elapsed
if elapsed > profileData[id].spike then
profileData[id].spike = elapsed
end
end
end
function lib.StartProfile()
if profiling then
print(MAJOR_VERSION, " (StartProfile) Profiling already started")
return false
end
profiling = true
profileData = {}
StartProfiling = _StartProfiling
StopProfiling = _StopProfiling
end
function lib.StopProfile()
if not profiling then
print(MAJOR_VERSION, " (StopProfile) Profiling not running")
return false
end
profiling = false
StartProfiling = doNothing
StopProfiling = doNothing
end
function lib.GetProfileData()
return profileData or {}
end
-- if frame doesn't have a name, try to use the key from it's parent
local function recurseGetName(frame)
local name = frame.GetName and frame:GetName() or nil
if name then
return name
end
local parent = frame.GetParent and frame:GetParent()
if parent then
local parentKey
for key, child in pairs(parent) do
if child == frame then
parentKey = key
break
end
end
if parentKey then
return (recurseGetName(parent) or "") .. "." .. parentKey
end
end
end
--local notAUnitFrameTypeAttribute = {
-- cancelaura = true
--}
local function ScanFrames(depth, frame, ...)
coroutine.yield()
if not frame then
return
end
if depth < maxDepth then
local frameType = frame:GetObjectType()
if frameType == "Frame" or frameType == "Button" then
ScanFrames(depth + 1, frame:GetChildren())
end
if frameType == "Button" then
local typeAttribute = frame:GetAttribute("type")
--if not notAUnitFrameTypeAttribute[typeAttribute] then
local unit = SecureButton_GetUnit(frame)
if unit and frame:IsVisible() then
local name = recurseGetName(frame)
if name then
FrameToFrameName:Add(frame, name)
FrameToUnit:Add(frame, unit)
end
end
--end
end
end
ScanFrames(depth, ...)
end
local status = "ready"
local co
local coroutineFrame = CreateFrame("Frame")
coroutineFrame:Hide()
local function doScanForUnitFrames()
if not coroutineFrame:IsShown() then
status = "scanning"
co = coroutine.create(ScanFrames)
coroutineFrame:Show()
end
end
coroutineFrame:SetScript("OnUpdate", function()
local start = debugprofilestop()
-- Limit to 5ms per frame
StartProfiling("scan frames")
while debugprofilestop() - start < 5 and coroutine.status(co) ~= "dead" do
coroutine.resume(co, 0, UIParent)
end
StopProfiling("scan frames")
if coroutine.status(co) == "dead" then
StartProfiling("callbacks")
FrameToFrameName:WriteCache()
FrameToUnit:CalcRemoved()
FrameToUnit:WriteCache()
StartProfiling("callback GETFRAME_REFRESH")
callbacks:Fire("GETFRAME_REFRESH")
StopProfiling("callback GETFRAME_REFRESH")
-- FrameToUnit
if next(FrameToUnit.added) then
StartProfiling("callback FRAME_UNIT_ADDED")
for frame in pairs(FrameToUnit.added) do
callbacks:Fire("FRAME_UNIT_ADDED", frame, FrameToUnit.data[frame])
end
StopProfiling("callback FRAME_UNIT_ADDED")
end
if next(FrameToUnit.updated) then
StartProfiling("callback FRAME_UNIT_UPDATE")
for frame, previousUnit in pairs(FrameToUnit.updated) do
callbacks:Fire("FRAME_UNIT_UPDATE", frame, FrameToUnit.data[frame], previousUnit)
end
StopProfiling("callback FRAME_UNIT_UPDATE")
end
if next(FrameToUnit.removed) then
StartProfiling("callback FRAME_UNIT_REMOVED")
for frame, unit in pairs(FrameToUnit.removed) do
callbacks:Fire("FRAME_UNIT_REMOVED", frame, unit)
end
StopProfiling("callback FRAME_UNIT_REMOVED")
end
coroutineFrame:Hide()
FrameToFrameName:Reset()
FrameToUnit:Reset()
StopProfiling("callbacks")
if status == "scan_queued" then
doScanForUnitFrames("queued")
else
status = "ready"
end
end
end)
local function ScanForUnitFrames(noDelay)
if status == "ready" then
if noDelay then
doScanForUnitFrames()
else
status = "scan_delay"
lib.timer:ScheduleTimer(function()
doScanForUnitFrames()
end, 1)
end
elseif status == "scanning" then
status = "scan_queued"
end
end
function lib.ScanForUnitFrames()
ScanForUnitFrames(true)
end
local function isFrameFiltered(name, ignoredFrames)
for _, filter in pairs(ignoredFrames) do
if name:find(filter) then
return true
end
end
return false
end
local function GetUnitFrames(target, ignoredFrames)
if not UnitExists(target) then
if type(target) ~= "string" then
return
end
local B = tonumber(target:sub(5, 5), 16)
if B and B % 8 == 0 then
target = select(6, GetPlayerInfoByGUID(target))
else
target = target:gsub(" .*", "")
end
if not UnitExists(target) then
return
end
end
local frames
for frame, frameName in pairs(FrameToFrameName.data) do
local unit = SecureButton_GetUnit(frame)
if unit and UnitIsUnit(unit, target) and not isFrameFiltered(frameName, ignoredFrames) then
frames = frames or {}
frames[frame] = frameName
end
end
return frames
end
local function ElvuiWorkaround(frame)
if IsAddOnLoaded("ElvUI") and frame and frame:GetName() and frame:GetName():find("^ElvUF_") and frame.Health then
return frame.Health
else
return frame
end
end
local function CellGetUnitFrames(target, frames, framePriorities)
if not IsAddOnLoaded("Cell") or not Cell.GetUnitFramesForLGF then
return frames
end
return Cell.GetUnitFramesForLGF(target, frames, framePriorities)
end
local defaultOptions = {
framePriorities = defaultFramePriorities,
ignorePlayerFrame = true,
ignoreTargetFrame = true,
ignoreTargettargetFrame = true,
ignorePartyFrame = false,
ignorePartyTargetFrame = true,
ignoreFocusFrame = true,
ignoreRaidFrame = false,
playerFrames = defaultPlayerFrames,
targetFrames = defaultTargetFrames,
targettargetFrames = defaultTargettargetFrames,
partyFrames = defaultPartyFrames,
partyTargetFrames = defaultPartyTargetFrames,
focusFrames = defaultFocusFrames,
raidFrames = defaultRaidFrames,
ignoreFrames = {
"PitBull4_Frames_Target's target's target",
"ElvUF_PartyGroup%dUnitButton%dTarget",
"RavenButton",
"RavenOverlay",
"AshToAshUnit%d+ShadowGroupHeaderUnitButton%d+",
"InvenUnitFrames_TargetTargetTarget",
"CellQuickCastButton",
},
skipCellOverrides = false,
returnAll = false,
}
local getDefaultOptions = function()
return CopyTable(defaultOptions)
end
lib.getDefaultOptions = getDefaultOptions
local IterateGroupMembers = function(reversed, forceParty)
local unit = (not forceParty and GetNumRaidMembers() > 0) and 'raid' or 'party'
local numGroupMembers = unit == 'party' and GetNumPartyMembers() or GetNumRaidMembers()
local i = reversed and numGroupMembers or (unit == 'party' and 0 or 1)
return function()
local ret
if i == 0 and unit == 'party' then
ret = 'player'
elseif i <= numGroupMembers and i > 0 then
ret = unit .. i
end
i = i + (reversed and -1 or 1)
return ret
end
end
local unitPetState = {} -- track if unit's pet exists
local saveGetUnitFrame
local function fixGetUnitFrameIntegrity()
lib.GetUnitFrame = saveGetUnitFrame
lib.GetFrame = saveGetUnitFrame
if WeakAuras and WeakAuras.GetUnitFrame then
WeakAuras.GetUnitFrame = saveGetUnitFrame
end
end
local GetFramesCacheListener
local function Init(noDelay)
GetFramesCacheListener = CreateFrame("Frame")
GetFramesCacheListener:RegisterEvent("PLAYER_REGEN_DISABLED")
GetFramesCacheListener:RegisterEvent("PLAYER_REGEN_ENABLED")
GetFramesCacheListener:RegisterEvent("PLAYER_ENTERING_WORLD")
GetFramesCacheListener:RegisterEvent("RAID_ROSTER_UPDATE")
GetFramesCacheListener:RegisterEvent("PARTY_MEMBERS_CHANGED")
GetFramesCacheListener:RegisterEvent("UNIT_PET")
GetFramesCacheListener:RegisterEvent("INSTANCE_ENCOUNTER_ENGAGE_UNIT")
GetFramesCacheListener:SetScript("OnEvent", function(self, event, unit, ...)
fixGetUnitFrameIntegrity()
if event == "RAID_ROSTER_UPDATE" or "PARTY_MEMBERS_CHANGED" then
wipe(unitPetState)
for member in IterateGroupMembers() do
unitPetState[member] = UnitExists(member .. "pet") and true or nil
end
end
if event == "UNIT_PET" then
if not (UnitIsUnit("player", unit) or UnitInParty(unit) or UnitInRaid(unit)) then
return
end
-- skip if unit's pet existance has not changed
local exists = UnitExists(unit .. "pet") and true or nil
if unitPetState[unit] == exists then
return
else
unitPetState[unit] = exists
end
end
ScanForUnitFrames(false)
end)
ScanForUnitFrames(noDelay)
end
function lib.GetUnitFrame(target, opt)
if type(GetFramesCacheListener) ~= "table" then
Init(true)
end
opt = opt or {}
setmetatable(opt, { __index = defaultOptions })
if not target then
return
end
local ignoredFrames = CopyTable(opt.ignoreFrames)
if opt.ignorePlayerFrame then
for _, v in pairs(opt.playerFrames) do
tinsert(ignoredFrames, v)
end
end
if opt.ignoreTargetFrame then
for _, v in pairs(opt.targetFrames) do
tinsert(ignoredFrames, v)
end
end
if opt.ignoreTargettargetFrame then
for _, v in pairs(opt.targettargetFrames) do
tinsert(ignoredFrames, v)
end
end
if opt.ignorePartyFrame then
for _, v in pairs(opt.partyFrames) do
tinsert(ignoredFrames, v)
end
end
if opt.ignorePartyTargetFrame then
for _, v in pairs(opt.partyTargetFrames) do
tinsert(ignoredFrames, v)
end
end
if opt.ignoreFocusFrame then
for _, v in pairs(opt.focusFrames) do
tinsert(ignoredFrames, v)
end
end
if opt.ignoreRaidFrame then
for _, v in pairs(opt.raidFrames) do
tinsert(ignoredFrames, v)
end
end
local frames = GetUnitFrames(target, ignoredFrames)
if not (opt.ignoreRaidFrame or opt.skipCellOverrides) then
frames = CellGetUnitFrames(target, frames, opt.framePriorities)
end
if not frames then
return
end
if not opt.returnAll then
for i = 1, #opt.framePriorities do
for frame, frameName in pairs(frames) do
if frameName:find(opt.framePriorities[i]) then
return ElvuiWorkaround(frame)
end
end
end
local next = next
return ElvuiWorkaround(next(frames))
else
for frame in pairs(frames) do
frames[frame] = ElvuiWorkaround(frame)
end
return frames
end
end
saveGetUnitFrame = lib.GetUnitFrame
lib.GetFrame = lib.GetUnitFrame -- compatibility
-- nameplates
function lib.GetUnitNameplate(unit)
if not unit then
return
end
local nameplate = C_NamePlate.GetNamePlateForUnit(unit)
if nameplate then
-- credit to Exality for https://wago.io/explosiveorbs
-- elvui bunny
if nameplate.UnitFrame and nameplate.UnitFrame.Health and nameplate.UnitFrame.Health:IsShown() then
return nameplate.UnitFrame.Health
elseif nameplate.UnitFrame and nameplate.UnitFrame.Name and nameplate.UnitFrame.Name:IsShown() then
return nameplate.UnitFrame.Name
-- elvui someday
elseif nameplate.unitFrame and nameplate.unitFrame.Health and nameplate.unitFrame.Health:IsShown() then
return nameplate.unitFrame.Health
elseif nameplate.unitFrame and nameplate.unitFrame.Name and nameplate.unitFrame.Name:IsShown() then
return nameplate.unitFrame.Name
elseif nameplate.unitFramePlater and nameplate.unitFramePlater.healthBar then
-- plater
-- fallback to default nameplate in case plater is not on screen and uses blizzard default (module disabled, force-blizzard functionality)
return nameplate.unitFramePlater.PlaterOnScreen and nameplate.unitFramePlater.healthBar or (nameplate.UnitFrame and nameplate.UnitFrame.healthBar) or nameplate
elseif nameplate.kui and nameplate.kui.HealthBar then
-- kui
return nameplate.kui.HealthBar
elseif nameplate.extended and nameplate.extended.visual and nameplate.extended.visual.healthbar then
-- tidyplates
return nameplate.extended.visual.healthbar
elseif nameplate.TPFrame and nameplate.TPFrame.visual and nameplate.TPFrame.visual.healthbar then
-- tidyplates: threat plates
return nameplate.TPFrame.visual.healthbar
elseif nameplate.unitFrame and nameplate.unitFrame.Health then
-- bdui nameplates
return nameplate.unitFrame.Health
elseif nameplate.ouf and nameplate.ouf.Health then
-- bdNameplates
return nameplate.ouf.Health
elseif nameplate.slab and nameplate.slab.components and nameplate.slab.components.healthBar and nameplate.slab.components.healthBar.frame then
-- Slab
return nameplate.slab.components.healthBar.frame
elseif nameplate.UnitFrame and nameplate.UnitFrame.healthBar then
-- default
return nameplate.UnitFrame.healthBar
else
return nameplate
end
end
end