initial commit
This commit is contained in:
472
Modules/BuffEndingAnnouncer/BuffEndingAnnouncer.lua
Normal file
472
Modules/BuffEndingAnnouncer/BuffEndingAnnouncer.lua
Normal file
@@ -0,0 +1,472 @@
|
||||
-- Modules/BuffEndingAnnouncer/BuffEndingAnnouncer.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("BuffEndingAnnouncer")
|
||||
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.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
|
||||
409
Modules/BuffEndingAnnouncer/BuffEndingAnnouncerOptions.lua
Normal file
409
Modules/BuffEndingAnnouncer/BuffEndingAnnouncerOptions.lua
Normal file
@@ -0,0 +1,409 @@
|
||||
-- Modules/BuffEndingAnnouncer/BuffEndingAnnouncerOptions.lua
|
||||
|
||||
local ADDON_NAME = "HailMaryGuildTools"
|
||||
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||
if not HMGT then return end
|
||||
local BEA = 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["BEA_NAME"] or "Buff Ending Announcer",
|
||||
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.buffEndingAnnouncer", function()
|
||||
return {
|
||||
path = "announcer",
|
||||
order = 3,
|
||||
group = BEA:GetOptionsGroup(),
|
||||
}
|
||||
end)
|
||||
Reference in New Issue
Block a user