vrtc / chorus (public) (License: CC0) (since 2023-08-12) (hash sha1)
World of Warcraft add-on stub. The overall goal is to create a specialized raid frame.

/src/ChorusAuraFrameTemplate.lua (3af7b6d30c106f3eefdc189a61f37bb255f6f509) (6406 bytes) (mode 100644) (type blob)

--[[--
`ChorusAuraFrameTemplate` handles subsets of unit auras, individual aura
buttons are handled by `ChorusAuraButtonTemplate`.

@submodule chorus
]]

local Chorus = Chorus
local auraButtonRefresh = Chorus.auraButtonRefresh

local SecureButton_GetUnit = Chorus.test.SecureButton_GetUnit or SecureButton_GetUnit

--[[ See `FrameXML/BuffFrame.lua:BUFF_MAX_DISPLAY`. ]]--
local BUFF_MAX_DISPLAY = BUFF_MAX_DISPLAY or 36
--[[ See `FrameXML/BuffFrame.lua:DEBUFF_MAX_DISPLAY`. ]]--
local DEBUFF_MAX_DISPLAY = DEBUFF_MAX_DISPLAY or 36

local auraWeightMap = ChorusAuraWeightMap

--[[--
Query the game for a given aura, then compute the weight for it, that is used
in when aura frame is sorted.

The weight is computed using remaining duration, owner of the aura (player
auras take priority), and special exceptions.

@function getAuraWeight
@see ChorusAuraWeightMap
@string unitDesignation unit as in `function UnitAura`
@int auraIndex sequential number starting from 1 as in `function UnitAura`
@string filter aura filter descriptor, usually either `HELPFUL` or `HARMFUL`, as in `function UnitAura`
@treturn int aura weight in [0,90000], the larger the number the more likely the aura is to be shown first
]]
local function getAuraWeight(unitDesignation, auraIndex, filter)
	local name, _, _, _, _, durationSec, owner = UnitAura(unitDesignation, auraIndex, filter)
	if not name then
		return 0
	end

	local specialPrio = auraWeightMap[name] or 0
	specialPrio = math.min(math.max(1, math.floor(math.abs(specialPrio))), 9)

	local durationIsLimited = 1
	if not durationSec or durationSec < 1 then
		durationIsLimited = 0
	end

	local ownerPrio = 1
	if 'player' == owner then
		ownerPrio = 2
	end

	local weight = 100000 * ownerPrio +
	               10000 * specialPrio +
		       durationIsLimited * (7201 - math.min(durationSec, 7200))
	return math.abs(math.floor(weight))
end

local function getAuraPriorityList(unitDesignation, filter)
	local t = {}
	local i = 0
	while (i < 8192) do
		i = i + 1
		local name = UnitAura(unitDesignation, i, filter)
		if not name then
			break
		end
		t[i] = i
	end

	table.sort(t, function(a, b)
		local p = getAuraWeight(unitDesignation, a, filter)
		local q = getAuraWeight(unitDesignation, b, filter)
		return p > q
	end)

	return t
end

