You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1031 lines
38 KiB

local L = DBM_GUI_L
---@class DBMGUI
local DBM_GUI = {
tabs = {},
panels = {}
}
_G.DBM_GUI = DBM_GUI
local isRetail = WOW_PROJECT_ID == (WOW_PROJECT_MAINLINE or 1)
local next, type, pairs, strsplit, tonumber, tostring, ipairs, tinsert, tsort, mfloor, slower = next, type, pairs, strsplit, tonumber, tostring, ipairs, table.insert, table.sort, math.floor, string.lower
local CreateFrame, C_Timer, GameFontNormal, GameFontNormalSmall, GameFontHighlight, GameFontHighlightSmall, ChatFontNormal, UIParent = CreateFrame, C_Timer, GameFontNormal, GameFontNormalSmall, GameFontHighlight, GameFontHighlightSmall, ChatFontNormal, UIParent
local RAID_DIFFICULTY1, RAID_DIFFICULTY2, RAID_DIFFICULTY3, RAID_DIFFICULTY4, PLAYER_DIFFICULTY1, PLAYER_DIFFICULTY2, PLAYER_DIFFICULTY3, PLAYER_DIFFICULTY6, PLAYER_DIFFICULTY_TIMEWALKER, CHALLENGE_MODE, ALL, CLOSE, SPECIALIZATION = RAID_DIFFICULTY1, RAID_DIFFICULTY2, RAID_DIFFICULTY3, RAID_DIFFICULTY4, PLAYER_DIFFICULTY1, PLAYER_DIFFICULTY2, PLAYER_DIFFICULTY3, PLAYER_DIFFICULTY6, PLAYER_DIFFICULTY_TIMEWALKER, CHALLENGE_MODE, ALL, CLOSE, SPECIALIZATION
local DBM, DBM_OPTION_SPACER = DBM, DBM_OPTION_SPACER
local playerName, realmName, playerLevel = UnitName("player"), GetRealmName(), UnitLevel("player")
StaticPopupDialogs["IMPORTPROFILE_ERROR"] = {
text = "There are one or more errors importing this profile. Please see the chat for more information. Would you like to continue and reset found errors to default?",
button1 = "Import and fix",
button2 = "No",
OnAccept = function(self)
self.importFunc()
end,
timeout = 0,
whileDead = true,
hideOnEscape = true,
preferredIndex = 3,
}
local challengeModeIds = {
[2] = 960, -- Temple of the Jade Serpent
[56] = 961, -- Stormstout Brewery
[57] = 962, -- Gate of the Setting Sun
[58] = 959, -- Shado-Pan Monastery
[59] = 1011, -- Siege of Niuzao Temple
[60] = 994, -- Mogu'shan Palace
[76] = 1007, -- Scholomance
[77] = 1001, -- Scarlet Halls
[78] = 1004, -- Scarlet Monastery
[161] = 1209, -- Skyreach
[163] = 1175, -- Bloodmaul Slag Mines
[164] = 1182, -- Auchindoun
[165] = 1176, -- Shadowmoon Burial Grounds
[166] = 1208, -- Grimrail Depot
[167] = 1358, -- Upper Blackrock Spire
[168] = 1279, -- The Everbloom
[169] = 1195, -- Iron Docks
[197] = 1456, -- Eye of Azshara
[198] = 1466, -- Darkheart Thicket
[199] = 1501, -- Black Rook Hold
[200] = 1477, -- Halls of Valor
[206] = 1458, -- Neltharion's Lair
[207] = 1493, -- Vault of the Wardens
[208] = 1492, -- Maw of Souls
[209] = 1516, -- The Arcway
[210] = 1571, -- Court of Stars
[227] = 1651, -- Return to Karazhan: Lower
[233] = 1677, -- Cathedral of Eternal Night
[234] = 1651, -- Return to Karazhan: Upper
[239] = 1753, -- Seat of the Triumvirate
[244] = 1763, -- Atal'Dazar
[245] = 1754, -- Freehold
[246] = 1771, -- Tol Dagor
[247] = 1594, -- The MOTHERLODE!!
[248] = 1862, -- Waycrest Manor
[249] = 1762, -- Kings' Rest
[250] = 1877, -- Temple of Sethraliss
[251] = 1841, -- The Underrot
[252] = 1864, -- Shrine of the Storm
[353] = 1822, -- Siege of Boralus
[369] = 2097, -- Operation: Mechagon - Junkyard
[370] = 2097, -- Operation: Mechagon - Workshop
[375] = 2290, -- Mists of Tirna Scithe
[376] = 2286, -- The Necrotic Wake
[377] = 2291, -- De Other Side
[378] = 2287, -- Halls of Atonement
[379] = 2289, -- Plaguefall
[380] = 2284, -- Sanguine Depths
[381] = 2285, -- Spires of Ascension
[382] = 2293, -- Theater of Pain
[391] = 2441, -- Tazavesh: Streets of Wonder
[392] = 2441, -- Tazavesh: So'leah's Gambit
[399] = 2521, -- Ruby Life Pools
[400] = 2516, -- The Nokhud Offensive
[401] = 2515, -- The Azure Vault
[402] = 2526, -- Algeth'ar Academy
[403] = 2451, -- Uldaman: Legacy of Tyr
[404] = 2519, -- Neltharus
[405] = 2520, -- Brackenhide Hollow
[406] = 2527, -- Halls of Infusion
[438] = 657, -- The Vortex Pinnacle
[456] = 643, -- Throne of the Tides
[463] = 2579, -- Dawn of the Infinite: Galakrond's Fall
[464] = 2579, -- Dawn of the Infinite: Murozond's Rise
[499] = 2649, -- Priory of the Sacred Flame
[500] = 2648, -- The Rookery
[501] = 2652, -- The Stonevault
[502] = 2669, -- City of Threads
[503] = 2660, -- Ara-Kara, City of Echoes
[504] = 2651, -- Darkflame Cleft
[505] = 2662, -- The Dawnbreaker
[506] = 2661, -- Cinderbrew Meadery
[507] = 670, -- Grim Batol
}
do
local soundsRegistered = false
function DBM_GUI:MixinSharedMedia3(mediatype, mediatable)
if not LibStub or not LibStub("LibSharedMedia-3.0", true) then
return mediatable
end
if not soundsRegistered then
local LSM = LibStub("LibSharedMedia-3.0")
soundsRegistered = true
-- Embedded Sound Clip media
LSM:Register("sound", "AirHorn (DBM)", [[Interface\AddOns\DBM-Core\sounds\AirHorn.ogg]])
LSM:Register("sound", "Jaina: Beware", [[Interface\AddOns\DBM-Core\sounds\SoundClips\beware.ogg]])
LSM:Register("sound", "Jaina: Beware (reverb)", [[Interface\AddOns\DBM-Core\sounds\SoundClips\beware_with_reverb.ogg]])
LSM:Register("sound", "Thrall: That's Incredible!", [[Interface\AddOns\DBM-Core\sounds\SoundClips\incredible.ogg]])
LSM:Register("sound", "Saurfang: Don't Die", [[Interface\AddOns\DBM-Core\sounds\SoundClips\dontdie.ogg]])
-- Blakbyrd
LSM:Register("sound", "Blakbyrd Alert 1", [[Interface\AddOns\DBM-Core\sounds\BlakbyrdAlerts\Alert1.ogg]])
LSM:Register("sound", "Blakbyrd Alert 2", [[Interface\AddOns\DBM-Core\sounds\BlakbyrdAlerts\Alert2.ogg]])
LSM:Register("sound", "Blakbyrd Alert 3", [[Interface\AddOns\DBM-Core\sounds\BlakbyrdAlerts\Alert3.ogg]])
-- User Media
if DBM.Options.CustomSounds >= 1 then
LSM:Register("sound", "DBM: Custom 1", [[Interface\AddOns\DBM-CustomSounds\Custom1.ogg]])
end
if DBM.Options.CustomSounds >= 2 then
LSM:Register("sound", "DBM: Custom 2", [[Interface\AddOns\DBM-CustomSounds\Custom2.ogg]])
end
if DBM.Options.CustomSounds >= 3 then
LSM:Register("sound", "DBM: Custom 3", [[Interface\AddOns\DBM-CustomSounds\Custom3.ogg]])
end
if DBM.Options.CustomSounds >= 4 then
LSM:Register("sound", "DBM: Custom 4", [[Interface\AddOns\DBM-CustomSounds\Custom4.ogg]])
end
if DBM.Options.CustomSounds >= 5 then
LSM:Register("sound", "DBM: Custom 5", [[Interface\AddOns\DBM-CustomSounds\Custom5.ogg]])
end
if DBM.Options.CustomSounds >= 6 then
LSM:Register("sound", "DBM: Custom 6", [[Interface\AddOns\DBM-CustomSounds\Custom6.ogg]])
end
if DBM.Options.CustomSounds >= 7 then
LSM:Register("sound", "DBM: Custom 7", [[Interface\AddOns\DBM-CustomSounds\Custom7.ogg]])
end
if DBM.Options.CustomSounds >= 8 then
LSM:Register("sound", "DBM: Custom 8", [[Interface\AddOns\DBM-CustomSounds\Custom8.ogg]])
end
if DBM.Options.CustomSounds >= 9 then
LSM:Register("sound", "DBM: Custom 9", [[Interface\AddOns\DBM-CustomSounds\Custom9.ogg]])
if DBM.Options.CustomSounds > 9 then
DBM.Options.CustomSounds = 9
end
end
end
-- Sort LibSharedMedia keys alphabetically (case-insensitive)
local hashtable = LibStub("LibSharedMedia-3.0", true):HashTable(mediatype)
local keytable = {}
for k in next, hashtable do
tinsert(keytable, k)
end
tsort(keytable, function(a, b)
return a:lower() < b:lower()
end);
-- DBM values (mediatable) first, LibSharedMedia values (sorted alphabetically) afterwards
local result = mediatable
for i = 1, #result do
if mediatype == "statusbar" then
result[i].texture = true
elseif mediatype == "font" then
result[i].font = true
elseif mediatype == "sound" then
result[i].sound = true
end
end
for i = 1, #keytable do
if mediatype ~= "sound" or (keytable[i] ~= "None" and keytable[i] ~= "NPCScan") then
local v = hashtable[keytable[i]]
-- Filter duplicates
local insertme = true
for _, v2 in next, result do
if slower(v2.value) == slower(v) then
insertme = false
break
end
end
if insertme then
local ins = {
text = keytable[i],
value = v
}
if mediatype == "statusbar" then
ins.texture = true
elseif mediatype == "font" then
ins.font = true
elseif mediatype == "sound" then--and type(v) == "string" and v:lower():find("addons")
ins.sound = true
end
if ins.texture or ins.font or ins.sound then
tinsert(result, ins)
end
end
end
end
return result
end
end
do
local LibSerialize = LibStub("LibSerialize")
local LibDeflate = LibStub("LibDeflate")
local canWeWork = LibStub and LibStub("LibDeflate", true) and LibStub("LibSerialize", true)
local popupFrame
local function createPopupFrame()
---@class DBMPopupFrame: Frame, BackdropTemplate
popupFrame = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
popupFrame:SetFrameStrata("DIALOG")
popupFrame:SetFrameLevel(popupFrame:GetFrameLevel() + 10)
popupFrame:SetSize(512, 512)
popupFrame:SetPoint("CENTER")
popupFrame.backdropInfo = {
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", -- 131071
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", -- 131072
tile = true,
tileSize = 32,
edgeSize = 32,
insets = { left = 8, right = 8, top = 8, bottom = 8 }
}
popupFrame:ApplyBackdrop()
popupFrame:SetMovable(true)
popupFrame:EnableMouse(true)
popupFrame:RegisterForDrag("LeftButton")
popupFrame:SetScript("OnDragStart", popupFrame.StartMoving)
popupFrame:SetScript("OnDragStop", popupFrame.StopMovingOrSizing)
popupFrame:Hide()
popupFrame.text = ""
---@class DBMPopupFrameBackdrop: Frame, BackdropTemplate
local backdrop = CreateFrame("Frame", nil, popupFrame, "BackdropTemplate")
backdrop.backdropInfo = {
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true,
tileSize = 16,
edgeSize = 16,
insets = { left = 3, right = 3, top = 5, bottom = 3 }
}
backdrop:ApplyBackdrop()
backdrop:SetBackdropColor(0.1, 0.1, 0.1, 0.6)
backdrop:SetBackdropBorderColor(0.4, 0.4, 0.4)
backdrop:SetPoint("TOPLEFT", 15, -15)
backdrop:SetPoint("BOTTOMRIGHT", -40, 40)
---@class DBMPopupFrameScrollFrame: ScrollFrame
local scrollFrame = CreateFrame("ScrollFrame", nil, popupFrame, "UIPanelScrollFrameTemplate")
scrollFrame:SetPoint("TOPLEFT", 15, -22)
scrollFrame:SetPoint("BOTTOMRIGHT", -40, 45)
local input = CreateFrame("EditBox", nil, scrollFrame)
input:SetTextInsets(7, 7, 3, 3)
input:SetFontObject(ChatFontNormal)
input:SetMultiLine(true)
input:EnableMouse(true)
input:SetAutoFocus(false)
input:SetMaxBytes(0)
input:SetScript("OnMouseUp", function(self)
self:HighlightText()
end)
input:SetScript("OnEscapePressed", function(self)
self:ClearFocus()
end)
input:HighlightText()
input:SetFocus()
scrollFrame:SetScrollChild(input)
input:ClearAllPoints()
input:SetPoint("TOPLEFT", scrollFrame, "TOPLEFT")
input:SetPoint("BOTTOMRIGHT", scrollFrame, "BOTTOMRIGHT")
input:SetWidth(452)
---@class DBMPopupFrameImportButton: Button
local import = CreateFrame("Button", nil, popupFrame, "UIPanelButtonTemplate")
import:SetPoint("BOTTOMRIGHT", -120, 13)
import:SetFrameLevel(import:GetFrameLevel() + 1)
import:SetSize(100, 20)
import:SetText(L.Import)
import:SetScript("OnClick", function()
if popupFrame:VerifyImport(input:GetText()) then
input:ClearFocus()
popupFrame:Hide()
end
end)
popupFrame.import = import
local close = CreateFrame("Button", nil, popupFrame, "UIPanelButtonTemplate")
close:SetPoint("LEFT", import, "RIGHT", 5, 0)
close:SetFrameLevel(close:GetFrameLevel() + 1)
close:SetSize(100, 20)
close:SetText(CLOSE)
close:SetScript("OnClick", function()
input:ClearFocus()
popupFrame:Hide()
end)
input:SetScript("OnChar", function()
if not import:IsShown() then
input:SetText(popupFrame.text)
input:HighlightText()
end
end)
function popupFrame:SetText(text)
input:SetText(text)
self.text = text
end
end
function DBM_GUI:CreateExportProfile(export)
if not canWeWork then
DBM:AddMsg("Missing required libraries to export.")
return
end
if not popupFrame then
createPopupFrame()
end
popupFrame.import:Hide()
popupFrame:SetText(LibDeflate:EncodeForPrint(LibDeflate:CompressDeflate(LibSerialize:Serialize(export), {level = 9})))
popupFrame:Show()
end
function DBM_GUI:CreateImportProfile(importFunc)
if not canWeWork then
DBM:AddMsg("Missing required libraries to export.")
return
end
if not popupFrame then
createPopupFrame()
end
function popupFrame:VerifyImport(import)
local success, deserialized = LibSerialize:Deserialize(LibDeflate:DecompressDeflate(LibDeflate:DecodeForPrint(import)))
if not success then
DBM:AddMsg("Failed to deserialize")
return false
end
importFunc(deserialized)
return true
end
popupFrame.import:Show()
popupFrame:SetText("")
popupFrame:Show()
end
end
do
local framecount = 0
function DBM_GUI:GetNewID()
framecount = framecount + 1
return framecount
end
function DBM_GUI:GetCurrentID()
return framecount
end
end
local UpdateCurrentSeason
local firstLoad = true
function DBM_GUI:ShowHide(forceshow)
if firstLoad then
UpdateCurrentSeason()
firstLoad = false
end
local optionsFrame = _G["DBM_GUI_OptionsFrame"]
if forceshow == true then
self:UpdateModList()
optionsFrame:Show()
elseif forceshow == false then
optionsFrame:Hide()
else
if optionsFrame:IsShown() then
optionsFrame:Hide()
else
self:UpdateModList()
optionsFrame:Show()
end
end
end
local catbutton, lastButton, addSpacer
local function addOptions(mod, catpanel, v)
if v == DBM_OPTION_SPACER then
addSpacer = true
else
lastButton = catbutton
if v.line then
catbutton = catpanel:CreateLine(v.text)
elseif type(mod.Options[v]) == "boolean" then
if mod.Options[v .. "TColor"] then
catbutton = catpanel:CreateCheckButton(mod.localization.options[v], true, nil, nil, nil, mod, v, nil, true)
elseif mod.Options[v .. "SWSound"] then
catbutton = catpanel:CreateCheckButton(mod.localization.options[v], true, nil, nil, nil, mod, v)
else
catbutton = catpanel:CreateCheckButton(mod.localization.options[v], true)
end
catbutton:SetScript("OnShow", function(self)
self:SetChecked(mod.Options[v])
end)
catbutton:SetScript("OnClick", function()
mod.Options[v] = not mod.Options[v]
if mod.optionFuncs and mod.optionFuncs[v] then
mod.optionFuncs[v]()
end
end)
elseif mod.dropdowns and mod.dropdowns[v] then
local dropdownOptions = {}
for _, val in ipairs(mod.dropdowns[v]) do
tinsert(dropdownOptions, {
text = mod.localization.options[val],
value = val
})
end
catbutton = catpanel:CreateDropdown(mod.localization.options[v], dropdownOptions, mod, v, function(value)
mod.Options[v] = value
if mod.optionFuncs and mod.optionFuncs[v] then
mod.optionFuncs[v]()
end
end, nil, 32)
if not addSpacer then
if lastButton then
catbutton:SetPoint("TOPLEFT", lastButton, "BOTTOMLEFT", 0, -12)
else
catbutton:SetPoint("TOPLEFT", catpanel:GetLastObj(), "BOTTOMLEFT", -14, -18)
end
end
end
if addSpacer then
catbutton:SetPoint("TOPLEFT", lastButton, "BOTTOMLEFT", 0, -6)
addSpacer = false
end
end
end
function DBM_GUI:CreateBossModPanel(mod)
if not mod.panel then
DBM:AddMsg("Couldn't create boss mod panel for " .. mod.localization.general.name)
return false
end
local panel = mod.panel
local category
local iconstat = panel.frame:CreateFontString("DBM_GUI_Mod_Icons" .. mod.localization.general.name, "ARTWORK")
iconstat:SetPoint("TOP", panel.frame, 0, -10)
iconstat:SetFontObject(GameFontNormal)
iconstat:SetText(L.IconsInUse)
for i = 1, 8 do
local icon = panel.frame:CreateTexture()
icon:SetTexture(137009) -- "Interface\\TargetingFrame\\UI-RaidTargetingIcons.blp"
icon:SetPoint("TOP", panel.frame, 81 - (i * 18), -26)
icon:SetSize(16, 16)
if not mod.usedIcons or not mod.usedIcons[i] then
icon:SetAlpha(0.25)
icon:SetDesaturated(true)
end
if i == 1 then icon:SetTexCoord(0, 0.25, 0, 0.25)
elseif i == 2 then icon:SetTexCoord(0.25, 0.5, 0, 0.25)
elseif i == 3 then icon:SetTexCoord(0.5, 0.75, 0, 0.25)
elseif i == 4 then icon:SetTexCoord(0.75, 1, 0, 0.25)
elseif i == 5 then icon:SetTexCoord(0, 0.25, 0.25, 0.5)
elseif i == 6 then icon:SetTexCoord(0.25, 0.5, 0.25, 0.5)
elseif i == 7 then icon:SetTexCoord(0.5, 0.75, 0.25, 0.5)
elseif i == 8 then icon:SetTexCoord(0.75, 1, 0.25, 0.5)
-- elseif i == 9 then icon:SetTexCoord(0, 0.25, 0.5, 0.75)
-- elseif i == 10 then icon:SetTexCoord(0.25, 0.5, 0.5, 0.75)
-- elseif i == 11 then icon:SetTexCoord(0.5, 0.75, 0.5, 0.75)
-- elseif i == 12 then icon:SetTexCoord(0.75, 1, 0.5, 0.75)
-- elseif i == 13 then icon:SetTexCoord(0, 0.25, 0.75, 1)
-- elseif i == 14 then icon:SetTexCoord(0.25, 0.5, 0.75, 1)
-- elseif i == 15 then icon:SetTexCoord(0.5, 0.75, 0.75, 1)
-- elseif i == 16 then icon:SetTexCoord(0.75, 1, 0.75, 1)
end
end
local reset = panel:CreateButton(L.Mod_Reset, 155, 30, nil, GameFontNormalSmall)
reset.myheight = 40
reset:SetPoint("TOPRIGHT", panel.frame, "TOPRIGHT", -24, -4)
reset:SetScript("OnClick", function()
DBM:LoadModDefaultOption(mod)
end)
local button = panel:CreateCheckButton(L.Mod_Enabled:format("|n|cFFFFFFFF" .. mod.localization.general.name), true)
button:SetChecked(mod.Options.Enabled)
button:SetPoint("TOPLEFT", panel.frame, "TOPLEFT", 8, -14)
button:SetScript("OnClick", function()
mod:Toggle()
end)
button.textObj:ClearAllPoints()
button.textObj:SetPoint("TOPLEFT", button, "TOPRIGHT", 0, 2)
if mod.addon then
for spellID, options in getmetatable(mod.groupOptions).__pairs(mod.groupOptions) do
if spellID:find("^line") then
panel:CreateLine(options)
else
local title, desc, _, icon
local usedSpellID, hasPrivate
if mod.groupOptions[spellID] and mod.groupOptions[spellID].customKeys then
usedSpellID = mod.groupOptions[spellID].customKeys--Color coding would be done in customKeys, not here
end
if mod.groupOptions[spellID] and mod.groupOptions[spellID].hasPrivate then
hasPrivate = true
end
if mod.groupOptions[spellID].title then--Custom title, it's a bogus spellId, so we completely ignore it and bundle with localized custom title
title, desc, icon = mod.groupOptions[spellID].title, L.CustomOptions, 136116
elseif tonumber(spellID) then
spellID = tonumber(spellID)
if spellID then--Because LuaLS doesn't understand tonumber(spellID) as a nil check
if spellID < 0 then
title, desc, _, icon = DBM:EJ_GetSectionInfo(-spellID)
else
local _title = DBM:GetSpellName(spellID)
if _title then
title, desc, icon = _title, tonumber(spellID), DBM:GetSpellTexture(spellID or 0)
end
end
end
elseif spellID:find("^ej") then
title, desc, _, icon = DBM:EJ_GetSectionInfo(spellID:gsub("ej", ""))
elseif spellID:find("^at") then
_, title, _, _, _, _, _, desc, _, icon = GetAchievementInfo(spellID:gsub("at", ""))
if not title then--Core has debug for spell and EJ, but this calls WoW API directly
DBM:Debug("|cffff0000Invalid call to GetAchievementInfo for achievementID: |r"..spellID:gsub("at", ""))
end
end
if not title then--Spell/EJ section/achievement not found - typo/removed/ptr or beta mod on live
title, desc, icon = spellID, L.NoDescription, 136116
end
if not usedSpellID then
usedSpellID = "|Hgarrmission:DBM:wacopy:"..spellID.."|h|cff69ccf0"..spellID.."|r|h"
end
local catpanel = panel:CreateAbility(title, icon, usedSpellID, hasPrivate)
if desc then
catpanel:CreateSpellDesc(desc)
end
catbutton, lastButton, addSpacer = nil, nil, nil
for _, v in ipairs(options) do
addOptions(mod, catpanel, v)
end
end
end
end
local scannedCategories = {}
for _, catident in pairs(mod.categorySort) do
category = mod.optionCategories[catident]
if not scannedCategories[catident] and category then
scannedCategories[catident] = true
local catpanel = panel:CreateArea(mod.localization.cats[catident])
catbutton, lastButton, addSpacer = nil, nil, nil
for _, v in ipairs(category) do
addOptions(mod, catpanel, v)
end
end
end
end
local function GetSpecializationGroup()
if isRetail then
return GetSpecialization() or 1
else
local numTabs = GetNumTalentTabs()
local highestPointsSpent, currentSpecGroup = 0, 1
if MAX_TALENT_TABS then
for i=1, MAX_TALENT_TABS do
if ( i <= numTabs ) then
local _, _, pointsSpent = GetTalentTabInfo(i)
if pointsSpent > highestPointsSpent then
highestPointsSpent = pointsSpent
currentSpecGroup = i
end
end
end
end
return currentSpecGroup
end
end
function DBM_GUI:CreateBossModTab(addon, panel, subtab)
if not panel then
error("Panel is nil", 2)
end
if panel.loadButton then
panel.loadButton:Hide()
panel.loadButton.headline:Hide()
end
local modProfileArea
if not subtab then
local modProfileDropdown = {}
modProfileArea = panel:CreateArea(L.Area_ModProfile)
modProfileArea.frame:SetPoint("TOPLEFT", 10, -25)
local resetButton = modProfileArea:CreateButton(L.ModAllReset, 200, 20)
resetButton:SetPoint("TOPLEFT", 10, -14)
resetButton:SetScript("OnClick", function()
DBM:LoadAllModDefaultOption(addon.modId)
end)
for charname, charTable in pairs(_G[addon.modId:gsub("-", "") .. "_AllSavedVars"] or {}) do
for _, optionTable in pairs(charTable) do
if type(optionTable) == "table" then
for i = 0, 4 do
if optionTable[i] then
tinsert(modProfileDropdown, {
text = (i == 0 and charname .. " (" .. ALL.. ")") or charname .. " (" .. SPECIALIZATION .. i .. "-" .. (charTable["talent" .. i] or "") .. ")",
value = charname .. "|" .. tostring(i)
})
end
end
break
end
end
end
local resetStatButton = modProfileArea:CreateButton(L.ModAllStatReset, 200, 20)
resetStatButton.myheight = 0
resetStatButton:SetPoint("LEFT", resetButton, "RIGHT", 40, 0)
resetStatButton:SetScript("OnClick", function()
DBM:ClearAllStats(addon.modId)
end)
local refresh
local copyModProfile = modProfileArea:CreateDropdown(L.SelectModProfileCopy, modProfileDropdown, nil, nil, function(value)
local name, profile = strsplit("|", value)
DBM:CopyAllModOption(addon.modId, name, tonumber(profile))
C_Timer.After(0.05, refresh)
end, 100)
copyModProfile:SetPoint("TOPLEFT", -7, -54)
copyModProfile:SetScript("OnShow", function()
copyModProfile.value = nil
copyModProfile.text = nil
_G[copyModProfile:GetName() .. "Text"]:SetText("")
end)
local copyModSoundProfile = modProfileArea:CreateDropdown(L.SelectModProfileCopySound, modProfileDropdown, nil, nil, function(value)
local name, profile = strsplit("|", value)
DBM:CopyAllModTypeOption(addon.modId, name, tonumber(profile), "SWSound")
C_Timer.After(0.05, refresh)
end, 100)
copyModSoundProfile.myheight = 0
copyModSoundProfile:SetPoint("LEFT", copyModProfile, "RIGHT", 27, 0)
copyModSoundProfile:SetScript("OnShow", function()
copyModSoundProfile.value = nil
copyModSoundProfile.text = nil
_G[copyModSoundProfile:GetName() .. "Text"]:SetText("")
end)
local copyModNoteProfile = modProfileArea:CreateDropdown(L.SelectModProfileCopyNote, modProfileDropdown, nil, nil, function(value)
local name, profile = strsplit("|", value)
DBM:CopyAllModTypeOption(addon.modId, name, tonumber(profile), "SWNote")
C_Timer.After(0.05, refresh)
end, 100)
copyModNoteProfile.myheight = 0
copyModNoteProfile:SetPoint("LEFT", copyModSoundProfile, "RIGHT", 27, 0)
copyModNoteProfile:SetScript("OnShow", function()
copyModNoteProfile.value = nil
copyModNoteProfile.text = nil
_G[copyModNoteProfile:GetName() .. "Text"]:SetText("")
end)
local deleteModProfile = modProfileArea:CreateDropdown(L.SelectModProfileDelete, modProfileDropdown, nil, nil, function(value)
local name, profile = strsplit("|", value)
DBM:DeleteAllModOption(addon.modId, name, tonumber(profile))
C_Timer.After(0.05, refresh)
end, 100)
deleteModProfile.myheight = 60
deleteModProfile:SetPoint("TOPLEFT", copyModSoundProfile, "BOTTOMLEFT", 0, -10)
deleteModProfile:SetScript("OnShow", function()
deleteModProfile.value = nil
deleteModProfile.text = nil
_G[deleteModProfile:GetName() .. "Text"]:SetText("")
end)
function refresh()
copyModProfile:GetScript("OnShow")()
copyModSoundProfile:GetScript("OnShow")()
copyModNoteProfile:GetScript("OnShow")()
deleteModProfile:GetScript("OnShow")()
end
-- Start import/export
local function actuallyImport(importTable)
local profileID = playerLevel > 9 and DBM_UseDualProfile and GetSpecializationGroup() or 0
for _, id in ipairs(DBM.ModLists[addon.modId]) do
_G[addon.modId:gsub("-", "") .. "_AllSavedVars"][playerName .. "-" .. realmName][id][profileID] = importTable[id]
---@diagnostic disable-next-line: inject-field
DBM:GetModByName(id).Options = importTable[id]
end
DBM:AddMsg("Profile imported.")
end
local importExportProfilesArea = panel:CreateArea(L.Area_ImportExportProfile)
local importExportText = importExportProfilesArea:CreateText(L.ImportExportInfo, nil, true)
local exportProfile = importExportProfilesArea:CreateButton(L.ButtonExportProfile, 120, 20, function()
local exportProfile = {}
local profileID = playerLevel > 9 and DBM_UseDualProfile and GetSpecializationGroup() or 0
for _, id in ipairs(DBM.ModLists[addon.modId]) do
exportProfile[id] = _G[addon.modId:gsub("-", "") .. "_AllSavedVars"][playerName .. "-" .. realmName][id][profileID]
end
DBM_GUI:CreateExportProfile(exportProfile)
end)
exportProfile:SetPoint("TOPLEFT", importExportText, "BOTTOMLEFT", 0, -12)
local importProfile = importExportProfilesArea:CreateButton(L.ButtonImportProfile, 120, 20, function()
DBM_GUI:CreateImportProfile(function(importTable)
local errors = {}
for id, table in pairs(importTable) do
-- Check if sound packs are missing
for settingName, settingValue in pairs(table) do
local ending = settingName:sub(-6):lower()
if ending == "cvoice" or ending == "wsound" then -- CVoice or SWSound (s is ignored so we only have to sub once)
if type(settingValue) == "string" and settingValue:lower() ~= "none" and not DBM:ValidateSound(settingValue, true, true) then
tinsert(errors, id .. "-" .. settingName)
end
end
end
end
-- Create popup confirming if they wish to continue (and therefor resetting to default)
if #errors > 0 then
local popup = StaticPopup_Show("IMPORTPROFILE_ERROR")
if popup then
popup.importFunc = function()
local modOptions = {}
for _, soundSetting in ipairs(errors) do
local modID, settingName = soundSetting:match("([^-]+)-([^-]+)")
if not modOptions[modID] then
modOptions[modID] = DBM:GetModByName(modID).DefaultOptions
end
importTable[modID][settingName] = modOptions[modID][settingName]
end
actuallyImport(importTable)
end
end
else
actuallyImport(importTable)
end
end)
end)
importProfile.myheight = 12
importProfile:SetPoint("LEFT", exportProfile, "RIGHT", 2, 0)
end
if addon.noStatistics then
return
end
local ptext = panel:CreateText(L.BossModLoaded:format(subtab and addon.subTabs[subtab] or addon.name), nil, nil, nil, "CENTER")
ptext:SetPoint("TOPLEFT", panel.frame, "TOPLEFT", 10, modProfileArea and -255 or -10)
local singleLine, doubleLine, noHeaderLine = 0, 0, 0
local area = panel:CreateArea()
area.frame.isStats = true
area.frame:SetPoint("TOPLEFT", 10, modProfileArea and -270 or -25)
local statOrder = {
"follower", "story", "lfr", "normal", "normal25", "heroic", "heroic25", "mythic", "challenge", "timewalker"
}
for _, mod in ipairs(DBM.Mods) do
if mod.modId == addon.modId and (not subtab or subtab == mod.subTab) and not mod.isTrashMod and not mod.noStatistics then
if not mod.stats then
mod.stats = {}
end
--Create Frames
local statSplit, statCount = {}, 0
for stat in (mod.statTypes or mod.addon.statTypes):gmatch("%s?([^%s,]+)%s?,?") do
statSplit[stat] = true
statCount = statCount + 1
end
if statCount == 0 then
DBM:AddMsg("No statTypes available for " .. mod.modId)
return -- No stats available for this? Possibly a bug
end
local Title = area:CreateText(mod.localization.general.name, nil, nil, GameFontHighlight)
local function CreateText(text, header)
local frame = area:CreateText(text or "", nil, nil, header and GameFontHighlightSmall or GameFontNormalSmall)
frame:Hide()
return frame
end
local sections = {}
for i = 1, 6 do
local section = {}
section.header = CreateText(nil, true)
section.text1 = CreateText(L.Statistic_Kills)
section.text2 = CreateText(L.Statistic_Wipes)
section.text3 = CreateText(L.Statistic_BestKill)
section.value1 = CreateText()
section.value2 = CreateText()
section.value3 = CreateText()
if i == 1 then
section.header:SetPoint("TOPLEFT", Title, "BOTTOMLEFT", 20, -5)
elseif i == 4 then
section.header:SetPoint("TOPLEFT", sections[1].text3, "BOTTOMLEFT", -20, -5)
else
section.header:SetPoint("LEFT", sections[i - 1].header, "LEFT", 150, 0)
end
section.text1:SetPoint("TOPLEFT", section.header, "BOTTOMLEFT", 20, -5)
section.text2:SetPoint("TOPLEFT", section.text1, "BOTTOMLEFT", 0, -5)
section.text3:SetPoint("TOPLEFT", section.text2, "BOTTOMLEFT", 0, -5)
section.value1:SetPoint("TOPLEFT", section.text1, "TOPLEFT", 80, 0)
section.value2:SetPoint("TOPLEFT", section.text2, "TOPLEFT", 80, 0)
section.value3:SetPoint("TOPLEFT", section.text3, "TOPLEFT", 80, 0)
section.header.OldSetText = section.header.SetText
section.header.SetText = function(self, text)
self:OldSetText(text)
self:Show()
section.text1:Show()
section.text2:Show()
section.text3:Show()
section.value1:Show()
section.value2:Show()
section.value3:Show()
end
sections[i] = section
end
local statTypes = {
follower = L.FOLLOWER,--no PLAYER_DIFFICULTY entry yet
story = L.STORY,--no PLAYER_DIFFICULTY entry yet
lfr25 = PLAYER_DIFFICULTY3,
normal = mod.addon.minExpansion < 5 and RAID_DIFFICULTY1 or PLAYER_DIFFICULTY1,
normal25 = RAID_DIFFICULTY2,
heroic = mod.addon.minExpansion < 5 and RAID_DIFFICULTY3 or PLAYER_DIFFICULTY2,
heroic25 = RAID_DIFFICULTY4,
mythic = PLAYER_DIFFICULTY6,
challenge = (mod.addon.minExpansion < 6 and not mod.upgradedMPlus) and CHALLENGE_MODE or (PLAYER_DIFFICULTY6 .. "+"),
timewalker = PLAYER_DIFFICULTY_TIMEWALKER
}
if (mod.addon.type == "PARTY" or mod.addon.type == "SCENARIO") or -- Fixes dungeons being labled incorrectly
(mod.addon.type == "RAID" and statSplit["timewalker"]) or -- Fixes raids with timewalker being labled incorrectly
(mod.instanceId == 369) then -- Fixes SoO being labled incorrectly
statTypes.normal = PLAYER_DIFFICULTY1
statTypes.heroic = PLAYER_DIFFICULTY2
end
local lastArea = 0
for _, statType in ipairs(statOrder) do
if statSplit[statType] then
if statType == "lfr" then
statType = "lfr25" -- Because Myst stores stats weird
end
if lastArea == 2 and statCount == 4 then -- Use top1, top2, bottom1, bottom2
lastArea = 3
end
lastArea = lastArea + 1
local section = sections[lastArea]
section.header:SetText(statTypes[statType])
local kills, pulls, bestRank, bestTime = mod.stats[statType .. "Kills"] or 0, mod.stats[statType .. "Pulls"] or 0, mod.stats["challengeBestRank"] or 0, mod.stats[statType .. "BestTime"]
section.value1:SetText(kills)
section.value2:SetText(pulls - kills)
if bestRank > 0 then--statType == "challenge" and
section.value3:SetText(bestTime and ("%d:%02d (%d)"):format(mfloor(bestTime / 60), bestTime % 60, bestRank) or "-")
else
section.value3:SetText(bestTime and ("%d:%02d"):format(mfloor(bestTime / 60), bestTime % 60) or "-")
end
end
end
Title:SetPoint("TOPLEFT", area.frame, "TOPLEFT", 10, -10 - (L.FontHeight * 5 * noHeaderLine) - (L.FontHeight * 6 * singleLine) - (L.FontHeight * 10 * doubleLine))
if statCount == 1 then
sections[1].header:Hide()
sections[1].text1:SetPoint("TOPLEFT", Title, "BOTTOMLEFT", 20, -5)
noHeaderLine = noHeaderLine + 1
area.frame:SetHeight(area.frame:GetHeight() + L.FontHeight * 5)
elseif statCount < 4 then
singleLine = singleLine + 1
area.frame:SetHeight(area.frame:GetHeight() + L.FontHeight * 6)
else
doubleLine = doubleLine + 1
area.frame:SetHeight(area.frame:GetHeight() + L.FontHeight * 10)
end
end
end
end
do
local subTabId = 0
local cachedAddOns = {}
local C_AddOns = {
DoesAddOnExist = C_AddOns.DoesAddOnExist or function(addon)
if not cachedAddOns then
for i = 1, GetNumAddOns() do ---@diagnostic disable-line:deprecated
cachedAddOns[GetAddOnInfo(i)] = true ---@diagnostic disable-line:deprecated
end
end
return cachedAddOns[addon]
end,
}
local currentSeasons = {}
function UpdateCurrentSeason()
if not C_ChallengeMode or not C_ChallengeMode.GetMapTable then
return
end
local seasonCategory = DBM_GUI:CreateNewPanel(L.TabCategory_CURRENT_SEASON, "PARTY")
local seasonCategoryTab = DBM_GUI.tabs[3].buttons[#DBM_GUI.tabs[3].buttons]
local hasAnyMod = false
for _, challengeMap in ipairs(C_ChallengeMode.GetMapTable()) do
local challengeMode = challengeModeIds[challengeMap]
local id = challengeMode
--For handling zones like Warfront: Arathi - Alliance
local mapName = GetRealZoneText(id):trim() or id
for w in string.gmatch(mapName, " - ") do
if w:trim() ~= "" then
mapName = w
break
end
end
if not currentSeasons[mapName] then
local modId
for _, addon in ipairs(DBM.AddOns) do
if addon.modId ~= "DBM-Affixes" and addon.type == "PARTY" then
for _, mapId in ipairs(addon.mapId) do
if mapId == id then
modId = addon.modId
break
end
end
end
end
if modId then
currentSeasons[mapName] = seasonCategory:CreateNewPanel(mapName, "PARTY", false, nil, true, modId, true)
hasAnyMod = true
end
end
end
if C_AddOns.DoesAddOnExist("DBM-Affixes") then
local affixAddon
for _, addon in ipairs(DBM.AddOns) do
if addon.modId == "DBM-Affixes" then
affixAddon = addon
break
end
end
if affixAddon then
currentSeasons["MPlusAffixes"] = seasonCategory:CreateNewPanel("MPlusAffixes", "PARTY", false, affixAddon.name, false, "DBM-Affixes", true)
hasAnyMod = true
end
end
if not hasAnyMod then
seasonCategoryTab.hidden = true
end
end
local expansions = {"CLASSIC", "BC", "WOTLK", "CATA", "MOP", "WOD", "LEG", "BFA", "SHADOWLANDS", "DRAGONFLIGHT", "WARWITHIN"}
-- WotLK compat, search for "local C_AddOns" in DBM-Core.lua for more details
local IsAddOnLoaded = _G.C_AddOns.IsAddOnLoaded or IsAddOnLoaded ---@diagnostic disable-line:deprecated
function DBM_GUI:UpdateModList()
for _, addon in ipairs(DBM.AddOns) do
if not addon.panel then
local customName
--Auto truncate Raid, Dungeon, and World boss mods to only display expansion name in list
if addon.type == "RAID" or addon.type == "PARTY" or addon.type == "WORLDBOSS" then
customName = _G["EXPANSION_NAME" .. (tIndexOf(expansions, addon.category:upper()) or 99) - 1]
end
-- Create a Panel for "Naxxramas" "Eye of Eternity" ...
addon.panel = DBM_GUI:CreateNewPanel(addon.name or "Error: No-modId", addon.type, false, customName, true, addon.modId)
if addon.modId == "DBM-Affixes" then -- If affixes, hide second general entry (as it's under Current Season)
DBM_GUI.tabs[3].buttons[#DBM_GUI.tabs[3].buttons].hidden = true
end
if not IsAddOnLoaded(addon.modId) then
local button = addon.panel:CreateButton(L.Button_LoadMod, 200, 30)
button.addon = addon
button.headline = addon.panel:CreateText(L.BossModLoad_now, 350, nil, nil, "CENTER")
button.headline:SetHeight(50)
button.headline:SetPoint("CENTER", button, "CENTER", 0, 80)
button:SetScript("OnClick", function(self)
DBM:LoadMod(self.addon, true)
end)
button:SetPoint("CENTER", 0, -20)
addon.panel.loadButton = button
else
DBM_GUI:CreateBossModTab(addon, addon.panel)
end
end
if addon.panel and addon.subTabs and IsAddOnLoaded(addon.modId) then
if not addon.subPanels then
addon.subPanels = {}
end
for k, v in pairs(addon.subTabs) do
if not addon.subPanels[k] then
subTabId = subTabId + 1
addon.subPanels[k] = currentSeasons[v] or addon.panel:CreateNewPanel("SubTab" .. subTabId, addon.type, false, v, addon.modId)
DBM_GUI:CreateBossModTab(addon, addon.subPanels[k], k)
end
end
end
for _, v in ipairs(DBM.Mods) do
---@class DBMMod
local mod = v
if mod.modId == addon.modId then
if not mod.panel and (not addon.subTabs or (addon.subPanels and (addon.subPanels[mod.subTab] or mod.subTab == 0))) then
if addon.subTabs and addon.subPanels[mod.subTab] then
mod.panel = addon.subPanels[mod.subTab]:CreateNewPanel(mod.id or "Error: DBM.Mods", addon.type, nil, mod.localization.general.name)
else
mod.panel = currentSeasons[mod.id] or addon.panel:CreateNewPanel(mod.id or "Error: DBM.Mods", addon.type, nil, mod.localization.general.name)
end
end
end
end
end
local optionsFrame = _G["DBM_GUI_OptionsFrame"]
if optionsFrame:IsShown() then
optionsFrame:Hide()
optionsFrame:Show()
end
end
end