Files
HailMaryGuildTools/HailMaryGuildToolsOptions.lua
Torsten Brendgen fc5a8aa361 initial commit
2026-04-10 21:30:31 +02:00

2064 lines
96 KiB
Lua

-- Config.lua
-- Hail Mary Guild Tools – AceConfig Optionentabellen
-- Spell-Liste: Icon-Anzeige, gruppiert nach Klasse → Kategorie
local ADDON_NAME = "HailMaryGuildTools"
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
local LSM = LibStub("LibSharedMedia-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
local AceConfigRegistry = LibStub("AceConfigRegistry-3.0", true)
HMGT_Config = {}
HMGT_Config._optionProviders = HMGT_Config._optionProviders or {}
HMGT_Config._settingsCategoryId = HMGT_Config._settingsCategoryId or nil
HMGT_Config._settingsCategoryName = HMGT_Config._settingsCategoryName or nil
function HMGT_Config:RegisterOptionsProvider(id, provider)
if type(id) ~= "string" or id == "" then return false end
if type(provider) ~= "function" then return false end
self._optionProviders[id] = provider
return true
end
function HMGT_Config:UnregisterOptionsProvider(id)
if type(id) ~= "string" or id == "" then return false end
self._optionProviders[id] = nil
return true
end
function HMGT_Config:GetSettingsCategory()
return self._settingsCategoryId or self._settingsCategoryName
end
-- ═══════════════════════════════════════════════════════════════
-- SPELL-TOGGLE-LISTE (Icon + Klassen-/Kategorie-Gruppen)
-- ═══════════════════════════════════════════════════════════════
--- Erstellt das Icon+Name Label für einen Spell
local function SpellLabel(entry)
-- |T path:h:w:xOff:yOff|t → 20×20 Icon vor dem Namen
local icon = HMGT_SpellData.GetSpellIcon(entry.spellId)
return string.format("|T%s:20:20:0:0|t %s", icon, entry.name)
end
--- Erstellt den Tooltip-Text eines Spell-Toggles
local function SpellDesc(entry)
local cooldown = tonumber(HMGT_SpellData.GetBaseCooldown and HMGT_SpellData.GetBaseCooldown(entry)) or tonumber(entry.cooldown) or 0
local cdStr = cooldown > 0
and string.format("%ds", cooldown)
or "–"
local specStr = entry.specs
and ("Spec " .. table.concat(entry.specs, "/"))
or "All Specs"
return string.format("SpellID: %d | CD: %s | %s", entry.spellId, cdStr, specStr)
end
--- Erstellt einen Hex-farbigen Klassen-Header
local function ClassHeader(classToken)
local hex = HMGT_SpellData.ClassColor[classToken] or "ffffffff"
-- Lokalisierter Klassenname (WoW-Globaltabelle)
local displayName = (LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE[classToken])
or classToken
return string.format("|c%s%s|r", hex, displayName)
end
--- Baut die Spell-Toggle-Options eines EINZELNEN Trackers,
--- gruppiert nach Klasse → Kategorie, mit Icons und Select-All-Buttons.
---
--- @param database table HMGT_SpellData.Interrupts / .RaidCooldowns
--- @param dbKey string z.B. "interruptTracker"
--- @return table AceConfig-args-Tabelle
local function BuildSpellOptions(database, dbKey)
-- live helper – nie gecachte Referenz
local function es() return HMGT.db.profile[dbKey].enabledSpells end
if true then
local function filterState()
HMGT._cfgSpellFilters = HMGT._cfgSpellFilters or {}
HMGT._cfgSpellFilters[dbKey] = HMGT._cfgSpellFilters[dbKey] or {
search = "",
enabledOnly = false,
}
return HMGT._cfgSpellFilters[dbKey]
end
local function NormalizeText(value)
local text = tostring(value or ""):lower()
text = text:gsub("^%s+", "")
text = text:gsub("%s+$", "")
return text
end
local function LocalClassName(classToken)
return (LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE[classToken])
or (LOCALIZED_CLASS_NAMES_FEMALE and LOCALIZED_CLASS_NAMES_FEMALE[classToken])
or classToken
end
local function CategoryLabel(category)
return L["CAT_" .. tostring(category)] or tostring(category or "utility")
end
local function SpellNameForId(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 ok, name = pcall(C_Spell.GetSpellName, sid)
if ok and 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 Notify()
if AceConfigRegistry and type(AceConfigRegistry.NotifyChange) == "function" then
AceConfigRegistry:NotifyChange(ADDON_NAME)
end
end
local browserClassSpells = {}
for _, entry in ipairs(database) do
for _, cls in ipairs(entry.classes) do
if not browserClassSpells[cls] then browserClassSpells[cls] = {} end
local cat = entry.category or "utility"
if not browserClassSpells[cls][cat] then browserClassSpells[cls][cat] = {} end
local already = false
for _, existing in ipairs(browserClassSpells[cls][cat]) do
if existing.spellId == entry.spellId then
already = true
break
end
end
if not already then
table.insert(browserClassSpells[cls][cat], entry)
end
end
end
local function GetSearchTerm()
return NormalizeText(filterState().search)
end
local function IsEntryVisible(entry, classToken, category)
local spellId = tonumber(entry and entry.spellId)
if not spellId or spellId <= 0 then
return false
end
if filterState().enabledOnly and es()[spellId] == false then
return false
end
local search = GetSearchTerm()
if search == "" then
return true
end
local haystack = table.concat({
tostring(entry.name or SpellNameForId(spellId) or ""),
tostring(spellId),
tostring(LocalClassName(classToken)),
tostring(CategoryLabel(category)),
}, " "):lower()
return haystack:find(search, 1, true) ~= nil
end
local function CollectVisibleSpellIds(classToken, category)
local ids = {}
local categories = {}
if category then
categories[1] = category
else
categories = HMGT_SpellData.CategoryOrder or {}
end
for _, cat in ipairs(categories) do
for _, entry in ipairs((browserClassSpells[classToken] and browserClassSpells[classToken][cat]) or {}) do
if IsEntryVisible(entry, classToken, cat) then
ids[#ids + 1] = tonumber(entry.spellId)
end
end
end
return ids
end
local function CountVisible(classToken, category)
local total = 0
local enabled = 0
for _, spellId in ipairs(CollectVisibleSpellIds(classToken, category)) do
total = total + 1
if es()[spellId] ~= false then
enabled = enabled + 1
end
end
return total, enabled
end
local function CountAllVisible()
local total = 0
local enabled = 0
for _, classToken in ipairs(HMGT_SpellData.ClassOrder or {}) do
local visible, active = CountVisible(classToken)
total = total + visible
enabled = enabled + active
end
return total, enabled
end
local function SetSpellIdsEnabled(spellIds, value)
for _, spellId in ipairs(spellIds) do
es()[spellId] = value
end
HMGT:TriggerTrackerUpdate()
Notify()
end
local browserArgs = {
overview = {
type = "group",
name = L["OPT_SPELL_BROWSER"] or "Spell Browser",
order = 1,
args = {
info = {
type = "description",
order = 1,
width = "full",
name = function()
local total, enabled = CountAllVisible()
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",
total,
L["OPT_SPELLS_ENABLED_COUNT"] or "Enabled",
enabled
)
end,
},
search = {
type = "input",
order = 2,
width = "full",
name = L["OPT_FILTER_SEARCH"] or "Search",
desc = L["OPT_FILTER_SEARCH_DESC"] or "Search by spell name, Spell ID, class, or category.",
get = function()
return filterState().search or ""
end,
set = function(_, val)
filterState().search = val or ""
Notify()
end,
},
enabledOnly = {
type = "toggle",
order = 3,
width = "full",
name = L["OPT_FILTER_ENABLED_ONLY"] or "Show enabled spells only",
get = function()
return filterState().enabledOnly == true
end,
set = function(_, val)
filterState().enabledOnly = val and true or false
Notify()
end,
},
selectVisible = {
type = "execute",
order = 4,
width = "half",
name = L["OPT_SELECT_VISIBLE"] or "Select visible",
func = function()
local allIds = {}
for _, classToken in ipairs(HMGT_SpellData.ClassOrder or {}) do
for _, spellId in ipairs(CollectVisibleSpellIds(classToken)) do
allIds[#allIds + 1] = spellId
end
end
SetSpellIdsEnabled(allIds, true)
end,
},
deselectVisible = {
type = "execute",
order = 5,
width = "half",
name = L["OPT_DESELECT_VISIBLE"] or "Deselect visible",
func = function()
local allIds = {}
for _, classToken in ipairs(HMGT_SpellData.ClassOrder or {}) do
for _, spellId in ipairs(CollectVisibleSpellIds(classToken)) do
allIds[#allIds + 1] = spellId
end
end
SetSpellIdsEnabled(allIds, false)
end,
},
resetFilters = {
type = "execute",
order = 6,
width = "full",
name = L["OPT_FILTER_RESET"] or "Reset filters",
func = function()
filterState().search = ""
filterState().enabledOnly = false
Notify()
end,
},
},
},
}
local classOrder = 2
for _, classToken in ipairs(HMGT_SpellData.ClassOrder or {}) do
if browserClassSpells[classToken] then
local classKey = "cls_" .. classToken
local classArgs = {
summary = {
type = "description",
order = 1,
width = "full",
name = function()
local total, enabled = CountVisible(classToken)
return string.format(
"|cffffd100%s|r: %d |cffffd100%s|r: %d",
L["OPT_SPELLS_VISIBLE"] or "Visible spells",
total,
L["OPT_SPELLS_ENABLED_COUNT"] or "Enabled",
enabled
)
end,
},
selectAll = {
type = "execute",
order = 2,
width = "half",
name = L["OPT_SELECT_VISIBLE"] or "Select visible",
func = function()
SetSpellIdsEnabled(CollectVisibleSpellIds(classToken), true)
end,
},
deselectAll = {
type = "execute",
order = 3,
width = "half",
name = L["OPT_DESELECT_VISIBLE"] or "Deselect visible",
func = function()
SetSpellIdsEnabled(CollectVisibleSpellIds(classToken), false)
end,
},
}
local catOrder = 4
for _, cat in ipairs(HMGT_SpellData.CategoryOrder or {}) do
if browserClassSpells[classToken][cat] then
local catKey = "cat_" .. cat
local catArgs = {
summary = {
type = "description",
order = 1,
width = "full",
name = function()
local total, enabled = CountVisible(classToken, cat)
return string.format(
"|cffffd100%s|r: %d |cffffd100%s|r: %d",
L["OPT_SPELLS_VISIBLE"] or "Visible spells",
total,
L["OPT_SPELLS_ENABLED_COUNT"] or "Enabled",
enabled
)
end,
},
selectVisible = {
type = "execute",
order = 2,
width = "half",
name = L["OPT_SELECT_VISIBLE"] or "Select visible",
func = function()
SetSpellIdsEnabled(CollectVisibleSpellIds(classToken, cat), true)
end,
},
deselectVisible = {
type = "execute",
order = 3,
width = "half",
name = L["OPT_DESELECT_VISIBLE"] or "Deselect visible",
func = function()
SetSpellIdsEnabled(CollectVisibleSpellIds(classToken, cat), false)
end,
},
}
local spOrder = 4
for _, entry in ipairs(browserClassSpells[classToken][cat]) do
local spellId = tonumber(entry.spellId)
catArgs["sp_" .. spellId] = {
type = "toggle",
name = SpellLabel(entry),
desc = SpellDesc(entry),
order = spOrder,
hidden = function()
return not IsEntryVisible(entry, classToken, cat)
end,
get = function()
return es()[spellId] ~= false
end,
set = function(_, val)
es()[spellId] = val
HMGT:TriggerTrackerUpdate()
end,
}
spOrder = spOrder + 1
end
classArgs[catKey] = {
type = "group",
name = function()
local total = CountVisible(classToken, cat)
return string.format("%s |cff808080(%d)|r", CategoryLabel(cat), total)
end,
order = catOrder,
inline = true,
hidden = function()
local total = CountVisible(classToken, cat)
return total <= 0
end,
args = catArgs,
}
catOrder = catOrder + 1
end
end
browserArgs[classKey] = {
type = "group",
name = function()
local total = CountVisible(classToken)
return string.format("%s |cff808080(%d)|r", ClassHeader(classToken), total)
end,
order = classOrder,
hidden = function()
local total = CountVisible(classToken)
return total <= 0
end,
args = classArgs,
}
classOrder = classOrder + 1
end
end
return browserArgs
end
-- 1. Spells nach Klasse und Kategorie einteilen
-- classSpells[classToken][category] = { entry, ... }
local classSpells = {}
for _, entry in ipairs(database) do
for _, cls in ipairs(entry.classes) do
if not classSpells[cls] then classSpells[cls] = {} end
local cat = entry.category or "utility"
if not classSpells[cls][cat] then classSpells[cls][cat] = {} end
-- Duplikate vermeiden (ein Spell kann für mehrere Klassen gelten)
local already = false
for _, e in ipairs(classSpells[cls][cat]) do
if e.spellId == entry.spellId then already = true; break end
end
if not already then
table.insert(classSpells[cls][cat], entry)
end
end
end
-- 2. Args-Tabelle aufbauen
local args = {}
local classOrder = 1
for _, classToken in ipairs(HMGT_SpellData.ClassOrder) do
repeat -- repeat/until true = continue-Ersatz für Lua 5.1
if not classSpells[classToken] then break end -- skip this class
-- ── Klassen-Gruppe ────────────────────────────────
local classKey = "cls_" .. classToken
local classArgs = {}
local catOrder = 1
-- Alle Spells dieser Klasse für Select/Deselect All sammeln
local allSpellIds = {}
for _, cat in ipairs(HMGT_SpellData.CategoryOrder) do
if classSpells[classToken][cat] then
for _, entry in ipairs(classSpells[classToken][cat]) do
table.insert(allSpellIds, entry.spellId)
end
end
end
-- Select All / Deselect All Buttons
classArgs["selectAll"] = {
type = "execute",
name = L["OPT_SELECT_ALL"],
order = 1,
width = "half",
func = function()
for _, sid in ipairs(allSpellIds) do
es()[sid] = true
end
HMGT:TriggerTrackerUpdate()
end,
}
classArgs["deselectAll"] = {
type = "execute",
name = L["OPT_DESELECT_ALL"],
order = 2,
width = "half",
func = function()
for _, sid in ipairs(allSpellIds) do
es()[sid] = false
end
HMGT:TriggerTrackerUpdate()
end,
}
catOrder = 3
-- ── Kategorie-Gruppen innerhalb der Klasse ────────
for _, cat in ipairs(HMGT_SpellData.CategoryOrder) do
repeat -- repeat/until true = continue-Ersatz für Lua 5.1
if not classSpells[classToken][cat] then break end -- skip empty category
local catKey = "cat_" .. cat
local catArgs = {}
local spOrder = 1
for _, entry in ipairs(classSpells[classToken][cat]) do
local spellId = entry.spellId
catArgs["sp_" .. spellId] = {
type = "toggle",
name = SpellLabel(entry),
desc = SpellDesc(entry),
order = spOrder,
get = function()
return es()[spellId] ~= false
end,
set = function(_, val)
es()[spellId] = val
HMGT:TriggerTrackerUpdate()
end,
}
spOrder = spOrder + 1
end
classArgs[catKey] = {
type = "group",
name = L["CAT_" .. cat] or cat,
order = catOrder,
inline = true,
args = catArgs,
}
catOrder = catOrder + 1
until true -- end of category repeat block
end
args[classKey] = {
type = "group",
name = ClassHeader(classToken),
order = classOrder,
args = classArgs,
}
classOrder = classOrder + 1
until true -- end of class repeat block
end
return args
end
-- ═══════════════════════════════════════════════════════════════
-- TRACKER-EINSTELLUNGEN (gemeinsamer Builder)
-- ═══════════════════════════════════════════════════════════════
local function LocalizedClassName(classToken)
return (LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE[classToken])
or (LOCALIZED_CLASS_NAMES_FEMALE and LOCALIZED_CLASS_NAMES_FEMALE[classToken])
or classToken
end
local function BuildClassDropdownValues()
local out = {}
for _, token in ipairs(HMGT_SpellData.ClassOrder or {}) do
out[token] = LocalizedClassName(token)
end
return out
end
local function NotifyOptionsChanged()
if AceConfigRegistry and type(AceConfigRegistry.NotifyChange) == "function" then
AceConfigRegistry:NotifyChange(ADDON_NAME)
end
end
local function GetSpellNameById(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 ok, name = pcall(C_Spell.GetSpellName, sid)
if ok and 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 GetSpellIconById(spellId)
local sid = tonumber(spellId)
if sid and HMGT_SpellData and type(HMGT_SpellData.GetSpellIcon) == "function" then
local ok, icon = pcall(HMGT_SpellData.GetSpellIcon, sid)
if ok and type(icon) == "string" and icon ~= "" then
return icon
end
end
return "Interface\\Icons\\INV_Misc_QuestionMark"
end
local function BuildSpellIconMarkup(spellId, size)
local iconSize = tonumber(size) or 18
return string.format("|T%s:%d:%d:0:0|t", GetSpellIconById(spellId), iconSize, iconSize)
end
local function GetCategoryLabel(category)
return L["CAT_" .. tostring(category)] or tostring(category or "utility")
end
local function NormalizeSearchText(value)
local text = tostring(value or ""):lower()
text = text:gsub("^%s+", "")
text = text:gsub("%s+$", "")
return text
end
local function GetSpellFilterState(dbKey)
HMGT._cfgSpellFilters = HMGT._cfgSpellFilters or {}
HMGT._cfgSpellFilters[dbKey] = HMGT._cfgSpellFilters[dbKey] or {
search = "",
enabledOnly = false,
}
return HMGT._cfgSpellFilters[dbKey]
end
local function GetCustomSpellDraft(dbKey)
HMGT._cfgCustomDraft = HMGT._cfgCustomDraft or {}
HMGT._cfgCustomDraft[dbKey] = HMGT._cfgCustomDraft[dbKey] or {}
return HMGT._cfgCustomDraft[dbKey]
end
local function ParseSpecText(specText)
local specs = {}
if type(specText) ~= "string" then
return specs
end
for spec in specText:gmatch("(%d+)") do
specs[#specs + 1] = tostring(tonumber(spec))
end
return specs
end
local function FormatSpecsLabel(specs)
if type(specs) ~= "table" or #specs == 0 then
return L["OPT_ALL_SPECS"] or "All specs"
end
return table.concat(specs, ", ")
end
local function GetCustomSpellList(dbKey)
local profile = HMGT.db and HMGT.db.profile
local list = profile and profile.customSpells and profile.customSpells[dbKey]
if type(list) ~= "table" then
return {}
end
return list
end
local function GetTrackerModeSummary(settings)
if settings.testMode then
return L["OPT_TEST_MODE"]
end
if settings.demoMode then
return L["OPT_DEMO_MODE"]
end
if settings.enabled then
return L["OPT_ENABLED"]
end
return L["OPT_DISABLED"] or "Disabled"
end
local function GetTrackerVisibilitySummary(settings)
local parts = {}
if settings.showInSolo ~= false then parts[#parts + 1] = L["OPT_SHOW_SOLO"] or "Solo" end
if settings.showInGroup ~= false then parts[#parts + 1] = L["OPT_SHOW_GROUP"] or "Group" end
if settings.showInRaid ~= false then parts[#parts + 1] = L["OPT_SHOW_RAID"] or "Raid" end
if #parts == 0 then
return L["OPT_VISIBILITY_NONE"] or "Hidden everywhere"
end
return table.concat(parts, ", ")
end
local function BuildTrackerStatusText(moduleName, settings)
local display = settings.showBar and (L["OPT_DISPLAY_BAR"] or "Progress bars") or (L["OPT_DISPLAY_ICON"] or "Icons")
local grow = settings.growDirection or "DOWN"
local visibility = GetTrackerVisibilitySummary(settings)
local lines = {
string.format("|cffffd100%s|r: %s", L["OPT_STATUS_MODE"] or "Mode", tostring(GetTrackerModeSummary(settings))),
string.format("|cffffd100%s|r: %s", L["OPT_STATUS_DISPLAY"] or "Display", tostring(display)),
string.format("|cffffd100%s|r: %s", L["OPT_STATUS_VISIBILITY"] or "Visibility", tostring(visibility)),
string.format("|cffffd100%s|r: %s", L["OPT_STATUS_GROWTH"] or "Growth", tostring(grow)),
}
if moduleName == "GroupCooldownTracker" and settings.attachToPartyFrame == true then
lines[#lines + 1] = string.format("|cffffd100%s|r: %s", L["OPT_STATUS_ATTACH"] or "Attach", L["OPT_ATTACH_PARTY_FRAME"] or "Attach to Party Frame")
end
return table.concat(lines, "\n")
end
local function BuildTrackerOptions(moduleName, dbKey, spellDatabase, order)
-- Immer LIVE aus dem aktuellen Profil lesen – nie eine stale Closure-Referenz nutzen
local function s() return HMGT.db.profile[dbKey] end
local labelByModule = {
InterruptTracker = L["IT_NAME"],
RaidCooldownTracker = L["RCD_NAME"],
GroupCooldownTracker= L["GCD_NAME"],
}
local label = labelByModule[moduleName] or moduleName
local categoryValues = {
interrupt = L["CAT_interrupt"] or "Interrupt",
offensive = L["CAT_offensive"] or "Offensive",
defensive = L["CAT_defensive"] or "Defensive",
tank = L["CAT_tank"] or "Tank",
healing = L["CAT_healing"] or "Healing",
utility = L["CAT_utility"] or "Utility",
cc = L["CAT_cc"] or "CC",
lust = L["CAT_lust"] or "Lust",
}
local currentListGroup
local function ValueLabel(baseLabel, value)
return string.format("%s: %s", tostring(baseLabel or ""), tostring(value or ""))
end
local function TriggerUpdate()
HMGT:TriggerTrackerUpdate()
end
local function RefreshAnchors()
HMGT:RefreshFrameAnchors()
end
local function GetDraft()
return GetCustomSpellDraft(dbKey)
end
local function SpecsToText(specs)
local values = {}
if type(specs) == "table" then
for _, spec in ipairs(specs) do
values[#values + 1] = tostring(spec)
end
end
return table.concat(values, ",")
end
local function LoadCustomSpellIntoDraft(entry)
local draft = GetDraft()
local cooldown = tonumber((HMGT_SpellData.GetBaseCooldown and HMGT_SpellData.GetBaseCooldown(entry)) or entry.cooldown)
draft.spellId = tostring(tonumber(entry.spellId) or "")
draft.cooldown = cooldown and tostring(cooldown) or ""
draft.classToken = (type(entry.classes) == "table" and entry.classes[1]) or draft.classToken or "WARRIOR"
draft.specs = SpecsToText(entry.specs)
draft.category = entry.category or "utility"
end
local function GetCustomSpellPreviewText()
local draft = GetDraft()
local spellId = tonumber(draft.spellId)
if not spellId or spellId <= 0 then
return L["OPT_CUSTOM_SPELLS_PREVIEW_EMPTY"] or "Enter a spell ID to preview the custom spell."
end
local spellName = GetSpellNameById(spellId) or ("Spell " .. spellId)
local classLabel = LocalizedClassName(tostring(draft.classToken or "WARRIOR"))
local cooldown = tonumber(draft.cooldown) or 0
local specs = ParseSpecText(draft.specs or "")
local category = GetCategoryLabel(draft.category or "utility")
return table.concat({
string.format("%s %s", BuildSpellIconMarkup(spellId, 18), spellName),
string.format("|cffffd100%s|r: %d", L["OPT_CUSTOM_SPELLS_ID"] or "Spell ID", spellId),
string.format("|cffffd100%s|r: %ss", L["OPT_CUSTOM_SPELLS_CD"] or "Cooldown (sec)", cooldown),
string.format("|cffffd100%s|r: %s", L["OPT_CUSTOM_SPELLS_CLASS"] or "Class", classLabel),
string.format("|cffffd100%s|r: %s", L["OPT_CUSTOM_SPELLS_CATEGORY"] or "Category", category),
string.format("|cffffd100%s|r: %s", L["OPT_CUSTOM_SPELLS_SPECS"] or "Specs", FormatSpecsLabel(specs)),
}, "\n")
end
local function RefreshCustomSpellRows()
if not currentListGroup then
return
end
currentListGroup.args = {}
local list = GetCustomSpellList(dbKey)
if type(list) ~= "table" or #list == 0 then
currentListGroup.args.empty = {
type = "description",
order = 1,
width = "full",
name = L["OPT_CUSTOM_SPELLS_EMPTY"] or "No custom spells for this tracker.",
}
return
end
for index, entry in ipairs(list) do
local spellId = tonumber(entry.spellId) or 0
local spellName = tostring(entry.name or GetSpellNameById(spellId) or ("Spell " .. spellId))
local classToken = (type(entry.classes) == "table" and entry.classes[1]) or "WARRIOR"
local specs = {}
if type(entry.specs) == "table" then
for _, spec in ipairs(entry.specs) do
specs[#specs + 1] = tostring(spec)
end
end
local cooldown = tonumber((HMGT_SpellData.GetBaseCooldown and HMGT_SpellData.GetBaseCooldown(entry)) or entry.cooldown) or 0
local rowKey = "entry_" .. index
currentListGroup.args[rowKey] = {
type = "group",
inline = true,
order = index,
name = string.format("%s %s |cff808080(#%d)|r", BuildSpellIconMarkup(spellId, 16), spellName, spellId),
args = {
details = {
type = "description",
order = 1,
width = "full",
name = string.format(
"|cffffd100%s|r: %s |cffffd100%s|r: %s |cffffd100%s|r: %ss\n|cffffd100%s|r: %s",
L["OPT_CUSTOM_SPELLS_CLASS"] or "Class",
LocalizedClassName(classToken),
L["OPT_CUSTOM_SPELLS_CATEGORY"] or "Category",
GetCategoryLabel(entry.category or "utility"),
L["OPT_CUSTOM_SPELLS_CD"] or "Cooldown (sec)",
cooldown,
L["OPT_CUSTOM_SPELLS_SPECS"] or "Specs",
FormatSpecsLabel(specs)
),
},
load = {
type = "execute",
order = 2,
width = "half",
name = L["OPT_CUSTOM_SPELLS_LOAD"] or "Load into editor",
func = function()
LoadCustomSpellIntoDraft(entry)
NotifyOptionsChanged()
end,
},
remove = {
type = "execute",
order = 3,
width = "half",
name = L["OPT_CUSTOM_SPELLS_REMOVE"] or "Remove SpellID",
func = function()
local ok = HMGT.RemoveCustomSpell and HMGT:RemoveCustomSpell(dbKey, spellId)
if ok then
HMGT:Print(L["OPT_CUSTOM_SPELLS_MSG_REMOVED"] or "HMGT: custom spell removed")
RefreshCustomSpellRows()
NotifyOptionsChanged()
else
HMGT:Print(L["OPT_CUSTOM_SPELLS_MSG_NOT_FOUND"] or "HMGT: spell not found")
end
end,
},
},
}
end
end
local function AddDraftSpell()
local draft = GetDraft()
local ok = HMGT.AddCustomSpell and HMGT:AddCustomSpell(
dbKey,
draft.spellId,
draft.cooldown,
draft.classToken,
draft.specs,
draft.category
)
if ok then
HMGT:Print(L["OPT_CUSTOM_SPELLS_MSG_ADDED"] or "HMGT: custom spell added")
RefreshCustomSpellRows()
NotifyOptionsChanged()
else
HMGT:Print(L["OPT_CUSTOM_SPELLS_MSG_INVALID"] or "HMGT: invalid custom spell input")
end
end
local function RemoveDraftSpell()
local draft = GetDraft()
local ok = HMGT.RemoveCustomSpell and HMGT:RemoveCustomSpell(dbKey, draft.spellId)
if ok then
HMGT:Print(L["OPT_CUSTOM_SPELLS_MSG_REMOVED"] or "HMGT: custom spell removed")
RefreshCustomSpellRows()
NotifyOptionsChanged()
else
HMGT:Print(L["OPT_CUSTOM_SPELLS_MSG_NOT_FOUND"] or "HMGT: spell not found")
end
end
currentListGroup = {
type = "group",
name = L["OPT_CUSTOM_SPELLS_CURRENT"] or "Current custom spells",
order = 4,
args = {},
}
RefreshCustomSpellRows()
return {
type = "group",
name = label,
order = order,
childGroups = "tab",
args = {
general = {
type = "group",
name = L["OPT_GENERAL"],
order = 1,
args = {
status = {
type = "group",
inline = true,
name = label,
order = 1,
args = {
summary = {
type = "description",
order = 1,
width = "full",
name = function()
return BuildTrackerStatusText(moduleName, s())
end,
},
},
},
modeGroup = {
type = "group",
inline = true,
name = L["OPT_UI_GROUP_MODE"] or "Mode",
order = 10,
args = {
enabled = {
type = "toggle", order = 1, width = "full",
name = L["OPT_ENABLED"], desc = L["OPT_ENABLED_DESC"],
get = function() return s().enabled end,
set = function(_, val)
s().enabled = val
if val then
if HMGT[moduleName] then HMGT[moduleName]:Enable() end
RefreshAnchors()
else
if not s().demoMode and not s().testMode and HMGT[moduleName] then
HMGT[moduleName]:Disable()
end
end
NotifyOptionsChanged()
end,
},
demoMode = {
type = "toggle", order = 2, width = "full",
name = L["OPT_DEMO_MODE"], desc = L["OPT_DEMO_MODE_DESC"],
get = function() return s().demoMode == true end,
set = function(_, val)
s().demoMode = val
if val then
s().testMode = false
end
if val then
if HMGT[moduleName] then HMGT[moduleName]:Enable() end
else
if not s().enabled and not s().testMode and HMGT[moduleName] then
HMGT[moduleName]:Disable()
end
end
TriggerUpdate()
NotifyOptionsChanged()
end,
},
testMode = {
type = "toggle", order = 3, width = "full",
name = L["OPT_TEST_MODE"], desc = L["OPT_TEST_MODE_DESC"],
get = function() return s().testMode == true end,
set = function(_, val)
s().testMode = val
if val then
s().demoMode = false
end
if val then
if HMGT[moduleName] then HMGT[moduleName]:Enable() end
else
if not s().enabled and not s().demoMode and HMGT[moduleName] then
HMGT[moduleName]:Disable()
end
end
TriggerUpdate()
NotifyOptionsChanged()
end,
},
showBar = {
type = "select", order = 4, width = "full",
name = L["OPT_DISPLAY_MODE"], desc = L["OPT_DISPLAY_MODE_DESC"],
values = { bar = L["OPT_DISPLAY_BAR"], icon = L["OPT_DISPLAY_ICON"] },
get = function() return s().showBar and "bar" or "icon" end,
set = function(_, val)
s().showBar = (val == "bar")
TriggerUpdate()
NotifyOptionsChanged()
end,
},
},
},
placementGroup = {
type = "group",
inline = true,
name = L["OPT_UI_GROUP_PLACEMENT"] or "Placement",
order = 20,
args = {
locked = {
type = "toggle", order = 1,
name = L["OPT_LOCKED"], desc = L["OPT_LOCKED_DESC"],
get = function() return s().locked end,
set = function(_, val)
s().locked = val
if moduleName == "GroupCooldownTracker" and HMGT.GroupCooldownTracker then
if HMGT.GroupCooldownTracker.SetLockedAll then
HMGT.GroupCooldownTracker:SetLockedAll(val)
end
if HMGT.GroupCooldownTracker.RefreshAnchors then
HMGT.GroupCooldownTracker:RefreshAnchors()
end
elseif HMGT[moduleName] and HMGT[moduleName].frame then
HMGT.TrackerFrame:SetLocked(HMGT[moduleName].frame, val)
end
end,
},
growDirection = {
type = "select", order = 2,
name = L["OPT_GROW_DIR"],
values = function()
if s().showBar then
return { DOWN = L["OPT_GROW_DOWN"], UP = L["OPT_GROW_UP"] }
end
return {
DOWN = L["OPT_GROW_DOWN"],
UP = L["OPT_GROW_UP"],
LEFT = L["OPT_GROW_LEFT"],
RIGHT = L["OPT_GROW_RIGHT"],
}
end,
get = function()
local dir = s().growDirection or "DOWN"
if s().showBar and (dir == "LEFT" or dir == "RIGHT") then
return "DOWN"
end
return dir
end,
set = function(_, val)
s().growDirection = val
TriggerUpdate()
NotifyOptionsChanged()
end,
},
attachToPartyFrame = {
type = "toggle", order = 3, width = "full",
name = L["OPT_ATTACH_PARTY_FRAME"],
desc = L["OPT_ATTACH_PARTY_FRAME_DESC"],
hidden = function() return moduleName ~= "GroupCooldownTracker" end,
get = function() return s().attachToPartyFrame == true end,
set = function(_, val)
s().attachToPartyFrame = val
RefreshAnchors()
TriggerUpdate()
NotifyOptionsChanged()
end,
},
partyAttachSide = {
type = "select", order = 4,
name = L["OPT_ATTACH_PARTY_SIDE"],
values = {
LEFT = L["OPT_ATTACH_LEFT"],
RIGHT = L["OPT_ATTACH_RIGHT"],
},
hidden = function()
return moduleName ~= "GroupCooldownTracker" or s().attachToPartyFrame ~= true
end,
get = function() return s().partyAttachSide or "RIGHT" end,
set = function(_, val)
s().partyAttachSide = val
RefreshAnchors()
TriggerUpdate()
end,
},
partyAttachOffsetX = {
type = "range", order = 5, min = -200, max = 200, step = 1,
name = function()
return ValueLabel(L["OPT_ATTACH_PARTY_OFFSET_X"] or "Attach X offset", s().partyAttachOffsetX or 8)
end,
hidden = function()
return moduleName ~= "GroupCooldownTracker" or s().attachToPartyFrame ~= true
end,
get = function() return s().partyAttachOffsetX or 8 end,
set = function(_, val)
s().partyAttachOffsetX = val
RefreshAnchors()
TriggerUpdate()
end,
},
partyAttachOffsetY = {
type = "range", order = 6, min = -200, max = 200, step = 1,
name = function()
return ValueLabel(L["OPT_ATTACH_PARTY_OFFSET_Y"] or "Attach Y offset", s().partyAttachOffsetY or 0)
end,
hidden = function()
return moduleName ~= "GroupCooldownTracker" or s().attachToPartyFrame ~= true
end,
get = function() return s().partyAttachOffsetY or 0 end,
set = function(_, val)
s().partyAttachOffsetY = val
RefreshAnchors()
TriggerUpdate()
end,
},
anchorTo = {
type = "select", order = 10,
name = L["OPT_ANCHOR_TO"], desc = L["OPT_ANCHOR_TO_DESC"],
hidden = function() return moduleName == "GroupCooldownTracker" end,
values = function()
return HMGT:GetAnchorTargetOptions(moduleName, s().anchorTo)
end,
get = function()
local current = s().anchorTo or "UIParent"
local values = HMGT:GetAnchorTargetOptions(moduleName, current)
if values[current] then
return current
end
return "CUSTOM"
end,
set = function(_, val)
if val == moduleName then
val = "UIParent"
end
if val ~= "CUSTOM" and type(val) == "string" then
s().anchorCustom = s().anchorCustom or ""
end
s().anchorTo = val
RefreshAnchors()
end,
},
anchorCustom = {
type = "input", order = 11, width = "full",
name = L["OPT_ANCHOR_CUSTOM_NAME"],
desc = L["OPT_ANCHOR_CUSTOM_NAME_DESC"],
hidden = function()
if moduleName == "GroupCooldownTracker" then
return true
end
return (s().anchorTo or "UIParent") ~= "CUSTOM"
end,
get = function() return s().anchorCustom or "" end,
set = function(_, val)
s().anchorCustom = (val or ""):gsub("^%s+", ""):gsub("%s+$", "")
RefreshAnchors()
end,
},
anchorPoint = {
type = "select", order = 12,
name = L["OPT_ANCHOR_POINT"], desc = L["OPT_ANCHOR_POINT_DESC"],
hidden = function() return moduleName == "GroupCooldownTracker" end,
values = {
TOPLEFT = "TOPLEFT", TOP = "TOP", TOPRIGHT = "TOPRIGHT",
LEFT = "LEFT", CENTER = "CENTER", RIGHT = "RIGHT",
BOTTOMLEFT = "BOTTOMLEFT", BOTTOM = "BOTTOM", BOTTOMRIGHT = "BOTTOMRIGHT",
},
get = function() return s().anchorPoint or "TOPLEFT" end,
set = function(_, val)
s().anchorPoint = val
RefreshAnchors()
end,
},
anchorRelativePoint = {
type = "select", order = 13,
name = L["OPT_ANCHOR_REL_POINT"], desc = L["OPT_ANCHOR_REL_POINT_DESC"],
hidden = function() return moduleName == "GroupCooldownTracker" end,
values = {
TOPLEFT = "TOPLEFT", TOP = "TOP", TOPRIGHT = "TOPRIGHT",
LEFT = "LEFT", CENTER = "CENTER", RIGHT = "RIGHT",
BOTTOMLEFT = "BOTTOMLEFT", BOTTOM = "BOTTOM", BOTTOMRIGHT = "BOTTOMRIGHT",
},
get = function() return s().anchorRelPoint or "TOPLEFT" end,
set = function(_, val)
s().anchorRelPoint = val
RefreshAnchors()
end,
},
anchorX = {
type = "range", order = 14, min = -2000, max = 2000, step = 1,
name = function()
local value = s().anchorX
if value == nil then value = s().posX or 0 end
return ValueLabel(L["OPT_ANCHOR_X"], value)
end,
hidden = function() return moduleName == "GroupCooldownTracker" end,
get = function()
if s().anchorX ~= nil then return s().anchorX end
return s().posX or 0
end,
set = function(_, val)
s().anchorX = val
RefreshAnchors()
end,
},
anchorY = {
type = "range", order = 15, min = -2000, max = 2000, step = 1,
name = function()
local value = s().anchorY
if value == nil then value = s().posY or 0 end
return ValueLabel(L["OPT_ANCHOR_Y"], value)
end,
hidden = function() return moduleName == "GroupCooldownTracker" end,
get = function()
if s().anchorY ~= nil then return s().anchorY end
return s().posY or 0
end,
set = function(_, val)
s().anchorY = val
RefreshAnchors()
end,
},
},
},
visibilityGroup = {
type = "group",
inline = true,
name = L["OPT_UI_GROUP_VISIBILITY"] or "Visibility",
order = 30,
args = {
showInSolo = {
type = "toggle", order = 1,
name = L["OPT_SHOW_SOLO"], desc = L["OPT_SHOW_SOLO_DESC"],
get = function() return s().showInSolo ~= false end,
set = function(_, val)
s().showInSolo = val
TriggerUpdate()
NotifyOptionsChanged()
end,
},
showInGroup = {
type = "toggle", order = 2,
name = L["OPT_SHOW_GROUP"], desc = L["OPT_SHOW_GROUP_DESC"],
get = function() return s().showInGroup ~= false end,
set = function(_, val)
s().showInGroup = val
TriggerUpdate()
NotifyOptionsChanged()
end,
},
showInRaid = {
type = "toggle", order = 3,
name = L["OPT_SHOW_RAID"], desc = L["OPT_SHOW_RAID_DESC"],
get = function() return s().showInRaid ~= false end,
set = function(_, val)
s().showInRaid = val
TriggerUpdate()
NotifyOptionsChanged()
end,
},
showOnlyReady = {
type = "toggle", order = 4, width = "full",
name = L["OPT_SHOW_ONLY_READY"],
desc = L["OPT_SHOW_ONLY_READY_DESC"],
get = function() return s().showOnlyReady == true end,
set = function(_, val)
s().showOnlyReady = val
TriggerUpdate()
end,
},
readySoonSec = {
type = "range", order = 5, min = 0, max = 300, step = 1,
name = function()
return ValueLabel(L["OPT_READY_SOON_SEC"], s().readySoonSec or 0)
end,
desc = L["OPT_READY_SOON_SEC_DESC"],
get = function() return s().readySoonSec or 0 end,
set = function(_, val)
s().readySoonSec = val
TriggerUpdate()
end,
},
roleFilter = {
type = "select", order = 6,
name = L["OPT_ROLE_FILTER"] or "Role filter",
values = {
ALL = L["OPT_ROLE_FILTER_ALL"] or "All",
TANK = L["OPT_ROLE_FILTER_TANK"] or "Tank",
HEALER = L["OPT_ROLE_FILTER_HEALER"] or "Healer",
DAMAGER = L["OPT_ROLE_FILTER_DAMAGER"] or "Damage",
},
get = function() return s().roleFilter or "ALL" end,
set = function(_, val)
s().roleFilter = val
TriggerUpdate()
end,
},
rangeCheck = {
type = "toggle", order = 7,
name = L["OPT_RANGE_CHECK"] or "Range check",
get = function() return s().rangeCheck == true end,
set = function(_, val)
s().rangeCheck = val
TriggerUpdate()
NotifyOptionsChanged()
end,
},
hideOutOfRange = {
type = "toggle", order = 8,
name = L["OPT_HIDE_OOR"] or "Hide out of range",
disabled = function() return s().rangeCheck ~= true end,
get = function() return s().hideOutOfRange == true end,
set = function(_, val)
s().hideOutOfRange = val
TriggerUpdate()
end,
},
outOfRangeAlpha = {
type = "range", order = 9, min = 0.1, max = 1, step = 0.05,
name = function()
return ValueLabel(L["OPT_OOR_ALPHA"] or "Out of range alpha", s().outOfRangeAlpha or 0.4)
end,
disabled = function() return s().rangeCheck ~= true end,
get = function() return s().outOfRangeAlpha or 0.4 end,
set = function(_, val)
s().outOfRangeAlpha = val
TriggerUpdate()
end,
},
},
},
layoutGroup = {
type = "group",
inline = true,
name = L["OPT_UI_GROUP_LAYOUT"] or "Layout",
order = 40,
args = {
width = {
type = "range", order = 1, min = 100, max = 600, step = 5,
name = function()
return ValueLabel(L["OPT_WIDTH"], s().width or 250)
end,
hidden = function() return not s().showBar end,
get = function() return s().width or 250 end,
set = function(_, val)
s().width = val
TriggerUpdate()
end,
},
barHeight = {
type = "range", order = 2, min = 10, max = 60, step = 1,
name = function()
return ValueLabel(L["OPT_BAR_HEIGHT"], s().barHeight or 20)
end,
hidden = function() return not s().showBar end,
get = function() return s().barHeight or 20 end,
set = function(_, val)
s().barHeight = val
TriggerUpdate()
end,
},
barSpacing = {
type = "range", order = 3, min = 0, max = 20, step = 1,
name = function()
return ValueLabel(L["OPT_BAR_SPACING"], s().barSpacing or 2)
end,
hidden = function() return not s().showBar end,
get = function() return s().barSpacing or 2 end,
set = function(_, val)
s().barSpacing = val
TriggerUpdate()
end,
},
barTexture = {
type = "select", order = 4,
name = L["OPT_BAR_TEXTURE"], desc = L["OPT_BAR_TEXTURE_DESC"],
hidden = function() return not s().showBar end,
dialogControl = "LSM30_Statusbar",
values = AceGUIWidgetLSMlists and AceGUIWidgetLSMlists.statusbar or LSM:HashTable("statusbar"),
get = function() return s().barTexture or "Blizzard" end,
set = function(_, val)
s().barTexture = val
TriggerUpdate()
end,
},
borderEnabled = {
type = "toggle", order = 5, width = "full",
name = L["OPT_BORDER_ENABLED"], desc = L["OPT_BORDER_ENABLED_DESC"],
get = function() return s().borderEnabled == true end,
set = function(_, val)
s().borderEnabled = val
TriggerUpdate()
NotifyOptionsChanged()
end,
},
borderColor = {
type = "color", order = 6, hasAlpha = false,
name = L["OPT_BORDER_COLOR"], desc = L["OPT_BORDER_COLOR_DESC"],
disabled = function() return s().borderEnabled ~= true end,
get = function()
local c = s().borderColor or {}
return c.r or c[1] or 1, c.g or c[2] or 1, c.b or c[3] or 1
end,
set = function(_, r, g, b)
s().borderColor = { r = r, g = g, b = b, a = 1 }
TriggerUpdate()
end,
},
iconSize = {
type = "range", order = 10, min = 16, max = 80, step = 2,
name = function()
return ValueLabel(L["OPT_ICON_SIZE"], s().iconSize or 32)
end,
hidden = function() return s().showBar end,
get = function() return s().iconSize or 32 end,
set = function(_, val)
s().iconSize = val
TriggerUpdate()
end,
},
iconSpacing = {
type = "range", order = 11, min = 0, max = 20, step = 1,
name = function()
return ValueLabel(L["OPT_ICON_SPACING"], s().iconSpacing or 2)
end,
hidden = function() return s().showBar end,
get = function() return s().iconSpacing or 2 end,
set = function(_, val)
s().iconSpacing = val
TriggerUpdate()
end,
},
iconCols = {
type = "range", order = 12, min = 1, max = 12, step = 1,
name = function()
return ValueLabel(L["OPT_ICON_COLS"], s().iconCols or 6)
end,
desc = L["OPT_ICON_COLS_DESC"],
hidden = function() return s().showBar end,
get = function() return s().iconCols or 6 end,
set = function(_, val)
s().iconCols = val
TriggerUpdate()
end,
},
textAnchor = {
type = "select", order = 14,
name = L["OPT_TEXT_ANCHOR"], desc = L["OPT_TEXT_ANCHOR_DESC"],
hidden = function()
return s().showBar or (s().iconOverlay or "sweep") == "sweep"
end,
values = {
onIcon = L["OPT_ANCHOR_ON_ICON"],
above = L["OPT_ANCHOR_ABOVE"],
below = L["OPT_ANCHOR_BELOW"],
},
get = function() return s().textAnchor or "below" end,
set = function(_, val)
s().textAnchor = val
TriggerUpdate()
end,
},
},
},
appearanceGroup = {
type = "group",
inline = true,
name = L["OPT_UI_GROUP_APPEARANCE"] or "Appearance",
order = 50,
args = {
showPlayerName = {
type = "toggle", order = 1,
name = L["OPT_SHOW_NAME"],
get = function() return s().showPlayerName end,
set = function(_, val)
s().showPlayerName = val
TriggerUpdate()
end,
},
colorByClass = {
type = "toggle", order = 2,
name = L["OPT_CLASS_COLOR"],
get = function() return s().colorByClass end,
set = function(_, val)
s().colorByClass = val
TriggerUpdate()
end,
},
font = {
type = "select", order = 3,
name = L["OPT_FONT"],
dialogControl = "LSM30_Font",
values = AceGUIWidgetLSMlists and AceGUIWidgetLSMlists.font or LSM:HashTable("font"),
get = function() return s().font or "Friz Quadrata TT" end,
set = function(_, val)
s().font = val
TriggerUpdate()
end,
},
fontSize = {
type = "range", order = 4, min = 6, max = 24, step = 1,
name = function()
return ValueLabel(L["OPT_FONT_SIZE"], s().fontSize or 12)
end,
get = function() return s().fontSize or 12 end,
set = function(_, val)
s().fontSize = val
TriggerUpdate()
end,
},
fontOutline = {
type = "select", order = 5,
name = L["OPT_FONT_OUTLINE"],
values = {
[""] = L["OPT_OUTLINE_NONE"],
["OUTLINE"] = L["OPT_OUTLINE_NORMAL"],
["THICKOUTLINE"] = L["OPT_OUTLINE_THICK"],
["MONOCHROME"] = L["OPT_OUTLINE_MONO"],
},
get = function() return s().fontOutline or "OUTLINE" end,
set = function(_, val)
s().fontOutline = val
TriggerUpdate()
end,
},
},
},
},
},
spells = {
type = "group",
name = L["OPT_SECTION_SPELLS"],
order = 2,
childGroups = "tree",
args = BuildSpellOptions(spellDatabase, dbKey),
},
customSpells = {
type = "group",
name = L["OPT_SECTION_CUSTOM_SPELLS"] or "Custom Spells",
order = 3,
args = {
editor = {
type = "group",
inline = true,
name = L["OPT_CUSTOM_SPELLS_EDITOR"] or "Spell Editor",
order = 1,
args = {
spellId = {
type = "input",
order = 1,
width = "half",
name = L["OPT_CUSTOM_SPELLS_ID"] or "Spell ID",
get = function() return GetDraft().spellId or "" end,
set = function(_, val)
GetDraft().spellId = val
NotifyOptionsChanged()
end,
},
cooldown = {
type = "input",
order = 2,
width = "half",
name = L["OPT_CUSTOM_SPELLS_CD"] or "Cooldown (sec)",
get = function() return GetDraft().cooldown or "" end,
set = function(_, val)
GetDraft().cooldown = val
NotifyOptionsChanged()
end,
},
classToken = {
type = "select",
order = 3,
width = "half",
name = L["OPT_CUSTOM_SPELLS_CLASS"] or "Class",
values = BuildClassDropdownValues,
get = function() return GetDraft().classToken or "WARRIOR" end,
set = function(_, val)
GetDraft().classToken = val
NotifyOptionsChanged()
end,
},
category = {
type = "select",
order = 4,
width = "half",
name = L["OPT_CUSTOM_SPELLS_CATEGORY"] or "Category",
values = categoryValues,
get = function() return GetDraft().category or "utility" end,
set = function(_, val)
GetDraft().category = val
NotifyOptionsChanged()
end,
},
specs = {
type = "input",
order = 5,
width = "full",
name = L["OPT_CUSTOM_SPELLS_SPECS"] or "Specs (optional, e.g. 1,3)",
get = function() return GetDraft().specs or "" end,
set = function(_, val)
GetDraft().specs = val
NotifyOptionsChanged()
end,
},
},
},
preview = {
type = "group",
inline = true,
name = L["OPT_CUSTOM_SPELLS_PREVIEW"] or "Preview",
order = 2,
args = {
previewText = {
type = "description",
order = 1,
width = "full",
name = function()
return GetCustomSpellPreviewText()
end,
},
},
},
actions = {
type = "group",
inline = true,
name = L["OPT_TRACKER_ACTIONS"] or "Actions",
order = 3,
args = {
add = {
type = "execute",
order = 1,
width = "half",
name = L["OPT_CUSTOM_SPELLS_ADD"] or "Add Spell",
func = AddDraftSpell,
},
remove = {
type = "execute",
order = 2,
width = "half",
name = L["OPT_CUSTOM_SPELLS_REMOVE"] or "Remove SpellID",
func = RemoveDraftSpell,
},
},
},
currentList = currentListGroup,
},
},
},
}
end
-- ═══════════════════════════════════════════════════════════════
-- INITIALISIERUNG
-- ═══════════════════════════════════════════════════════════════
local function GetAddonVersion()
if HMGT and HMGT.ADDON_VERSION then
return HMGT.ADDON_VERSION
end
if C_AddOns and type(C_AddOns.GetAddOnMetadata) == "function" then
return C_AddOns.GetAddOnMetadata(ADDON_NAME, "Version")
end
if type(GetAddOnMetadata) == "function" then
return GetAddOnMetadata(ADDON_NAME, "Version")
end
return nil
end
local function GetChangelogReleases()
local data = HMGT_Changelog and HMGT_Changelog.releases
if type(data) ~= "table" then
return nil
end
local locale = type(GetLocale) == "function" and GetLocale() or "enUS"
local releases = data[locale]
if type(releases) == "table" and #releases > 0 then
return releases
end
releases = data.enUS
if type(releases) == "table" and #releases > 0 then
return releases
end
return nil
end
local function GetChangelogText()
local releases = GetChangelogReleases()
if type(releases) ~= "table" or #releases == 0 then
local version = tostring(GetAddonVersion() or "dev")
local header = string.format("%s: %s", L["OPT_CHANGELOG_VERSION"] or "Version", version)
local body = L["OPT_CHANGELOG_EMPTY"] or "No changelog entries available."
return table.concat({ header, "", body }, "\n")
end
local lines = {}
for index, release in ipairs(releases) do
local version = tostring(release.version or GetAddonVersion() or "dev")
lines[#lines + 1] = string.format("%s: %s", L["OPT_CHANGELOG_VERSION"] or "Version", version)
local entries = release.entries
if type(entries) == "table" and #entries > 0 then
for _, entry in ipairs(entries) do
lines[#lines + 1] = "- " .. tostring(entry)
end
else
lines[#lines + 1] = "- " .. (L["OPT_CHANGELOG_EMPTY"] or "No changelog entries available.")
end
if index < #releases then
lines[#lines + 1] = ""
end
end
return table.concat(lines, "\n")
end
local function NormalizeExecuteButtonWidths(option)
if type(option) ~= "table" then
return
end
if option.type == "execute" and option.width == nil then
option.width = "double"
end
if type(option.args) == "table" then
for _, child in pairs(option.args) do
NormalizeExecuteButtonWidths(child)
end
end
end
local function SetAllTrackerFramesLocked(locked)
local profile = HMGT.db and HMGT.db.profile
if not profile then
return
end
if type(profile.trackers) == "table" then
for _, tracker in ipairs(profile.trackers) do
tracker.locked = locked
end
end
if HMGT.TrackerManager and HMGT.TrackerManager.SetAllLocked then
HMGT.TrackerManager:SetAllLocked(locked)
end
HMGT:RefreshFrameAnchors(true)
HMGT:TriggerTrackerUpdate()
end
HMGT_Config.SetAllTrackerFramesLocked = SetAllTrackerFramesLocked
function HMGT_Config:Initialize()
-- Profiles: GetOptionsTable() direkt verwenden – NICHT einwickeln!
local profilesOpts = LibStub("AceDBOptions-3.0"):GetOptionsTable(HMGT.db)
profilesOpts.order = 99
profilesOpts.name = L["OPT_PROFILES"] or profilesOpts.name
local function NormalizeProviderResult(result)
if type(result) ~= "table" then return {} end
if type(result.path) == "string" and type(result.group) == "table" then
return { result }
end
local out = {}
for _, entry in ipairs(result) do
if type(entry) == "table" and type(entry.path) == "string" and type(entry.group) == "table" then
out[#out + 1] = entry
end
end
return out
end
local function BuildGeneralSettingsGroup()
return {
type = "group",
order = 10,
name = L["OPT_GENERAL_SETTINGS"] or "General Settings",
args = {
generalSettings = {
type = "group",
order = 1,
inline = true,
name = L["OPT_GENERAL_SETTINGS"] or "General Settings",
args = {
showMinimapIcon = {
type = "toggle",
order = 1,
width = "full",
name = L["OPT_SHOW_MINIMAP_ICON"] or "Show Minimap Icon",
get = function()
local profile = HMGT.db and HMGT.db.profile
if not profile then
return true
end
profile.minimap = profile.minimap or {}
return profile.minimap.hide ~= true
end,
set = function(_, value)
local profile = HMGT.db and HMGT.db.profile
if not profile then
return
end
profile.minimap = profile.minimap or {}
profile.minimap.hide = not (value == true)
if HMGT.CreateMinimapButton then
HMGT:CreateMinimapButton()
end
if HMGT.UpdateMinimapButtonPosition then
HMGT:UpdateMinimapButtonPosition()
end
end,
},
},
},
commands = {
type = "group",
order = 2,
inline = true,
name = L["OPT_COMMANDS"] or "Commands",
args = {
list = {
type = "description",
order = 1,
width = "full",
name = table.concat({
"|cffffd100/hmgt|r",
"|cffffd100/hmgt debug|r",
"|cffffd100/hmgt version|r",
}, "\n"),
},
},
},
},
}
end
local function GetProviderEntries(providerId)
local provider = HMGT_Config._optionProviders and HMGT_Config._optionProviders[providerId]
if type(provider) ~= "function" then
return {}
end
local ok, result = pcall(provider, HMGT_Config)
if not ok then
if HMGT and HMGT.DevError then
HMGT:DevError("Options", "provider_failed", {
provider = tostring(providerId),
error = tostring(result),
})
end
return {}
end
return NormalizeProviderResult(result)
end
local function GetSingleProviderGroup(providerId)
local entries = GetProviderEntries(providerId)
if type(entries[1]) == "table" then
return entries[1].group
end
return nil
end
local function CopyGroup(group, overrides)
if type(group) ~= "table" then
return nil
end
local out = {}
for key, value in pairs(group) do
out[key] = value
end
for key, value in pairs(overrides or {}) do
out[key] = value
end
return out
end
local function FlattenGeneralSubgroup(group)
if type(group) ~= "table" or type(group.args) ~= "table" then
return group
end
local generalGroup = group.args.general
if type(generalGroup) ~= "table" or generalGroup.type ~= "group" or type(generalGroup.args) ~= "table" then
return group
end
local merged = CopyGroup(group)
merged.args = {}
for key, value in pairs(group.args) do
if key ~= "general" then
merged.args[key] = value
end
end
for key, value in pairs(generalGroup.args) do
if merged.args[key] == nil then
merged.args[key] = value
else
merged.args["general_" .. key] = value
end
end
local hasChildGroups = false
for _, value in pairs(merged.args) do
if type(value) == "table" and value.type == "group" then
hasChildGroups = true
break
end
end
if not hasChildGroups then
merged.childGroups = nil
end
return merged
end
local function BuildNamedModuleGroup(providerId, displayName, order)
local group = GetSingleProviderGroup(providerId)
if not group then
return nil
end
return FlattenGeneralSubgroup(CopyGroup(group, {
name = displayName or group.name,
order = order or group.order,
}))
end
local function BuildModulesGroup()
local modulesGroup = {
type = "group",
name = L["OPT_MODULES"] or "Modules",
order = 20,
childGroups = "tree",
args = {},
}
local trackerGroup = BuildNamedModuleGroup(
"tracker",
L["OPT_MODULE_TRACKER"] or "Tracker",
10
)
if trackerGroup then
modulesGroup.args.tracker = trackerGroup
end
local buffEndingGroup = BuildNamedModuleGroup(
"announcer.buffEndingAnnouncer",
L["OPT_MODULE_BUFF_ENDING"] or "Buff Ending",
20
)
if buffEndingGroup then
modulesGroup.args.buffEnding = buffEndingGroup
end
local mapOverlayGroup = BuildNamedModuleGroup(
"map.overlay",
L["OPT_MODULE_MAP_OVERLAY"] or "Map Overlay",
30
)
if mapOverlayGroup then
modulesGroup.args.mapOverlay = mapOverlayGroup
end
local raidTimelineGroup = BuildNamedModuleGroup(
"raidTimeline",
L["OPT_RT_NAME"] or "Raid Timeline",
40
)
if raidTimelineGroup then
modulesGroup.args.raidTimeline = raidTimelineGroup
end
if next(modulesGroup.args) == nil then
return nil
end
return modulesGroup
end
local function BuildChangelogOptions()
return {
type = "group",
name = L["OPT_CHANGELOG"] or "Changelog",
args = {
changelog = {
type = "input",
order = 1,
width = "full",
multiline = 24,
name = L["OPT_CHANGELOG_DESC"] or "Recent addon changes",
get = function()
return GetChangelogText()
end,
set = function() end,
},
},
}
end
local rootOptions = {
type = "group",
name = L["ADDON_TITLE"],
childGroups = "tree",
args = {
generalSettings = BuildGeneralSettingsGroup(),
profiles = profilesOpts,
},
}
local modulesGroup = BuildModulesGroup()
if modulesGroup then
rootOptions.args.modules = modulesGroup
end
NormalizeExecuteButtonWidths(rootOptions)
local aceConfig = LibStub("AceConfig-3.0")
local aceConfigDialog = LibStub("AceConfigDialog-3.0")
local rootCategoryName = L["ADDON_TITLE"]
aceConfig:RegisterOptionsTable(ADDON_NAME, rootOptions)
local _, rootCategoryId = aceConfigDialog:AddToBlizOptions(ADDON_NAME, rootCategoryName)
HMGT_Config._settingsCategoryId = rootCategoryId
HMGT_Config._settingsCategoryName = rootCategoryName
local changelogTableName = ADDON_NAME .. "_Changelog"
aceConfig:RegisterOptionsTable(changelogTableName, BuildChangelogOptions())
aceConfigDialog:AddToBlizOptions(changelogTableName, L["OPT_CHANGELOG"] or "Changelog", rootCategoryName)
end
HMGT_Config.BuildTrackerOptions = BuildTrackerOptions