local ADDON_NAME = "HailMaryGuildTools" local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME) if not HMGT then return end local RT = HMGT.RaidTimeline if not RT then return end if not HMGT_Config or not HMGT_Config.RegisterOptionsProvider then return end local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME, true) or {} local AceConfigRegistry = LibStub("AceConfigRegistry-3.0") local LSM = LibStub("LibSharedMedia-3.0", true) local FONT_OUTLINE_VALUES = { NONE = NONE or "None", OUTLINE = "Outline", THICKOUTLINE = "Thick Outline", MONOCHROME = "Monochrome", ["OUTLINE,MONOCHROME"] = "Outline Monochrome", } local DIFFICULTY_KEYS = { "lfr", "normal", "heroic", "mythic" } local MAX_ENTRY_ROWS = 24 local raidTimelineOptionsGroup local raidCooldownSpellValuesCache local bossAbilityValuesCache = {} local function ClearOptionCaches() raidCooldownSpellValuesCache = nil bossAbilityValuesCache = {} end local function Notify(rebuild) if rebuild then ClearOptionCaches() end if rebuild and raidTimelineOptionsGroup then local fresh = RT:GetOptionsGroup() raidTimelineOptionsGroup.name = fresh.name raidTimelineOptionsGroup.order = fresh.order raidTimelineOptionsGroup.childGroups = fresh.childGroups raidTimelineOptionsGroup.args = fresh.args end AceConfigRegistry:NotifyChange(ADDON_NAME) end local function GetDrafts() HMGT._raidTimelineDraft = HMGT._raidTimelineDraft or { addEncounterId = "", addEncounterName = "", entries = {}, } HMGT._raidTimelineDraft.entries = HMGT._raidTimelineDraft.entries or {} return HMGT._raidTimelineDraft end local function TrimText(value) return tostring(value or ""):gsub("^%s+", ""):gsub("%s+$", "") end local function GetEncounterDraft(encounterId) local drafts = GetDrafts() local key = tostring(tonumber(encounterId) or encounterId or "") drafts.entries[key] = drafts.entries[key] or { time = "", spellId = 0, alertText = "", playerName = "", entryType = "spell", triggerType = "time", actionType = "raidCooldown", targetSpec = "", bossAbilityId = "", bossAbilityBarName = "", castCount = "1", } return drafts.entries[key] end local function GetTriggerTypeValues() return (RT.GetTriggerTypeValues and RT:GetTriggerTypeValues()) or { time = L["OPT_RT_TRIGGER_TIME"] or "Time", bossAbility = L["OPT_RT_TRIGGER_BOSS_ABILITY"] or "Boss ability", } end local function GetActionTypeValues() return (RT.GetActionTypeValues and RT:GetActionTypeValues()) or { text = L["OPT_RT_ACTION_TEXT"] or "Text", raidCooldown = L["OPT_RT_ACTION_RAID_COOLDOWN"] or "Raid Cooldown", } end local function GetEncounterEntry(encounterId, row) local encounter = encounterId and RT:GetEncounter(encounterId) return encounter and encounter.entries and encounter.entries[row] or nil end local function GetTargetFieldLabel(kind, isAddRow) return (isAddRow and (L["OPT_RT_ADD_PLAYER"] or "Target")) or (L["OPT_RT_ENTRY_PLAYER"] or "Target") end local function GetTargetFieldDesc(kind) return L["OPT_RT_ADD_PLAYER_DESC"] or "Optional. Comma-separated player names or variables like Group1, Group8, GroupEven, GroupOdd." end local function FormatEntryTime(value) return RT.FormatTimelineClock and RT:FormatTimelineClock(value) or tostring(value or "") end local function GetBossAbilityValues(encounterId) local encounterKey = tostring(tonumber(encounterId) or encounterId or 0) if bossAbilityValuesCache[encounterKey] then return bossAbilityValuesCache[encounterKey] end local values = RT.GetBossAbilityValues and RT:GetBossAbilityValues(encounterId) or { [""] = L["OPT_RT_NO_BOSS_ABILITY"] or "No boss ability" } bossAbilityValuesCache[encounterKey] = values return values end local function GetDraftTriggerType(encounterId) local draft = GetEncounterDraft(encounterId) draft.triggerType = tostring(draft.triggerType or "time") return draft.triggerType == "bossAbility" and "bossAbility" or "time" end local function GetDraftActionType(encounterId) local draft = GetEncounterDraft(encounterId) draft.actionType = tostring(draft.actionType or "raidCooldown") return draft.actionType == "text" and "text" or "raidCooldown" end local function GetSpellName(spellId) local sid = tonumber(spellId) if not sid or sid <= 0 then return nil end if C_Spell and type(C_Spell.GetSpellName) == "function" then local name = C_Spell.GetSpellName(sid) if type(name) == "string" and name ~= "" then return name end end if type(GetSpellInfo) == "function" then local name = GetSpellInfo(sid) if type(name) == "string" and name ~= "" then return name end end return nil end local function GetSpellIcon(spellId) local sid = tonumber(spellId) if not sid or sid <= 0 then return nil end if HMGT_SpellData and type(HMGT_SpellData.GetSpellIcon) == "function" then local icon = HMGT_SpellData.GetSpellIcon(sid) if icon and icon ~= "" then return icon end end if C_Spell and type(C_Spell.GetSpellTexture) == "function" then local icon = C_Spell.GetSpellTexture(sid) if icon and icon ~= "" then return icon end end local _, _, icon = GetSpellInfo(sid) return icon end local function GetRaidCooldownSpellValues() if raidCooldownSpellValuesCache then return raidCooldownSpellValuesCache end local values = { [0] = L["OPT_RT_NO_SPELL"] or "No spell" } local seen = { [0] = true } for _, entry in ipairs(HMGT_SpellData and HMGT_SpellData.RaidCooldowns or {}) do local spellId = tonumber(entry and entry.spellId) if spellId and spellId > 0 and not seen[spellId] then seen[spellId] = true local spellName = GetSpellName(spellId) or tostring(entry.name or ("Spell " .. spellId)) local icon = GetSpellIcon(spellId) values[spellId] = icon and icon ~= "" and string.format("|T%s:16:16:0:0|t %s (%d)", tostring(icon), spellName, spellId) or string.format("%s (%d)", spellName, spellId) end end raidCooldownSpellValuesCache = values return values end local function GetDifficultyLabel(key) if key == "lfr" then return L["OPT_RT_DIFF_LFR"] or "LFR" end if key == "heroic" then return L["OPT_RT_DIFF_HEROIC"] or "HC" end if key == "mythic" then return L["OPT_RT_DIFF_MYTHIC"] or "Mythic" end return L["OPT_RT_DIFF_NORMAL"] or "Normal" end local function GetEncounterLabel(encounterId) local encounter = RT:GetEncounter(encounterId) local name = TrimText(encounter and encounter.name or "") if name == "" then name = L["OPT_RT_ENCOUNTER"] or "Encounter" end return string.format("%s (%d)", name, tonumber(encounterId) or 0) end local function GetBossTreeLabel(encounterId) local encounter = RT:GetEncounter(encounterId) local name = TrimText(encounter and encounter.name or "") if name == "" then name = L["OPT_RT_ENCOUNTER"] or "Encounter" end return string.format("%s (%d)", name, #(encounter and encounter.entries or {})) end local function GetRaidBuckets() local buckets, raidNames = {}, {} for _, encounterId in ipairs(RT:GetEncounterIds()) do local encounter = RT:GetEncounter(encounterId) if encounter then local journalInstanceId, instanceName = RT:GetEncounterInstanceInfo(encounterId) local raidName = tostring(encounter.instanceName or instanceName or (L["OPT_RT_RAID_DEFAULT"] or "Encounter")) if not buckets[raidName] then buckets[raidName] = { key = tostring(journalInstanceId or raidName), ids = {}, difficulties = { lfr = {}, normal = {}, heroic = {}, mythic = {} }, } table.insert(raidNames, raidName) end table.insert(buckets[raidName].ids, encounterId) for _, difficultyKey in ipairs(DIFFICULTY_KEYS) do if encounter.difficulties and encounter.difficulties[difficultyKey] == true then table.insert(buckets[raidName].difficulties[difficultyKey], encounterId) end end end end table.sort(raidNames) for _, raidName in ipairs(raidNames) do table.sort(buckets[raidName].ids) for _, difficultyKey in ipairs(DIFFICULTY_KEYS) do table.sort(buckets[raidName].difficulties[difficultyKey]) end end return raidNames, buckets end local function BuildEntryEditorArgs(encounterId) local args = { entriesHeader = { type = "header", order = 20, name = L["OPT_RT_ENCOUNTERS_HEADER"] or "Encounter timelines", }, addTime = { type = "input", order = 21, width = 0.7, name = L["OPT_RT_ADD_TIME"] or "Time (MM:SS)", disabled = function() return not RT:IsLocalEditor() end, get = function() return tostring(GetEncounterDraft(encounterId).time or "") end, set = function(_, val) GetEncounterDraft(encounterId).time = val end, hidden = function() return GetDraftTriggerType(encounterId) == "bossAbility" end, }, addTriggerType = { type = "select", order = 22, width = 0.8, name = L["OPT_RT_TRIGGER"] or "Trigger", disabled = function() return not RT:IsLocalEditor() end, values = GetTriggerTypeValues, get = function() return GetDraftTriggerType(encounterId) end, set = function(_, val) local draft = GetEncounterDraft(encounterId) draft.triggerType = (tostring(val or "") == "bossAbility") and "bossAbility" or "time" if draft.triggerType == "time" then draft.bossAbilityId = "" draft.bossAbilityBarName = "" draft.castCount = "1" end Notify(false) end, }, addActionType = { type = "select", order = 22.1, width = 1.0, name = L["OPT_RT_ACTION"] or "Action", disabled = function() return not RT:IsLocalEditor() end, values = GetActionTypeValues, get = function() return GetDraftActionType(encounterId) end, set = function(_, val) local draft = GetEncounterDraft(encounterId) draft.actionType = (tostring(val or "") == "text") and "text" or "raidCooldown" if draft.actionType == "text" then draft.spellId = 0 end Notify(false) end, }, addSpellId = { type = "select", order = 23, width = 1.2, name = L["OPT_RT_ADD_SPELL"] or "Spell", disabled = function() return not RT:IsLocalEditor() end, values = GetRaidCooldownSpellValues, get = function() return math.max(0, tonumber(GetEncounterDraft(encounterId).spellId) or 0) end, set = function(_, val) GetEncounterDraft(encounterId).spellId = math.max(0, tonumber(val) or 0) end, hidden = function() return GetDraftActionType(encounterId) ~= "raidCooldown" end, }, addCastCount = { type = "input", order = 23.1, width = 0.7, name = L["OPT_RT_CAST_COUNT"] or "Cast count", disabled = function() return not RT:IsLocalEditor() end, desc = L["OPT_RT_CAST_COUNT_DESC"] or "Use a number, All, Odd, or Even.", get = function() return RT:FormatCastCountSelector(GetEncounterDraft(encounterId).castCount or "1") end, set = function(_, val) GetEncounterDraft(encounterId).castCount = RT:NormalizeCastCountSelector(val) end, hidden = function() return GetDraftTriggerType(encounterId) ~= "bossAbility" end, }, addBossAbilityBarName = { type = "input", order = 23.2, width = 1.2, name = L["OPT_RT_BOSS_BAR_NAME"] or "Bossmod bar name", disabled = function() return not RT:IsLocalEditor() end, get = function() return tostring(GetEncounterDraft(encounterId).bossAbilityBarName or "") end, set = function(_, val) GetEncounterDraft(encounterId).bossAbilityBarName = tostring(val or "") end, hidden = function() return GetDraftTriggerType(encounterId) ~= "bossAbility" end, }, addAlertText = { type = "input", order = 24, width = 1.2, name = L["OPT_RT_ADD_TEXT"] or "Custom text", disabled = function() return not RT:IsLocalEditor() end, get = function() return tostring(GetEncounterDraft(encounterId).alertText or "") end, set = function(_, val) GetEncounterDraft(encounterId).alertText = tostring(val or "") end, hidden = function() return GetDraftActionType(encounterId) ~= "text" end, }, addPlayerName = { type = "input", order = 25, width = 1.2, name = function() return GetTargetFieldLabel(GetDraftActionType(encounterId), true) end, desc = function() return GetTargetFieldDesc(GetDraftActionType(encounterId)) end, disabled = function() return not RT:IsLocalEditor() end, get = function() local draft = GetEncounterDraft(encounterId) return tostring(draft.playerName or draft.targetSpec or "") end, set = function(_, val) local draft = GetEncounterDraft(encounterId) draft.playerName = tostring(val or "") draft.targetSpec = tostring(val or "") end, }, addEntry = { type = "execute", order = 26, width = "full", name = L["OPT_RT_ADD_ENTRY"] or "Add entry", disabled = function() return not RT:IsLocalEditor() end, func = function() local draft = GetEncounterDraft(encounterId) local ok = RT:AddDetailedEntry(encounterId, draft) if ok then draft.time, draft.spellId, draft.alertText, draft.playerName, draft.entryType, draft.triggerType, draft.actionType, draft.targetSpec, draft.bossAbilityId, draft.bossAbilityBarName, draft.castCount = "", 0, "", "", "spell", "time", "raidCooldown", "", "", "", "1" Notify(true) else HMGT:Print(L["OPT_RT_ADD_ENTRY_INVALID"] or "HMGT: invalid raid timeline entry") end end, }, addBreak = { type = "description", order = 27, width = "full", name = " " }, } for entryRow = 1, MAX_ENTRY_ROWS do local order = 40 + (entryRow * 10) args["entryTime_" .. entryRow] = { type = "input", order = order, width = 0.7, disabled = function() return not RT:IsLocalEditor() end, name = function() return entryRow == 1 and (L["OPT_RT_ENTRY_TIME"] or "Time") or "" end, get = function() local entry = GetEncounterEntry(encounterId, entryRow); return entry and FormatEntryTime(entry.time or 0) or "" end, set = function(_, val) if not RT:SetEntryField(encounterId, entryRow, "time", val) then HMGT:Print(L["OPT_RT_INVALID_TIME"] or "HMGT: invalid time") end Notify(false) end, hidden = function() local entry = GetEncounterEntry(encounterId, entryRow) return not entry or RT:GetTriggerType(entry) == "bossAbility" end, } args["entryTriggerType_" .. entryRow] = { type = "select", order = order + 1, width = 0.8, disabled = function() return not RT:IsLocalEditor() end, name = function() return entryRow == 1 and (L["OPT_RT_TRIGGER"] or "Trigger") or "" end, values = GetTriggerTypeValues, get = function() local entry = GetEncounterEntry(encounterId, entryRow); return entry and RT:GetTriggerType(entry) or "time" end, set = function(_, val) RT:SetEntryField(encounterId, entryRow, "triggerType", val); Notify(false) end, hidden = function() return GetEncounterEntry(encounterId, entryRow) == nil end, } args["entryActionType_" .. entryRow] = { type = "select", order = order + 1.1, width = 1.0, disabled = function() return not RT:IsLocalEditor() end, name = function() return entryRow == 1 and (L["OPT_RT_ACTION"] or "Action") or "" end, values = GetActionTypeValues, get = function() local entry = GetEncounterEntry(encounterId, entryRow); return entry and RT:GetActionType(entry) or "raidCooldown" end, set = function(_, val) RT:SetEntryField(encounterId, entryRow, "actionType", val); Notify(false) end, hidden = function() return GetEncounterEntry(encounterId, entryRow) == nil end, } args["entrySpell_" .. entryRow] = { type = "select", order = order + 2, width = 1.2, disabled = function() return not RT:IsLocalEditor() end, name = function() return entryRow == 1 and (L["OPT_RT_ENTRY_SPELL"] or "Spell") or "" end, values = GetRaidCooldownSpellValues, get = function() local entry = GetEncounterEntry(encounterId, entryRow); return entry and math.max(0, tonumber(entry.spellId) or 0) or 0 end, set = function(_, val) if not RT:SetEntryField(encounterId, entryRow, "spellId", math.max(0, tonumber(val) or 0)) then HMGT:Print(L["OPT_RT_INVALID_SPELL"] or "HMGT: invalid spell ID") end Notify(false) end, hidden = function() local entry = GetEncounterEntry(encounterId, entryRow); return not entry or RT:GetActionType(entry) ~= "raidCooldown" end, } args["entryCastCount_" .. entryRow] = { type = "input", order = order + 2.1, width = 0.7, disabled = function() return not RT:IsLocalEditor() end, name = function() return entryRow == 1 and (L["OPT_RT_CAST_COUNT"] or "Cast count") or "" end, desc = L["OPT_RT_CAST_COUNT_DESC"] or "Use a number, All, Odd, or Even.", get = function() local entry = GetEncounterEntry(encounterId, entryRow); return entry and RT:FormatCastCountSelector(entry.castCount) or "1" end, set = function(_, val) RT:SetEntryField(encounterId, entryRow, "castCount", val); Notify(false) end, hidden = function() local entry = GetEncounterEntry(encounterId, entryRow); return not entry or RT:GetTriggerType(entry) ~= "bossAbility" end, } args["entryBossAbilityBarName_" .. entryRow] = { type = "input", order = order + 2.2, width = 1.2, disabled = function() return not RT:IsLocalEditor() end, name = function() return entryRow == 1 and (L["OPT_RT_BOSS_BAR_NAME"] or "Bossmod bar name") or "" end, get = function() local entry = GetEncounterEntry(encounterId, entryRow); return entry and tostring(entry.bossAbilityBarName or "") or "" end, set = function(_, val) RT:SetEntryField(encounterId, entryRow, "bossAbilityBarName", val); Notify(false) end, hidden = function() local entry = GetEncounterEntry(encounterId, entryRow); return not entry or RT:GetTriggerType(entry) ~= "bossAbility" end, } args["entryText_" .. entryRow] = { type = "input", order = order + 3, width = 1.2, disabled = function() return not RT:IsLocalEditor() end, name = function() return entryRow == 1 and (L["OPT_RT_ENTRY_TEXT"] or "Custom text") or "" end, get = function() local entry = GetEncounterEntry(encounterId, entryRow); return entry and tostring(entry.alertText or "") or "" end, set = function(_, val) RT:SetEntryField(encounterId, entryRow, "alertText", val); Notify(false) end, hidden = function() local entry = GetEncounterEntry(encounterId, entryRow) if not entry then return true end return RT:GetActionType(entry) ~= "text" end, } args["entryPlayer_" .. entryRow] = { type = "input", order = order + 4, width = 1.1, disabled = function() return not RT:IsLocalEditor() end, name = function() local entry = GetEncounterEntry(encounterId, entryRow) return entryRow == 1 and GetTargetFieldLabel(entry and RT:GetActionType(entry) or "raidCooldown", false) or "" end, desc = function() local entry = GetEncounterEntry(encounterId, entryRow) return GetTargetFieldDesc(entry and RT:GetActionType(entry) or "raidCooldown") end, get = function() local entry = GetEncounterEntry(encounterId, entryRow) if not entry then return "" end return tostring(entry.playerName or entry.targetSpec or "") end, set = function(_, val) local entry = GetEncounterEntry(encounterId, entryRow) if entry then RT:SetEntryField(encounterId, entryRow, "playerName", val) RT:SetEntryField(encounterId, entryRow, "targetSpec", val) Notify(false) end end, hidden = function() return GetEncounterEntry(encounterId, entryRow) == nil end, } args["entryDelete_" .. entryRow] = { type = "execute", order = order + 5, width = 0.6, name = REMOVE or "Remove", disabled = function() return not RT:IsLocalEditor() end, func = function() RT:RemoveEntry(encounterId, entryRow); Notify(true) end, hidden = function() return GetEncounterEntry(encounterId, entryRow) == nil end, } args["entryBreak_" .. entryRow] = { type = "description", order = order + 6, width = "full", name = " ", hidden = function() return GetEncounterEntry(encounterId, entryRow) == nil end, } end return args end local function BuildEncounterDetailGroup(encounterId) local encounter = RT:GetEncounter(encounterId) if not encounter then return nil end local args = { header = { type = "header", order = 1, name = GetEncounterLabel(encounterId) }, encounterId = { type = "description", order = 2, width = "full", name = string.format("|cffffd100%s|r: %d", L["OPT_RT_ADD_ENCOUNTER_ID"] or "Encounter ID", tonumber(encounterId) or 0), }, raidName = { type = "description", order = 3, width = "full", name = function() local _, instanceName = RT:GetEncounterInstanceInfo(encounterId) local current = RT:GetEncounter(encounterId) return string.format("|cffffd100%s|r: %s", L["OPT_RT_RAID_NAME"] or "Raid", tostring((current and current.instanceName) or instanceName or (L["OPT_RT_RAID_DEFAULT"] or "Encounter"))) end, }, encounterName = { type = "input", order = 4, width = "full", name = L["OPT_RT_ENCOUNTER_NAME"] or "Name", disabled = function() return not RT:IsLocalEditor() end, get = function() local current = RT:GetEncounter(encounterId); return current and tostring(current.name or "") or "" end, set = function(_, val) local current = RT:GetEncounter(encounterId); if current then current.name = tostring(val or ""); Notify(true) end end, }, difficultyHeader = { type = "header", order = 5, name = L["OPT_RT_DIFFICULTY_HEADER"] or "Difficulties" }, } local diffOrder = 6 for _, difficultyKey in ipairs(DIFFICULTY_KEYS) do args["difficulty_" .. difficultyKey] = { type = "toggle", order = diffOrder, width = 0.75, name = GetDifficultyLabel(difficultyKey), disabled = function() return not RT:IsLocalEditor() end, get = function() local current = RT:GetEncounter(encounterId) local difficulties = current and current.difficulties or nil return difficulties and difficulties[difficultyKey] ~= false or false end, set = function(_, val) local current = RT:GetEncounter(encounterId) if current then current.difficulties = current.difficulties or {} current.difficulties[difficultyKey] = val and true or false Notify(true) end end, } diffOrder = diffOrder + 0.01 end args.openTimelineEditor = { type = "execute", order = 7, width = "full", name = L["OPT_RT_OPEN_EDITOR"] or "Open timeline", func = function() if RT.OpenTimelineEditor then RT:OpenTimelineEditor(encounterId) end end, } args.runTest = { type = "execute", order = 7.5, width = "full", name = function() if RT:IsTestRunning(encounterId) then return L["OPT_RT_STOP_TEST"] or "Stop test" end return L["OPT_RT_START_TEST"] or "Start timeline test" end, disabled = function() return not RT:IsLocalEditor() end, func = function() if RT:IsTestRunning(encounterId) then RT:StopEncounterTest() else RT:StartEncounterTest(encounterId) end Notify() end, } args.testHint = { type = "description", order = 7.6, width = "full", name = L["OPT_RT_TEST_HINT"] or "Runs the encounter timeline outside of combat so you can verify assignments, whispers and debug output.", } args.editorHint = { type = "description", order = 8, width = "full", name = L["OPT_RT_TIMELINE_HINT"] or "Click the bar to add a cooldown. Drag markers horizontally to change the time. Mousewheel scrolls, Ctrl+Mousewheel zooms.", } args.encounterDelete = { type = "execute", order = 9, width = "full", name = DELETE or "Delete", disabled = function() return not RT:IsLocalEditor() end, confirm = function() return string.format(L["OPT_RT_DELETE_ENCOUNTER_CONFIRM"] or "Delete raid timeline for encounter %d?", tonumber(encounterId) or 0) end, func = function() RT:RemoveEncounter(encounterId); Notify(true) end, } args.entryBreak = { type = "description", order = 10, width = "full", name = " " } for key, value in pairs(BuildEntryEditorArgs(encounterId)) do args[key] = value end return { type = "group", name = GetBossTreeLabel(encounterId), args = args } end local function BuildRaidTreeArgs() local args = {} local raidNames, buckets = GetRaidBuckets() for raidIndex, raidName in ipairs(raidNames) do local raidKey = tostring(buckets[raidName].key or raidIndex) args["raid_" .. raidKey] = { type = "group", order = 100 + raidIndex, name = raidName, childGroups = "tree", args = { description = { type = "description", order = 1, width = "full", name = string.format("|cffffd100%s|r: %s", L["OPT_RT_RAID_NAME"] or "Raid", raidName), }, raidId = { type = "description", order = 2, width = "full", name = string.format("|cffffd100%s|r: %s", L["OPT_RT_RAID_ID"] or "Raid ID", tostring(buckets[raidName].key or raidIndex)), }, }, } local addedEncounterIds = {} local bossOrder = 10 for _, difficultyKey in ipairs(DIFFICULTY_KEYS) do local encounterIds = buckets[raidName].difficulties[difficultyKey] or {} for _, encounterId in ipairs(encounterIds) do if not addedEncounterIds[encounterId] then local encounterGroup = BuildEncounterDetailGroup(encounterId) if encounterGroup then encounterGroup.order = bossOrder args["raid_" .. raidKey].args["boss_" .. tostring(encounterId)] = encounterGroup bossOrder = bossOrder + 1 addedEncounterIds[encounterId] = true end end end end end return args, #raidNames end function RT:GetOptionsGroup() local drafts = GetDrafts() local raidTreeArgs, raidCount = BuildRaidTreeArgs() local group = { type = "group", name = L["OPT_RT_NAME"] or "Raid Timeline", order = 4, childGroups = "tree", args = { general = { type = "group", order = 1, name = L["OPT_RT_SECTION_GENERAL"] or "General", args = { enabled = { type = "toggle", order = 1, width = "full", name = L["OPT_RT_ENABLED"] or "Enable Raid Timeline", get = function() return RT:GetSettings().enabled == true end, set = function(_, val) RT:GetSettings().enabled = val and true or false if val then RT:Enable() else RT:Disable() end end, }, leadTime = { type = "range", order = 2, min = 1, max = 15, step = 1, name = L["OPT_RT_LEAD_TIME"] or "Warning lead time", get = function() return tonumber(RT:GetSettings().leadTime) or 5 end, set = function(_, val) RT:GetSettings().leadTime = math.floor((tonumber(val) or 5) + 0.5) end, }, assignmentLeadTime = { type = "range", order = 2.1, min = 0, max = 60, step = 1, name = L["OPT_RT_ASSIGNMENT_LEAD_TIME"] or "Assignment lead time", desc = L["OPT_RT_ASSIGNMENT_LEAD_TIME_DESC"] or "How many seconds before the planned use the assigned player should be selected.", get = function() local settings = RT:GetSettings() return tonumber(settings.assignmentLeadTime) or tonumber(settings.leadTime) or 5 end, set = function(_, val) RT:GetSettings().assignmentLeadTime = math.floor((tonumber(val) or 5) + 0.5) end, }, header = { type = "header", order = 3, name = L["OPT_RT_ALERT_HEADER"] or "Alert frame" }, unlocked = { type = "toggle", order = 4, width = "double", name = L["OPT_RT_UNLOCK"] or "Unlock alert frame", get = function() return RT:GetSettings().unlocked == true end, set = function(_, val) RT:GetSettings().unlocked = val and true or false RT:ApplyAlertStyle() if val then RT:ShowPreview() end end, }, preview = { type = "toggle", order = 5, width = "double", name = L["OPT_RT_PREVIEW"] or "Preview alert", get = function() return RT.previewAlertActive == true end, set = function(_, val) if val then RT:ShowPreview() else RT:HideAlert() end end, }, alertFont = { type = "select", order = 6, width = 1.4, name = L["OPT_FONT"] or "Typeface", dialogControl = "LSM30_Font", values = AceGUIWidgetLSMlists and AceGUIWidgetLSMlists.font or (LSM and LSM:HashTable("font")) or {}, get = function() return RT:GetSettings().alertFont or "Friz Quadrata TT" end, set = function(_, val) RT:GetSettings().alertFont = val; RT:ApplyAlertStyle() end, }, alertFontSize = { type = "range", order = 7, min = 10, max = 72, step = 1, width = 1.0, name = L["OPT_FONT_SIZE"] or "Font size", get = function() return tonumber(RT:GetSettings().alertFontSize) or 30 end, set = function(_, val) RT:GetSettings().alertFontSize = math.floor((tonumber(val) or 30) + 0.5); RT:ApplyAlertStyle() end, }, alertFontOutline = { type = "select", order = 8, width = 1.0, name = L["OPT_FONT_OUTLINE"] or "Font outline", values = FONT_OUTLINE_VALUES, get = function() return RT:GetSettings().alertFontOutline or "OUTLINE" end, set = function(_, val) RT:GetSettings().alertFontOutline = val; RT:ApplyAlertStyle() end, }, alertColor = { type = "color", order = 9, width = 0.8, name = L["OPT_RT_ALERT_COLOR"] or "Text colour", hasAlpha = true, get = function() local color = RT:GetSettings().alertColor or {} return color.r or 1, color.g or 0.82, color.b or 0.15, color.a or 1 end, set = function(_, r, g, b, a) local color = RT:GetSettings().alertColor color.r, color.g, color.b, color.a = r, g, b, a RT:ApplyAlertStyle() end, }, desc = { type = "description", order = 10, width = "full", name = L["OPT_RT_DESC"] or "Create encounter timelines here and open the interactive Ace3 timeline editor for visual planning.", }, }, }, manage = { type = "group", order = 2, name = L["OPT_RT_SECTION_MANAGE"] or "Manage encounters", args = { header = { type = "header", order = 1, name = L["OPT_RT_ENCOUNTERS_HEADER"] or "Encounter timelines" }, addEncounterId = { type = "input", order = 2, width = 0.8, name = L["OPT_RT_ADD_ENCOUNTER_ID"] or "Encounter ID", disabled = function() return not RT:IsLocalEditor() end, get = function() return tostring(drafts.addEncounterId or "") end, set = function(_, val) drafts.addEncounterId = val end, }, addEncounterName = { type = "input", order = 3, width = 1.2, name = L["OPT_RT_ADD_ENCOUNTER_NAME"] or "Encounter name", disabled = function() return not RT:IsLocalEditor() end, get = function() return tostring(drafts.addEncounterName or "") end, set = function(_, val) drafts.addEncounterName = val end, }, addEncounter = { type = "execute", order = 5, width = "full", name = L["OPT_RT_ADD_ENCOUNTER"] or "Add encounter", disabled = function() return not RT:IsLocalEditor() end, func = function() if RT:AddEncounter(drafts.addEncounterId, drafts.addEncounterName) then drafts.addEncounterId, drafts.addEncounterName = "", "" Notify(true) else HMGT:Print(L["OPT_RT_INVALID_ENCOUNTER"] or "HMGT: invalid encounter ID") end end, }, summary = { type = "description", order = 6, width = "full", name = function() if raidCount == 0 then return L["OPT_RT_EMPTY"] or "No encounter timelines configured yet." end return string.format("|cffffd100%s|r: %d", L["OPT_RT_ENCOUNTERS_HEADER"] or "Encounter timelines", #RT:GetEncounterIds()) end, }, }, }, }, } if raidCount == 0 then group.args.empty = { type = "group", order = 50, name = L["OPT_RT_EMPTY"] or "No encounter timelines configured yet.", args = { description = { type = "description", order = 1, width = "full", name = L["OPT_RT_EMPTY"] or "No encounter timelines configured yet." }, }, } else for key, value in pairs(raidTreeArgs) do group.args[key] = value end end return group end HMGT_Config:RegisterOptionsProvider("raidTimeline", function() raidTimelineOptionsGroup = raidTimelineOptionsGroup or RT:GetOptionsGroup() ClearOptionCaches() local fresh = RT:GetOptionsGroup() raidTimelineOptionsGroup.name = fresh.name raidTimelineOptionsGroup.order = fresh.order raidTimelineOptionsGroup.childGroups = fresh.childGroups raidTimelineOptionsGroup.args = fresh.args return { path = "raidTimeline", order = 4, group = raidTimelineOptionsGroup, } end)