feat: Add Personal Auras module for tracking debuffs on the player

- Introduced a new module for Personal Auras that allows players to track selected debuffs on themselves in a movable frame.
- Implemented functionality to manage tracked debuffs, including adding and removing spells.
- Added options for configuring the appearance and behavior of the Personal Auras frame.
- Updated the readme to include information about the new Personal Auras feature.
This commit is contained in:
Torsten Brendgen
2026-04-12 00:04:34 +02:00
parent 01eeae9603
commit 391e581d32
11 changed files with 1034 additions and 5 deletions

View File

@@ -0,0 +1,474 @@
-- Modules/AuraExpiry/AuraExpiry.lua
-- Announces tracked buffs in SAY shortly before they expire.
local ADDON_NAME = "HailMaryGuildTools"
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
if not HMGT then return end
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME, true) or {}
local BEA = HMGT:NewModule("AuraExpiry")
HMGT.AuraExpiry = BEA
HMGT.BuffEndingAnnouncer = BEA
BEA.runtimeEnabled = false
BEA.eventFrame = nil
BEA.ticker = nil
BEA.lastAnnouncedSecond = {}
BEA.recentOwnCastAt = {}
local function NormalizeThreshold(value, fallback)
local threshold = tonumber(value)
if not threshold then
threshold = tonumber(fallback) or 5
end
threshold = math.floor(threshold + 0.5)
if threshold < 1 then threshold = 1 end
if threshold > 30 then threshold = 30 end
return threshold
end
local function GetSpellName(spellId)
local sid = tonumber(spellId)
if not sid or sid <= 0 then return nil end
if C_Spell and type(C_Spell.GetSpellName) == "function" then
local name = C_Spell.GetSpellName(sid)
if type(name) == "string" and name ~= "" then
return name
end
end
if type(GetSpellInfo) == "function" then
local name = GetSpellInfo(sid)
if type(name) == "string" and name ~= "" then
return name
end
end
return nil
end
local function IsAuraSourcePlayer(sourceUnit)
if not sourceUnit then return nil end
if sourceUnit == "player" then return true end
if type(UnitIsUnit) == "function" and type(UnitExists) == "function" and UnitExists(sourceUnit) then
return UnitIsUnit(sourceUnit, "player") and true or false
end
return false
end
local function GetPlayerBuffExpiration(spellId)
local sid = tonumber(spellId)
if not sid or sid <= 0 then return nil, nil, nil, nil end
if C_UnitAuras and type(C_UnitAuras.GetPlayerAuraBySpellID) == "function" then
local aura = C_UnitAuras.GetPlayerAuraBySpellID(sid)
if aura then
local exp = tonumber(aura.expirationTime) or 0
if exp > 0 then
local isOwnCaster = IsAuraSourcePlayer(aura.sourceUnit)
if isOwnCaster == nil and aura.isFromPlayerOrPlayerPet ~= nil then
isOwnCaster = aura.isFromPlayerOrPlayerPet and true or false
end
return exp, tonumber(aura.duration) or 0, aura.name, isOwnCaster
end
end
end
if AuraUtil and type(AuraUtil.FindAuraBySpellID) == "function" then
local name, _, _, _, duration, expirationTime, sourceUnit, _, _, _, _, _, castByPlayer =
AuraUtil.FindAuraBySpellID(sid, "player", "HELPFUL")
local exp = tonumber(expirationTime) or 0
if name and exp > 0 then
local isOwnCaster = IsAuraSourcePlayer(sourceUnit)
if isOwnCaster == nil and castByPlayer ~= nil then
isOwnCaster = castByPlayer and true or false
end
return exp, tonumber(duration) or 0, name, isOwnCaster
end
end
return nil, nil, nil, nil
end
local function GetPlayerChannelExpiration(spellId)
local sid = tonumber(spellId)
if not sid or sid <= 0 then return nil, nil, nil, nil end
if type(UnitChannelInfo) ~= "function" then
return nil, nil, nil, nil
end
local name, _, _, startTimeMS, endTimeMS, _, _, channelSpellId = UnitChannelInfo("player")
local activeSpellId = tonumber(channelSpellId)
if activeSpellId ~= sid then
return nil, nil, nil, nil
end
local startTime = tonumber(startTimeMS) or 0
local endTime = tonumber(endTimeMS) or 0
if endTime <= 0 or endTime <= startTime then
return nil, nil, nil, nil
end
return endTime / 1000, (endTime - startTime) / 1000, name, true
end
local function GetTrackedSpellExpiration(spellId)
local expirationTime, duration, name, isOwnCaster = GetPlayerBuffExpiration(spellId)
if expirationTime and expirationTime > 0 then
return expirationTime, duration, name, isOwnCaster, "aura"
end
expirationTime, duration, name, isOwnCaster = GetPlayerChannelExpiration(spellId)
if expirationTime and expirationTime > 0 then
return expirationTime, duration, name, isOwnCaster, "channel"
end
return nil, nil, nil, nil, nil
end
function BEA:GetSettings()
local p = HMGT.db and HMGT.db.profile
if not p then return nil end
p.buffEndingAnnouncer = p.buffEndingAnnouncer or {}
p.buffEndingAnnouncer.announceAtSec = NormalizeThreshold(p.buffEndingAnnouncer.announceAtSec, 5)
p.buffEndingAnnouncer.trackedBuffs = p.buffEndingAnnouncer.trackedBuffs or {}
return p.buffEndingAnnouncer
end
function BEA:GetDefaultThreshold()
local s = self:GetSettings()
return NormalizeThreshold(s and s.announceAtSec, 5)
end
function BEA:GetTrackedBuffThreshold(spellId)
local sid = tonumber(spellId)
if not sid or sid <= 0 then return nil end
local settings = self:GetSettings()
local tracked = settings and settings.trackedBuffs
local value = tracked and tracked[sid]
if value == nil then
return nil
end
if type(value) == "table" then
value = value.threshold
end
if value == true then
value = self:GetDefaultThreshold()
end
return NormalizeThreshold(value, self:GetDefaultThreshold())
end
function BEA:SetTrackedBuffThreshold(spellId, threshold)
local sid = tonumber(spellId)
if not sid or sid <= 0 then
return false
end
local s = self:GetSettings()
s.trackedBuffs[sid] = NormalizeThreshold(threshold, self:GetDefaultThreshold())
self.lastAnnouncedSecond[sid] = nil
self:Refresh()
return true
end
function BEA:GetTrackedBuffEntries()
local entries = {}
for _, sid in ipairs(self:GetTrackedBuffSpellIds()) do
entries[#entries + 1] = {
spellId = sid,
name = GetSpellName(sid) or ("Spell " .. tostring(sid)),
threshold = self:GetTrackedBuffThreshold(sid) or self:GetDefaultThreshold(),
}
end
return entries
end
function BEA:GetTrackedBuffSpellIds()
local ids = {}
local settings = self:GetSettings()
local tracked = (settings and settings.trackedBuffs) or {}
for sid, value in pairs(tracked) do
local id = tonumber(sid)
if id and id > 0 and value ~= nil and value ~= false then
ids[#ids + 1] = id
end
end
table.sort(ids, function(a, b)
local nameA = tostring(GetSpellName(a) or a):lower()
local nameB = tostring(GetSpellName(b) or b):lower()
if nameA == nameB then
return a < b
end
return nameA < nameB
end)
return ids
end
function BEA:ResetAnnouncements()
for sid in pairs(self.lastAnnouncedSecond) do
self.lastAnnouncedSecond[sid] = nil
end
end
function BEA:ResetRecentOwnCasts()
for sid in pairs(self.recentOwnCastAt) do
self.recentOwnCastAt[sid] = nil
end
end
function BEA:StopTicker()
if self.ticker then
self.ticker:Cancel()
self.ticker = nil
end
end
function BEA:HasTrackedBuffsConfigured()
return #self:GetTrackedBuffSpellIds() > 0
end
function BEA:UpdateRuntimeEventRegistrations()
if not self.eventFrame then
return
end
self.eventFrame:UnregisterEvent("UNIT_AURA")
self.eventFrame:UnregisterEvent("UNIT_SPELLCAST_SUCCEEDED")
self.eventFrame:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_START")
self.eventFrame:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
self.eventFrame:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
if not self.runtimeEnabled or not self:HasTrackedBuffsConfigured() then
return
end
self.eventFrame:RegisterUnitEvent("UNIT_AURA", "player")
self.eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player")
self.eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", "player")
self.eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", "player")
self.eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", "player")
end
function BEA:EnsureTicker()
if self.ticker then return end
self.ticker = C_Timer.NewTicker(0.1, function()
self:OnTicker()
end)
end
function BEA:IsProfileEnabled()
local s = self:GetSettings()
return s and s.enabled == true
end
function BEA:BuildAnnouncement(spellName, secondsLeft)
local template = L["BEA_MSG_TEMPLATE"] or "%s ending in %d"
return string.format(template, tostring(spellName or "?"), tonumber(secondsLeft) or 0)
end
function BEA:IsOwnBuffCaster(spellId, isOwnCaster, duration, now)
if isOwnCaster == true then
return true
end
if isOwnCaster == false then
return false
end
-- Fallback when aura source information is missing:
-- only trust a very recent own cast of the tracked spell.
local castAt = self.recentOwnCastAt[tonumber(spellId)]
if not castAt then
return false
end
local maxAge = tonumber(duration) or 0
if maxAge < 3 then maxAge = 3 end
if maxAge > 12 then maxAge = 12 end
return (tonumber(now) or GetTime()) - castAt <= maxAge
end
function BEA:EvaluateTrackedBuffs()
if not self:IsProfileEnabled() then
self:ResetAnnouncements()
return false
end
local ids = self:GetTrackedBuffSpellIds()
if #ids == 0 then
self:ResetAnnouncements()
return false
end
local now = GetTime()
local hasAnyTrackedBuff = false
for _, sid in ipairs(ids) do
local threshold = self:GetTrackedBuffThreshold(sid)
local expirationTime, duration, auraName, isOwnCaster, stateKind = GetTrackedSpellExpiration(sid)
if expirationTime and expirationTime > now then
local isOwnSource = (stateKind == "channel") or self:IsOwnBuffCaster(sid, isOwnCaster, duration, now)
if isOwnSource then
hasAnyTrackedBuff = true
local remaining = expirationTime - now
if threshold and remaining > 0 and remaining <= threshold then
local second = math.ceil(remaining - 0.0001)
if second < 1 then second = 1 end
if self.lastAnnouncedSecond[sid] ~= second then
local msg = self:BuildAnnouncement(auraName or GetSpellName(sid) or ("Spell " .. tostring(sid)), second)
SendChatMessage(msg, "SAY")
self.lastAnnouncedSecond[sid] = second
end
else
self.lastAnnouncedSecond[sid] = nil
end
else
self.lastAnnouncedSecond[sid] = nil
end
else
self.lastAnnouncedSecond[sid] = nil
end
local castAt = self.recentOwnCastAt[sid]
if castAt and (now - castAt) > 30 then
self.recentOwnCastAt[sid] = nil
end
end
return hasAnyTrackedBuff
end
function BEA:Refresh()
if not self.runtimeEnabled then return end
self:UpdateRuntimeEventRegistrations()
local active = self:EvaluateTrackedBuffs()
if active then
self:EnsureTicker()
else
self:StopTicker()
end
end
function BEA:OnTicker()
if not self.runtimeEnabled then
self:StopTicker()
return
end
local active = self:EvaluateTrackedBuffs()
if not active then
self:StopTicker()
end
end
function BEA:OnEvent(event, ...)
if event == "UNIT_AURA" then
local unit = ...
if unit ~= "player" then
return
end
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
local unit, _, spellId = ...
if unit == "player" then
local sid = tonumber(spellId)
if sid and sid > 0 then
local s = self:GetSettings()
if s and s.trackedBuffs and s.trackedBuffs[sid] then
self.recentOwnCastAt[sid] = GetTime()
end
end
end
elseif event == "UNIT_SPELLCAST_CHANNEL_START" or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
local unit = ...
if unit ~= "player" then
return
end
elseif event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_DEAD" then
self:ResetAnnouncements()
self:ResetRecentOwnCasts()
end
self:Refresh()
end
function BEA:StartRuntime()
if not self:IsProfileEnabled() then return end
if self.runtimeEnabled then
self:Refresh()
return
end
self.runtimeEnabled = true
if not self.eventFrame then
self.eventFrame = CreateFrame("Frame")
self.eventFrame:SetScript("OnEvent", function(_, event, ...)
self:OnEvent(event, ...)
end)
end
self.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
self.eventFrame:RegisterEvent("PLAYER_DEAD")
self:Refresh()
end
function BEA:StopRuntime()
if self.eventFrame then
self.eventFrame:UnregisterEvent("UNIT_AURA")
self.eventFrame:UnregisterEvent("UNIT_SPELLCAST_SUCCEEDED")
self.eventFrame:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_START")
self.eventFrame:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
self.eventFrame:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
self.eventFrame:UnregisterEvent("PLAYER_ENTERING_WORLD")
self.eventFrame:UnregisterEvent("PLAYER_DEAD")
end
self.runtimeEnabled = false
self:StopTicker()
self:ResetAnnouncements()
self:ResetRecentOwnCasts()
end
function BEA:OnInitialize()
HMGT.AuraExpiry = self
HMGT.BuffEndingAnnouncer = self
end
function BEA:OnEnable()
self:StartRuntime()
end
function BEA:OnDisable()
self:StopRuntime()
end
function BEA:AddTrackedBuff(spellId, threshold)
local sid = tonumber(spellId)
if not sid or sid <= 0 then
return false, "invalid"
end
local name = GetSpellName(sid)
if not name then
return false, "invalid"
end
local s = self:GetSettings()
s.trackedBuffs[sid] = NormalizeThreshold(threshold, self:GetDefaultThreshold())
self.lastAnnouncedSecond[sid] = nil
self:Refresh()
return true, name
end
function BEA:RemoveTrackedBuff(spellId)
local sid = tonumber(spellId)
if not sid or sid <= 0 then
return false, "invalid"
end
local s = self:GetSettings()
if not s.trackedBuffs[sid] then
return false, "missing"
end
s.trackedBuffs[sid] = nil
self.lastAnnouncedSecond[sid] = nil
self:Refresh()
return true, GetSpellName(sid) or tostring(sid)
end

View File

@@ -0,0 +1,409 @@
-- Modules/AuraExpiry/AuraExpiryOptions.lua
local ADDON_NAME = "HailMaryGuildTools"
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
if not HMGT then return end
local BEA = HMGT.AuraExpiry or HMGT.BuffEndingAnnouncer
if not BEA then return end
if not HMGT_Config or not HMGT_Config.RegisterOptionsProvider then return end
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME, true) or {}
local AceConfigRegistry = LibStub("AceConfigRegistry-3.0")
local beaOptionsGroup
local function GetSpellName(spellId)
local sid = tonumber(spellId)
if not sid or sid <= 0 then return nil end
if C_Spell and type(C_Spell.GetSpellName) == "function" then
local name = C_Spell.GetSpellName(sid)
if type(name) == "string" and name ~= "" then
return name
end
end
if type(GetSpellInfo) == "function" then
local name = GetSpellInfo(sid)
if type(name) == "string" and name ~= "" then
return name
end
end
return nil
end
local function GetSpellIcon(spellId)
local sid = tonumber(spellId)
if not sid or sid <= 0 then return nil end
if HMGT_SpellData and type(HMGT_SpellData.GetSpellIcon) == "function" then
return HMGT_SpellData.GetSpellIcon(sid)
end
if C_Spell and type(C_Spell.GetSpellTexture) == "function" then
return C_Spell.GetSpellTexture(sid)
end
local _, _, icon = GetSpellInfo(sid)
return icon
end
local function RefreshTrackedGroup()
if not beaOptionsGroup or type(beaOptionsGroup.args) ~= "table" then
return
end
local fresh = BEA:BuildOptionsGroup()
local currentTracked = beaOptionsGroup.args.tracked
local freshTracked = fresh and fresh.args and fresh.args.tracked
if type(currentTracked) == "table" and type(freshTracked) == "table" then
currentTracked.name = freshTracked.name
currentTracked.order = freshTracked.order
currentTracked.inline = freshTracked.inline
currentTracked.childGroups = freshTracked.childGroups
currentTracked.args = freshTracked.args
end
end
local function NotifyOptionsChanged(rebuild)
if rebuild ~= false then
RefreshTrackedGroup()
end
AceConfigRegistry:NotifyChange(ADDON_NAME)
end
local function GetFilterState()
HMGT._beaSpellFilter = HMGT._beaSpellFilter or {
search = "",
}
return HMGT._beaSpellFilter
end
local function NormalizeSearchText(value)
local text = tostring(value or ""):lower()
text = text:gsub("^%s+", "")
text = text:gsub("%s+$", "")
return text
end
local function GetDraft()
HMGT._beaDraft = HMGT._beaDraft or {}
return HMGT._beaDraft
end
local function GetTrackedBuffLabel(entry)
local spellId = tonumber(entry and entry.spellId) or 0
local spellName = tostring(entry and entry.name or GetSpellName(spellId) or ("Spell " .. spellId))
local icon = GetSpellIcon(spellId)
local threshold = tonumber(entry and entry.threshold) or tonumber(BEA:GetDefaultThreshold()) or 0
if icon and icon ~= "" then
return string.format("|T%s:16:16:0:0|t %s (%ss)", tostring(icon), spellName, threshold)
end
return string.format("%s (%ss)", spellName, threshold)
end
local function BuildTrackedBuffLeaf(entry)
local spellId = tonumber(entry and entry.spellId) or 0
return {
type = "group",
name = GetTrackedBuffLabel(entry),
args = {
header = {
type = "header",
order = 1,
name = GetTrackedBuffLabel(entry),
},
spellInfo = {
type = "description",
order = 2,
width = "full",
name = string.format(
"|cffffd100Spell ID|r: %d\n|cffffd100%s|r: %s",
spellId,
L["OPT_BEA_COL_SPELL"] or "Spellname",
tostring(entry and entry.name or GetSpellName(spellId) or ("Spell " .. spellId))
),
},
threshold = {
type = "range",
order = 3,
min = 1,
max = 60,
step = 1,
width = "full",
name = L["OPT_BEA_COL_THRESHOLD"] or "Threshold",
get = function()
local current = BEA:GetTrackedBuffEntries()
for _, candidate in ipairs(current) do
if tonumber(candidate.spellId) == spellId then
return tonumber(candidate.threshold) or BEA:GetDefaultThreshold()
end
end
return tonumber(BEA:GetDefaultThreshold()) or 5
end,
set = function(_, val)
BEA:SetTrackedBuffThreshold(spellId, val)
NotifyOptionsChanged()
end,
},
remove = {
type = "execute",
order = 4,
width = "full",
name = REMOVE or (L["OPT_BEA_REMOVE"] or "Remove buff"),
confirm = function()
return string.format("%s?", tostring(entry and entry.name or GetSpellName(spellId) or ("Spell " .. spellId)))
end,
func = function()
local ok, info = BEA:RemoveTrackedBuff(spellId)
if ok then
HMGT:Print(string.format(L["OPT_BEA_MSG_REMOVED"] or "HMGT: buff removed: %s", tostring(info or spellId)))
else
HMGT:Print(L["OPT_BEA_MSG_NOT_FOUND"] or "HMGT: buff not found")
end
NotifyOptionsChanged()
end,
},
},
}
end
local function IsTrackedBuffVisible(entry)
local spellId = tonumber(entry and entry.spellId) or 0
if spellId <= 0 then
return false
end
local search = NormalizeSearchText(GetFilterState().search)
if search == "" then
return true
end
local haystack = table.concat({
tostring(entry and entry.name or GetSpellName(spellId) or ""),
tostring(spellId),
}, " "):lower()
return haystack:find(search, 1, true) ~= nil
end
local function CountVisibleEntries(entries)
local count = 0
for _, entry in ipairs(entries or {}) do
if IsTrackedBuffVisible(entry) then
count = count + 1
end
end
return count
end
function BEA:BuildOptionsGroup()
local draft = GetDraft()
local entries = self:GetTrackedBuffEntries()
local group = {
type = "group",
name = L["AE_NAME"] or L["BEA_NAME"] or "Aura Expiry",
order = 3,
childGroups = "tree",
args = {
general = {
type = "group",
order = 1,
name = L["OPT_BEA_SECTION_GENERAL"] or "General",
args = {
enabled = {
type = "toggle",
order = 1,
width = "full",
name = L["OPT_BEA_ENABLED"] or "Enable buff ending announcer",
desc = L["OPT_BEA_ENABLED_DESC"] or "Announce tracked buff countdowns in /say",
get = function()
return self:GetSettings().enabled == true
end,
set = function(_, val)
self:GetSettings().enabled = val and true or false
if val then
self:Enable()
else
self:Disable()
end
end,
},
defaultThreshold = {
type = "range",
order = 2,
min = 1,
max = 60,
step = 1,
width = "full",
name = L["OPT_BEA_DEFAULT_THRESHOLD"] or "Default threshold (sec)",
desc = L["OPT_BEA_DEFAULT_THRESHOLD_DESC"] or "Used when you add a new tracked buff",
get = function()
return tonumber(self:GetDefaultThreshold()) or 5
end,
set = function(_, val)
self:GetSettings().announceAtSec = math.floor((tonumber(val) or 5) + 0.5)
self:Refresh()
end,
},
},
},
tracked = {
type = "group",
order = 2,
name = string.format(
"%s (%d)",
L["OPT_BEA_SECTION_BUFFS"] or "Tracked buffs",
#entries
),
args = {
header = {
type = "header",
order = 1,
name = L["OPT_SPELL_BROWSER"] or "Spell Browser",
},
summary = {
type = "description",
order = 2,
width = "full",
name = function()
local currentEntries = BEA:GetTrackedBuffEntries()
return string.format(
"%s\n\n|cffffd100%s|r: %d\n|cffffd100%s|r: %d",
L["OPT_SPELL_BROWSER_DESC"] or "Filter tracked spells by name or Spell ID and apply quick actions to the visible results.",
L["OPT_SPELLS_VISIBLE"] or "Visible spells",
CountVisibleEntries(currentEntries),
L["OPT_BEA_SECTION_BUFFS"] or "Tracked buffs",
#currentEntries
)
end,
},
search = {
type = "input",
order = 3,
width = "full",
name = L["OPT_FILTER_SEARCH"] or "Search",
desc = L["OPT_FILTER_SEARCH_DESC"] or "Search by spell name or Spell ID",
get = function()
return GetFilterState().search or ""
end,
set = function(_, val)
GetFilterState().search = val or ""
NotifyOptionsChanged(false)
end,
},
resetFilters = {
type = "execute",
order = 4,
width = "full",
name = L["OPT_FILTER_RESET"] or "Reset filters",
func = function()
GetFilterState().search = ""
NotifyOptionsChanged(false)
end,
},
addGroup = {
type = "group",
order = 5,
inline = true,
name = L["OPT_BEA_CURRENT"] or "Current tracked buffs",
args = {
addSpellId = {
type = "input",
order = 1,
width = 0.9,
name = L["OPT_BEA_ADD_ID"] or "Add Spell ID",
get = function()
return tostring(draft.addSpellId or "")
end,
set = function(_, val)
draft.addSpellId = val
end,
},
addThreshold = {
type = "range",
order = 2,
min = 1,
max = 60,
step = 1,
width = 1.1,
name = L["OPT_BEA_ADD_THRESHOLD"] or "Threshold",
desc = L["OPT_BEA_ADD_THRESHOLD_DESC"] or "Countdown start in seconds for this buff",
get = function()
return tonumber(draft.addThreshold) or tonumber(self:GetDefaultThreshold()) or 5
end,
set = function(_, val)
draft.addThreshold = tonumber(val) or self:GetDefaultThreshold()
end,
},
addSpell = {
type = "execute",
order = 3,
width = "full",
name = L["OPT_BEA_ADD"] or "Add buff",
func = function()
local sid = tonumber(draft.addSpellId)
local threshold = tonumber(draft.addThreshold) or self:GetDefaultThreshold()
local ok, info = self:AddTrackedBuff(sid, threshold)
if ok then
draft.addSpellId = ""
draft.addThreshold = tonumber(self:GetDefaultThreshold()) or 5
HMGT:Print(string.format(L["OPT_BEA_MSG_ADDED"] or "HMGT: buff added: %s", tostring(info or sid)))
else
HMGT:Print(L["OPT_BEA_MSG_INVALID"] or "HMGT: invalid buff spell ID")
end
NotifyOptionsChanged()
end,
},
},
},
},
},
},
}
if #entries == 0 then
group.args.tracked.args.empty = {
type = "description",
order = 10,
width = "full",
name = L["OPT_BEA_EMPTY"] or "No buffs configured.",
}
else
for index, entry in ipairs(entries) do
local key = "buff_" .. tostring(tonumber(entry.spellId) or index)
local leaf = BuildTrackedBuffLeaf(entry)
leaf.order = 10 + index
leaf.inline = true
leaf.name = " "
leaf.hidden = function()
return not IsTrackedBuffVisible(entry)
end
group.args.tracked.args[key] = leaf
end
group.args.tracked.args.noVisible = {
type = "description",
order = 1000,
width = "full",
hidden = function()
return CountVisibleEntries(BEA:GetTrackedBuffEntries()) > 0
end,
name = L["OPT_BEA_EMPTY"] or "No buffs configured.",
}
end
return group
end
function BEA:GetOptionsGroup()
if not beaOptionsGroup then
beaOptionsGroup = self:BuildOptionsGroup()
else
RefreshTrackedGroup()
end
return beaOptionsGroup
end
HMGT_Config:RegisterOptionsProvider("announcer.auraExpiry", function()
return {
path = "announcer",
order = 3,
group = BEA:GetOptionsGroup(),
}
end)