local function MTH_CHRON_GetGlobal(name) if type(getglobal) == "function" then return getglobal(name) end if _G then return _G[name] end return nil end local function MTH_CHRON_IsReady() local required = { "MTH_GetFrame", "MTH_ClearContainer", "MTH_CreateCheckbox", "MTH_CreateSlider", "MTH_CreateActionButton", } for i = 1, table.getn(required) do if MTH_CHRON_GetGlobal(required[i]) == nil then return false end end return true end local MTH_CHRON_STATE = { controls = {}, } local MTH_CHRON_DEFAULTS = { growup = false, reverse = false, fadeonkill = true, fadeonfade = true, barposition = {}, ghost = 0, selfbars = true, barwidth = 220, barheight = 16, spacing = 0, barscale = 1, iconposition = "LEFT", textsize = 10, textcolor = "white", bgcolor = "teal", barcolor = "gray", bordercolor = "black", bordertex = "None", bgalpha = 0.5, text = "$t", onlyself = false, disabledSpells = { COMMON = { ["Ephemeral Power"] = {}, ["Mind Quickening"] = {}, ["Unstable Power"] = {}, }, RACIAL = {}, HUNTER = {}, }, } local function MTH_CHRON_SeedDefaultCommonDisabled(commonBucket) if type(commonBucket) ~= "table" then return end local names = { "Ephemeral Power", "Mind Quickening", "Unstable Power", } for i = 1, table.getn(names) do local baseName = names[i] if commonBucket[baseName] == nil then commonBucket[baseName] = {} end if MTH and MTH.LocalizeSpell then local localized = MTH:LocalizeSpell(baseName) if localized and localized ~= "" and commonBucket[localized] == nil then commonBucket[localized] = {} end end end end local function MTH_CHRON_CopyTable(source) local target = {} for key, value in pairs(source) do if type(value) == "table" then target[key] = MTH_CHRON_CopyTable(value) else target[key] = value end end return target end local function MTH_CHRON_GetProfile() if not (MTH and MTH.GetModuleCharSavedVariables) then return MTH_CHRON_CopyTable(MTH_CHRON_DEFAULTS) end local store = MTH:GetModuleCharSavedVariables("chronometer") if type(store) ~= "table" then return MTH_CHRON_CopyTable(MTH_CHRON_DEFAULTS) end if type(store.profile) ~= "table" and MTH.GetModuleSavedVariables then local accountStore = MTH:GetModuleSavedVariables("chronometer") if type(accountStore) == "table" and type(accountStore.profile) == "table" then store.profile = MTH_CHRON_CopyTable(accountStore.profile) end end if type(store.profile) ~= "table" then store.profile = {} end local profile = store.profile for key, value in pairs(MTH_CHRON_DEFAULTS) do if profile[key] == nil then if type(value) == "table" then profile[key] = MTH_CHRON_CopyTable(value) else profile[key] = value end end end if type(profile.disabledSpells) ~= "table" then profile.disabledSpells = {} end if type(profile.disabledSpells.COMMON) ~= "table" then profile.disabledSpells.COMMON = {} end MTH_CHRON_SeedDefaultCommonDisabled(profile.disabledSpells.COMMON) if type(profile.disabledSpells.RACIAL) ~= "table" then profile.disabledSpells.RACIAL = {} end local _, class = UnitClass("player") class = class or "HUNTER" if type(profile.disabledSpells[class]) ~= "table" then profile.disabledSpells[class] = {} end return profile end local function MTH_CHRON_GetTimerDefinitions() local engine = MTH_ChronometerHunter if engine and engine.timers and (engine.timers[engine.SPELL] or engine.timers[engine.EVENT]) then return engine.timers, engine.SPELL, engine.EVENT end if not engine then return nil, 1, 2 end local fake = { SPELL = 1, EVENT = 2, groups = {}, timers = {}, } function fake:AddGroup(id, forall, color) if color then self.groups[id] = { fa = forall, cr = color } else self.groups[id] = { fa = forall } end end function fake:AddTimer(kind, name, duration, targeted, isgain, selforselect, extra) if type(self.timers[kind]) ~= "table" then self.timers[kind] = {} end if type(extra) ~= "table" then extra = {} end self.timers[kind][name] = { d = duration, k = { t = targeted, g = isgain, s = selforselect }, x = extra, } end if type(engine.dataSetup) == "table" then for _, setupFunc in pairs(engine.dataSetup) do if type(setupFunc) == "function" then setupFunc(fake) end end end return fake.timers, fake.SPELL, fake.EVENT end local function MTH_CHRON_SortKeys(list) table.sort(list, function(a, b) return tostring(a) < tostring(b) end) end local function MTH_CHRON_BuildTimerLists() local timers, spellKind, eventKind = MTH_CHRON_GetTimerDefinitions() local classSpells, classEvents, racial = {}, {}, {} if type(timers) ~= "table" then return classSpells, classEvents, racial end local function classify(timerRecord) if timerRecord and timerRecord.x and timerRecord.x.cl == "COMMON" then return "COMMON" elseif timerRecord and timerRecord.x and timerRecord.x.cl == "RACIAL" then return "RACIAL" end local _, class = UnitClass("player") return class or "HUNTER" end for timerName, timerRecord in pairs(timers[spellKind] or {}) do local bucket = classify(timerRecord) if bucket == "RACIAL" then table.insert(racial, timerName) elseif bucket ~= "COMMON" then table.insert(classSpells, timerName) end end for timerName, timerRecord in pairs(timers[eventKind] or {}) do local bucket = classify(timerRecord) if bucket == "RACIAL" then table.insert(racial, timerName) elseif bucket ~= "COMMON" then table.insert(classEvents, timerName) end end MTH_CHRON_SortKeys(classSpells) MTH_CHRON_SortKeys(classEvents) MTH_CHRON_SortKeys(racial) return classSpells, classEvents, racial end local function MTH_CHRON_ApplyLiveProfile(profile) if not profile then return end local engine = MTH_ChronometerHunter if not (engine and engine._mth_enabled) then return end engine.profile = profile if engine.SetCandyBarGroupGrowth and engine.SetCandyBarGroupPoint and engine.anchor then local growup = profile.growup and true or false if growup then engine:SetCandyBarGroupPoint("MTHChronometer", "BOTTOM", engine.anchor, "TOP", 0, 0) else engine:SetCandyBarGroupPoint("MTHChronometer", "TOP", engine.anchor, "BOTTOM", 0, 0) end engine:SetCandyBarGroupGrowth("MTHChronometer", growup) end if engine.SetCandyBarGroupVerticalSpacing then engine:SetCandyBarGroupVerticalSpacing("MTHChronometer", profile.spacing or 0) end if type(engine.bars) == "table" then for i = 1, 20 do local bar = engine.bars[i] if bar and bar.id then if profile.barscale and engine.SetCandyBarScale then engine:SetCandyBarScale(bar.id, profile.barscale) end if profile.barwidth and engine.SetCandyBarWidth then engine:SetCandyBarWidth(bar.id, profile.barwidth) end if profile.barheight and engine.SetCandyBarHeight then engine:SetCandyBarHeight(bar.id, profile.barheight) end if profile.iconposition and engine.SetCandyBarIconPosition then engine:SetCandyBarIconPosition(bar.id, profile.iconposition) end if profile.textsize and engine.SetCandyBarFontSize then engine:SetCandyBarFontSize(bar.id, profile.textsize) end if profile.text and engine.SetCandyBarText then local target = bar.target or "none" local text = target == "none" and (bar.name or "") or profile.text text = string.gsub(tostring(text or ""), "$t", tostring(target)) text = string.gsub(tostring(text or ""), "$s", tostring(bar.name or "")) engine:SetCandyBarText(bar.id, text) end if engine.SetCandyBarReversed then engine:SetCandyBarReversed(bar.id, profile.reverse and true or false) end end end end end local function MTH_CHRON_MakeTitle(parent, name, text, x, y) local label = parent:CreateFontString(name, "ARTWORK", "GameFontHighlight") label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) label:SetText(text) return label end local function MTH_CHRON_MakeSmall(parent, name, text, x, y) local label = parent:CreateFontString(name, "ARTWORK", "GameFontNormalSmall") label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) label:SetText(text) return label end local function MTH_CHRON_IsChecked(control) if not control or type(control.GetChecked) ~= "function" then return false end local checked = control:GetChecked() return checked == 1 or checked == true end local function MTH_CHRON_MakeInput(parent, name, x, y, width, height, value, onAccept) local edit = CreateFrame("EditBox", name, parent, "InputBoxTemplate") if not edit then return nil end edit:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) edit:SetWidth(width or 200) edit:SetHeight(height or 20) edit:SetAutoFocus(false) edit:SetText(tostring(value or "")) edit:SetScript("OnEnterPressed", function() if not this then return end if type(onAccept) == "function" then onAccept(this:GetText() or "") end this:ClearFocus() end) return edit end local function MTH_CHRON_NormalizeSection(tabKey) if not tabKey and _G then tabKey = _G.MTH_CHRON_OPTIONS_SECTION end if tabKey == "ChronometerBar" then return "bar" elseif tabKey == "ChronometerClassSpells" then return "classspells" elseif tabKey == "ChronometerClassEvents" then return "classevents" elseif tabKey == "ChronometerRacial" then return "racial" end return "general" end local function MTH_CHRON_RenderTimerSection(container, profile, title, items, bucket, opts) opts = opts or {} local timerX = opts.x or 20 local timerY = opts.y or -84 local prefix = tostring(opts.prefix or "Main") MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerTimersHeader" .. prefix, title, timerX, timerY) timerY = timerY - 24 if table.getn(items) == 0 then MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerSectionEmpty" .. prefix, "(none)", timerX + 8, timerY) return end for i = 1, table.getn(items) do if timerY < -560 then MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerOverflow" .. prefix, "...more", timerX + 8, timerY) break end local row = items[i] local timerName = row local label = row if type(row) == "table" then timerName = row.key or row.timer or row.name label = row.label or row.name or row.key end timerName = tostring(timerName or "") label = tostring(label or timerName) local controlName = "MetaHuntOptionsChronometerTimer" .. prefix .. tostring(bucket) .. tostring(i) local check = MTH_CreateCheckbox(container, controlName, label, timerY, timerX) if check then check:SetChecked(profile.disabledSpells[bucket][timerName] == nil and true or false) check:SetScript("OnClick", function() if not this then return end local currentlyDisabled = profile.disabledSpells[bucket][timerName] ~= nil if currentlyDisabled then profile.disabledSpells[bucket][timerName] = nil this:SetChecked(true) else profile.disabledSpells[bucket][timerName] = {} this:SetChecked(nil) end MTH_CHRON_ApplyLiveProfile(profile) end) end timerY = timerY - 20 end end local MTH_CHRON_HUNTER_TRINKETS = { { labelToken = "Devilsaur Eye", timerToken = "Devilsaur Fury" }, { labelToken = "Zandalarian Hero Medallion", timerToken = "Restless Strength" }, { labelToken = "Earthstrike", timerToken = "Earthstrike" }, { labelToken = "Badge of the Swarmguard", timerToken = "Badge of the Swarmguard" }, { labelToken = "Jom Gabbar", timerToken = "Jom Gabbar" }, { labelToken = "Kiss of the Spider", timerToken = "Kiss of the Spider" }, { labelToken = "Slayer's Crest", timerToken = "Slayer's Crest" }, } local function MTH_CHRON_LocalizeTimerToken(token) if MTH and MTH.LocalizeSpell then return tostring(MTH:LocalizeSpell(token) or token) end return tostring(token) end local function MTH_CHRON_BuildHunterTrinketItems(classEvents) local eventSet = {} for i = 1, table.getn(classEvents or {}) do eventSet[tostring(classEvents[i])] = true end local result = {} for i = 1, table.getn(MTH_CHRON_HUNTER_TRINKETS) do local def = MTH_CHRON_HUNTER_TRINKETS[i] local key = MTH_CHRON_LocalizeTimerToken(def.timerToken) if eventSet[key] then table.insert(result, { key = key, label = MTH_CHRON_LocalizeTimerToken(def.labelToken), }) end end return result end function MTH_RefreshChronometerOptions(tabKey) if not MTH_CHRON_IsReady() then return end MTH_SetupChronometerOptions(tabKey) end function MTH_SetupChronometerOptions(tabKey) if not MTH_CHRON_IsReady() then if MTH and MTH.Print then MTH:Print("Chronometer options dependencies missing", "error") end return end local section = MTH_CHRON_NormalizeSection(tabKey) local container = MTH_GetFrame("MetaHuntOptionsChronometer") if not container then return end MTH_ClearContainer(container) MTH_CHRON_STATE.controls = {} local profile = MTH_CHRON_GetProfile() local _, class = UnitClass("player") class = class or "HUNTER" MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerTitle", "Chronometer", 20, -10) local moduleCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerEnabled", "Enable Chronometer module", -54) if moduleCheck then local module = MTH and MTH:GetModule("chronometer") moduleCheck:SetChecked(module and module.enabled and true or false) moduleCheck:SetScript("OnClick", function() if not this then return end local enabled = MTH_CHRON_IsChecked(this) if MTH and MTH.SetModuleEnabled then MTH:SetModuleEnabled("chronometer", enabled) end end) end if section == "general" then MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerGeneralHeader", "General", 20, -84) local ghostSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerGhost", "Ghost Duration", 0, 30, 1, -112) if ghostSlider then ghostSlider:SetValue(tonumber(profile.ghost) or 0) ghostSlider.onChange = function(value) profile.ghost = tonumber(value) or 0 MTH_CHRON_ApplyLiveProfile(profile) end end local killCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerFadeOnKill", "Fade on kill", -154) if killCheck then killCheck:SetChecked(profile.fadeonkill and true or false) killCheck:SetScript("OnClick", function() if not this then return end profile.fadeonkill = MTH_CHRON_IsChecked(this) end) end local fadeCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerFadeOnFade", "Fade on aura fade", -179) if fadeCheck then fadeCheck:SetChecked(profile.fadeonfade and true or false) fadeCheck:SetScript("OnClick", function() if not this then return end profile.fadeonfade = MTH_CHRON_IsChecked(this) end) end local selfCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerSelfBars", "Show self bars", -204) if selfCheck then selfCheck:SetChecked(profile.selfbars and true or false) selfCheck:SetScript("OnClick", function() if not this then return end profile.selfbars = MTH_CHRON_IsChecked(this) end) end local onlySelfCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerOnlySelf", "Only self target", -229) if onlySelfCheck then onlySelfCheck:SetChecked(profile.onlyself and true or false) onlySelfCheck:SetScript("OnClick", function() if not this then return end profile.onlyself = MTH_CHRON_IsChecked(this) end) end elseif section == "bar" then MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerBarHeader", "Bars", 20, -84) local runTestButton = MTH_CreateActionButton(container, "MetaHuntOptionsChronometerRunTest", "Run Test", 20, -112, 130, 22, function() local engine = MTH_ChronometerHunter if engine and engine.RunTest then engine:RunTest() end end) local anchorButton = MTH_CreateActionButton(container, "MetaHuntOptionsChronometerAnchor", "Toggle Anchor", 165, -112, 130, 22, function() local engine = MTH_ChronometerHunter if engine and engine.ToggleAnchor then engine:ToggleAnchor() end end) if runTestButton then runTestButton:Show() end if anchorButton then anchorButton:Show() end local anchorX = tonumber(profile.barposition and profile.barposition.x) or 0 local anchorY = tonumber(profile.barposition and profile.barposition.y) or 0 MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerAnchorPos", "Anchor: X " .. tostring(anchorX) .. " Y " .. tostring(anchorY), 20, -142) local widthSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarWidth", "Bar Width", 80, 320, 1, -180) if widthSlider then widthSlider:SetValue(tonumber(profile.barwidth) or 220) widthSlider.onChange = function(value) profile.barwidth = tonumber(value) or 220 MTH_CHRON_ApplyLiveProfile(profile) end end local scaleSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarScale", "Bar Scale", 0.5, 1.5, 0.1, -222) if scaleSlider then scaleSlider:SetValue(tonumber(profile.barscale) or 1) scaleSlider.onChange = function(value) profile.barscale = tonumber(value) or 1 MTH_CHRON_ApplyLiveProfile(profile) end end local heightSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarHeight", "Bar Height", 8, 30, 1, -264) if heightSlider then heightSlider:SetValue(tonumber(profile.barheight) or 16) heightSlider.onChange = function(value) profile.barheight = tonumber(value) or 16 MTH_CHRON_ApplyLiveProfile(profile) end end local spacingSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarSpacing", "Bar Spacing", 0, 15, 1, -306) if spacingSlider then spacingSlider:SetValue(tonumber(profile.spacing) or 0) spacingSlider.onChange = function(value) profile.spacing = tonumber(value) or 0 MTH_CHRON_ApplyLiveProfile(profile) end end local textSizeSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerTextSize", "Text Size", 8, 20, 1, -348) if textSizeSlider then textSizeSlider:SetValue(tonumber(profile.textsize) or 10) textSizeSlider.onChange = function(value) profile.textsize = tonumber(value) or 10 MTH_CHRON_ApplyLiveProfile(profile) end end MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerTextPatternLabel", "Bar Text ($s spell, $t target)", 20, -390) MTH_CHRON_MakeInput(container, "MetaHuntOptionsChronometerTextPattern", 20, -408, 220, 20, profile.text or "$t", function(newText) if not newText or newText == "" then newText = "$t" end profile.text = tostring(newText) MTH_CHRON_ApplyLiveProfile(profile) end) local growCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerGrowUp", "Grow bars upward", -438) if growCheck then growCheck:SetChecked(profile.growup and true or false) growCheck:SetScript("OnClick", function() if not this then return end profile.growup = MTH_CHRON_IsChecked(this) MTH_CHRON_ApplyLiveProfile(profile) end) end local reverseCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerReverse", "Reverse bars", -463) if reverseCheck then reverseCheck:SetChecked(profile.reverse and true or false) reverseCheck:SetScript("OnClick", function() if not this then return end profile.reverse = MTH_CHRON_IsChecked(this) MTH_CHRON_ApplyLiveProfile(profile) end) end MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerIconPosLabel", "Icon Position", 20, -494) local iconLeft = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerIconLeft", "Left", -514) local iconRight = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerIconRight", "Right", -538) if iconLeft and iconRight then iconLeft:SetChecked(profile.iconposition ~= "RIGHT") iconRight:SetChecked(profile.iconposition == "RIGHT") iconLeft:SetScript("OnClick", function() profile.iconposition = "LEFT" iconLeft:SetChecked(true) iconRight:SetChecked(nil) MTH_CHRON_ApplyLiveProfile(profile) end) iconRight:SetScript("OnClick", function() profile.iconposition = "RIGHT" iconRight:SetChecked(true) iconLeft:SetChecked(nil) MTH_CHRON_ApplyLiveProfile(profile) end) end else local classSpells, classEvents, racial = MTH_CHRON_BuildTimerLists() if section == "classspells" then MTH_CHRON_RenderTimerSection(container, profile, "Hunter Spells", classSpells, class, { x = 20, y = -84, prefix = "ClassSpells", }) MTH_CHRON_RenderTimerSection(container, profile, "Hunter Trinkets", MTH_CHRON_BuildHunterTrinketItems(classEvents), class, { x = 300, y = -84, prefix = "ClassTrinkets", }) elseif section == "classevents" then MTH_CHRON_RenderTimerSection(container, profile, "Hunter Events", classEvents, class, { prefix = "ClassEvents" }) elseif section == "racial" then MTH_CHRON_RenderTimerSection(container, profile, "Racial", racial, "RACIAL", { prefix = "Racial" }) end end end if _G then _G.MTH_SetupChronometerOptions = MTH_SetupChronometerOptions _G.MTH_RefreshChronometerOptions = MTH_RefreshChronometerOptions end