local function auraFrameEventProcessor(self, eventCategory, ...)
	assert(self ~= nil)

	local unitDesignation = SecureButton_GetUnit(self) or 'none'
	assert(unitDesignation ~= nil)
	assert('string' == type(unitDesignation))
	unitDesignation = string.lower(strtrim(unitDesignation))
	assert(string.len(unitDesignation) >= 1)
	assert(string.len(unitDesignation) <= 256)

	--[[-- @fixme Taint here.
	]]

	if 'UNIT_AURA' == tostring(eventCategory) then
		local u = select(1, ...)
		if not UnitIsUnit(unitDesignation, u) then
			return
		end
	end

	assert(eventCategory ~= nil)

	local filter = SecureButton_GetAttribute(self, 'filter')
	assert(filter ~= nil)
	assert('string' == type(filter))
	filter = string.upper(strtrim(filter))
	assert(string.len(filter) >= 1)
	assert(string.len(filter) <= 256)

	local q = getAuraPriorityList(unitDesignation, filter)
	assert(q ~= nil)
	assert('table' == type(q))


	local i = 0
	local t = {self:GetChildren()}
	while (i < #q) do
		i = i + 1
		local b = t[i]
		if not b then
			break
		end
		assert(b ~= nil)

		local k = q[i]
		assert(k ~= nil)
		assert('number' == type(k))
		k = math.floor(math.abs(k))
		assert(k >= 0)
		b.index = k

		--[[ @note Use concrete function instead of `GetScript`
		callback to hopefully improve performance. Additionally, this
		approach helps the code to remain simple and explicit. ]]--

		auraButtonRefresh(b)
	end

	while (i < #t) do
		i = i + 1
		local b = t[i]
		assert(b ~= nil)
		b.index = 0

		auraButtonRefresh(b)
	end
end

local function createEveryAuraButton(auraFrame)
	local self = auraFrame
	assert(self ~= nil)

	local n = self:GetName()
	assert(n ~= nil)

	local max = math.max(BUFF_MAX_DISPLAY or 36, DEBUFF_MAX_DISPLAY or 36) or 36
	local i = 0
	local w = math.max(self:GetWidth(), 30)
	local bmaxh = 30
	local x = 0
	local y = 0
	while (i < max) do
		i = i + 1

		local m = string.format('%sAuraButton%02d', n, i)
		local b = CreateFrame('FRAME', m, self, 'ChorusAuraLargeButtonTemplate')
		b:SetPoint('TOPLEFT', x, -y)
		x = x + b:GetWidth()
		bmaxh = math.max(bmaxh, b:GetHeight())
		if x >= w then
			x = 0
			y = y + bmaxh
		end
	end
end

local function validateAuraWeightMap(t)
	assert(t ~= nil)
	assert('table' == type(t))
	for spellName, weightNumber in pairs(t) do
		assert(spellName ~= nil)
		assert('string' == type(spellName))
		spellName = strtrim(spellName)
		assert(string.len(spellName) >= 1)
		assert(string.len(spellName) <= 256)

		assert(weightNumber ~= nil)
		assert('number' == type(weightNumber))
		assert(weightNumber >= 1)
		assert(weightNumber <= 9)
	end
end

--[[--
Initialize the aura frame. Given no aura buttons were explicitly decalred in an
XML descriptor, then allocate them with a best guess.

@function auraFrameMain
@tparam frame self the aura frame
@return nothing
]]
function Chorus.auraFrameMain(self)
	assert(self ~= nil)

	--[[ `main` function is called on frame load. Frames declared in the
	XML descriptor are allocated before that. It is preferrable to use
	the XML descriptor for performance and separation of concerns. When
	no descendant frames were declared in this manner or another,
	fallback to allocating required aura buttons dynamically. ]]--

	local createAuraButtonsDynamically = nil == self:GetChildren()
	if createAuraButtonsDynamically then
		createEveryAuraButton(self)
	end

	self:RegisterEvent('PARTY_MEMBERS_CHANGED')
	self:RegisterEvent('PLAYER_FOCUS_CHANGED')
	self:RegisterEvent('PLAYER_LOGIN')
	self:RegisterEvent('PLAYER_TARGET_CHANGED')
	self:RegisterEvent('UNIT_AURA')

	--[[ Disable redundant aura updates. ]]--
	local t = {self:GetChildren()}
	local i = 0
	while (i < #t) do
		i = i + 1
		local b = t[i]
		if b then
			b:UnregisterEvent('UNIT_AURA')
		end
	end

	self:SetScript('OnEvent', auraFrameEventProcessor)
	--self:SetScript('OnShow', auraFrameEventProcessor)

	if auraWeightMap then
		validateAuraWeightMap(auraWeightMap)
	else
		print('ChorusAuraFrameTemplate.lua: warning: aura weight map empty or missing')
	end
end

function Chorus.getAuraWeight(...)
	return getAuraWeight(...)
end

function Chorus.getAuraPriorityList(...)
	return getAuraPriorityList(...)
end


Mode Type Size Ref File
100644 blob 35 5c40e6e2862d70b5c51c326a13073a4012ac05c7 .gitignore
100644 blob 3606 f73da473168d1897963fd2e32d89841ca0461ec0 README.adoc
040000 tree - 271296b4bafcaa151458a0192fd313641ca9b409 bin
100644 blob 228 c7dd24afa7d5c2375ff60a91c73623a304b808f9 chorus.toc
040000 tree - 99c99c3cbc641f8954a5be291e61681cb5e74629 conf
040000 tree - efa7258757edf7b888ea13c215e14b497fef8a16 doc
100644 blob 2391 1b0ca1bc25f74a34476360e5a8f14e28767b204e makefile
040000 tree - f16576d6b030728a6fd22869589b1fbc2fa5bef6 src
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/vrtc/chorus

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/vrtc/chorus

Clone this repository using git:
git clone git://git.rocketgit.com/user/vrtc/chorus

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main