-- 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