800 lines
38 KiB
Lua
800 lines
38 KiB
Lua
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)
|