ElvUI/Core/Core.lua

1821 lines
54 KiB
Lua

local ElvUI = select(2, ...)
ElvUI[2] = ElvUI[1].Libs.ACL:GetLocale('ElvUI', ElvUI[1]:GetLocale()) -- Locale doesn't exist yet, make it exist.
local E, L, V, P, G = unpack(ElvUI); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB
local _G = _G
local tonumber, pairs, ipairs, error, unpack, select, tostring = tonumber, pairs, ipairs, error, unpack, select, tostring
local strsplit, strjoin, wipe, sort, tinsert, tremove, tContains = strsplit, strjoin, wipe, sort, tinsert, tremove, tContains
local format, find, strrep, strlen, sub, gsub = format, strfind, strrep, strlen, strsub, gsub
local assert, type, pcall, xpcall, next, print = assert, type, pcall, xpcall, next, print
local rawget, rawset, setmetatable = rawget, rawset, setmetatable
local CreateFrame = CreateFrame
local GetCVar = GetCVar
local GetSpellInfo = GetSpellInfo
local GetCVarBool = GetCVarBool
local GetNumGroupMembers = GetNumGroupMembers
local GetSpecialization = GetSpecialization
local hooksecurefunc = hooksecurefunc
local InCombatLockdown = InCombatLockdown
local GetAddOnEnableState = GetAddOnEnableState
local UnitFactionGroup = UnitFactionGroup
local DisableAddOn = DisableAddOn
local IsInGroup = IsInGroup
local IsInGuild = IsInGuild
local IsInRaid = IsInRaid
local SetCVar = SetCVar
local ReloadUI = ReloadUI
local UnitGUID = UnitGUID
local ERR_NOT_IN_COMBAT = ERR_NOT_IN_COMBAT
local LE_PARTY_CATEGORY_HOME = LE_PARTY_CATEGORY_HOME
local LE_PARTY_CATEGORY_INSTANCE = LE_PARTY_CATEGORY_INSTANCE
local C_ChatInfo_SendAddonMessage = C_ChatInfo.SendAddonMessage
-- GLOBALS: ElvUIPlayerBuffs, ElvUIPlayerDebuffs
--Modules
local ActionBars = E:GetModule('ActionBars')
local AFK = E:GetModule('AFK')
local Auras = E:GetModule('Auras')
local Bags = E:GetModule('Bags')
local Blizzard = E:GetModule('Blizzard')
local Chat = E:GetModule('Chat')
local DataBars = E:GetModule('DataBars')
local DataTexts = E:GetModule('DataTexts')
local Layout = E:GetModule('Layout')
local Minimap = E:GetModule('Minimap')
local NamePlates = E:GetModule('NamePlates')
local Tooltip = E:GetModule('Tooltip')
local Totems = E:GetModule('Totems')
local UnitFrames = E:GetModule('UnitFrames')
local LSM = E.Libs.LSM
--Constants
E.noop = function() end
E.title = format('|cff1784d1%s |r', 'ElvUI')
E.version = tonumber(GetAddOnMetadata('ElvUI', 'Version'))
E.myfaction, E.myLocalizedFaction = UnitFactionGroup('player')
E.mylevel = UnitLevel('player')
E.myLocalizedClass, E.myclass, E.myClassID = UnitClass('player')
E.myLocalizedRace, E.myrace = UnitRace('player')
E.myname = UnitName('player')
E.myrealm = GetRealmName()
E.mynameRealm = format('%s - %s', E.myname, E.myrealm) -- contains spaces/dashes in realm (for profile keys)
E.myspec = GetSpecialization()
E.wowpatch, E.wowbuild = GetBuildInfo()
E.wowbuild = tonumber(E.wowbuild)
E.isMacClient = IsMacClient()
E.IsRetail = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE
E.screenwidth, E.screenheight = GetPhysicalScreenSize()
E.resolution = format('%dx%d', E.screenwidth, E.screenheight)
E.NewSign = [[|TInterface\OptionsFrame\UI-OptionsFrame-NewFeatureIcon:14:14|t]] -- not used by ElvUI yet, but plugins like BenikUI and MerathilisUI use it.
E.TexturePath = [[Interface\AddOns\ElvUI\Media\Textures\]] -- for plugins?
E.InfoColor = '|cff1784d1'
E.UserList = {}
-- oUF Defines
E.oUF.Tags.Vars.E = E
E.oUF.Tags.Vars.L = L
--Tables
E.media = {}
E.frames = {}
E.unitFrameElements = {}
E.statusBars = {}
E.texts = {}
E.snapBars = {}
E.RegisteredModules = {}
E.RegisteredInitialModules = {}
E.valueColorUpdateFuncs = {}
E.TexCoords = {0, 1, 0, 1}
E.FrameLocks = {}
E.VehicleLocks = {}
E.CreditsList = {}
E.LockedCVars = {}
E.IgnoredCVars = {}
E.UpdatedCVars = {}
E.InversePoints = {
TOP = 'BOTTOM',
BOTTOM = 'TOP',
TOPLEFT = 'BOTTOMLEFT',
TOPRIGHT = 'BOTTOMRIGHT',
LEFT = 'RIGHT',
RIGHT = 'LEFT',
BOTTOMLEFT = 'TOPLEFT',
BOTTOMRIGHT = 'TOPRIGHT',
CENTER = 'CENTER'
}
E.ClassRole = {
HUNTER = 'Melee',
ROGUE = 'Melee',
MAGE = 'Caster',
PRIEST = 'Caster',
WARLOCK = 'Caster',
DEMONHUNTER = {'Melee', 'Tank'},
WARRIOR = {'Melee', 'Melee', 'Tank'},
DEATHKNIGHT = {'Tank', 'Melee', 'Melee'},
MONK = {'Tank', 'Caster', 'Melee'},
PALADIN = {'Caster', 'Tank', 'Melee'},
SHAMAN = {'Caster', 'Melee', 'Caster'},
DRUID = {'Caster', 'Melee', 'Tank', 'Caster'},
}
E.DispelClasses = {
PRIEST = { Magic = true, Disease = true },
SHAMAN = { Magic = false, Curse = true },
PALADIN = { Poison = true, Magic = false, Disease = true },
DRUID = { Magic = false, Curse = true, Poison = true, Disease = false },
MONK = { Magic = false, Disease = true, Poison = true },
MAGE = { Curse = true }
}
E.BadDispels = {
[34914] = 'Vampiric Touch', -- horrifies
[233490] = 'Unstable Affliction' -- silences
}
--Workaround for people wanting to use white and it reverting to their class color.
E.PriestColors = { r = 0.99, g = 0.99, b = 0.99, colorStr = 'fffcfcfc' }
-- Socket Type info from 8.2
E.GemTypeInfo = {
Yellow = { r = 0.97, g = 0.82, b = 0.29 },
Red = { r = 1.00, g = 0.47, b = 0.47 },
Blue = { r = 0.47, g = 0.67, b = 1.00 },
Hydraulic = { r = 1.00, g = 1.00, b = 1.00 },
Cogwheel = { r = 1.00, g = 1.00, b = 1.00 },
Meta = { r = 1.00, g = 1.00, b = 1.00 },
Prismatic = { r = 1.00, g = 1.00, b = 1.00 },
PunchcardRed = { r = 1.00, g = 0.47, b = 0.47 },
PunchcardYellow = { r = 0.97, g = 0.82, b = 0.29 },
PunchcardBlue = { r = 0.47, g = 0.67, b = 1.00 },
}
--This frame everything in ElvUI should be anchored to for Eyefinity support.
E.UIParent = CreateFrame('Frame', 'ElvUIParent', _G.UIParent)
E.UIParent:SetFrameLevel(_G.UIParent:GetFrameLevel())
E.UIParent:SetSize(_G.UIParent:GetSize())
E.UIParent:SetPoint('BOTTOM')
E.UIParent.origHeight = E.UIParent:GetHeight()
E.snapBars[#E.snapBars + 1] = E.UIParent
E.HiddenFrame = CreateFrame('Frame')
E.HiddenFrame:Hide()
do -- used in optionsUI
E.DEFAULT_FILTER = {}
for filter, tbl in pairs(G.unitframe.aurafilters) do
E.DEFAULT_FILTER[filter] = tbl.type
end
end
do
local a1,a2 = '','[%s%-]'
function E:ShortenRealm(realm)
return gsub(realm, a2, a1)
end
local a3 = format('%%-%s', E:ShortenRealm(E.myrealm))
function E:StripMyRealm(name)
return gsub(name, a3, a1)
end
end
function E:Print(...)
(E.db and _G[E.db.general.messageRedirect] or _G.DEFAULT_CHAT_FRAME):AddMessage(strjoin('', E.media.hexvaluecolor or '|cff00b3ff', 'ElvUI:|r ', ...)) -- I put DEFAULT_CHAT_FRAME as a fail safe.
end
function E:GrabColorPickerValues(r, g, b)
-- we must block the execution path to `ColorCallback` in `AceGUIWidget-ColorPicker-ElvUI`
-- in order to prevent an infinite loop from `OnValueChanged` when passing into `E.UpdateMedia` which eventually leads here again.
_G.ColorPickerFrame.noColorCallback = true
-- grab old values
local oldR, oldG, oldB = _G.ColorPickerFrame:GetColorRGB()
-- set and define the new values
_G.ColorPickerFrame:SetColorRGB(r, g, b)
r, g, b = _G.ColorPickerFrame:GetColorRGB()
-- swap back to the old values
if oldR then _G.ColorPickerFrame:SetColorRGB(oldR, oldG, oldB) end
-- free it up..
_G.ColorPickerFrame.noColorCallback = nil
return r, g, b
end
--Basically check if another class border is being used on a class that doesn't match. And then return true if a match is found.
function E:CheckClassColor(r, g, b)
r, g, b = E:GrabColorPickerValues(r, g, b)
for class in pairs(_G.RAID_CLASS_COLORS) do
if class ~= E.myclass then
local colorTable = E:ClassColor(class, true)
local red, green, blue = E:GrabColorPickerValues(colorTable.r, colorTable.g, colorTable.b)
if red == r and green == g and blue == b then
return true
end
end
end
end
function E:SetColorTable(t, data)
if not data.r or not data.g or not data.b then
error('SetColorTable: Could not unpack color values.')
end
if t and (type(t) == 'table') then
t[1], t[2], t[3], t[4] = E:UpdateColorTable(data)
else
t = E:GetColorTable(data)
end
return t
end
function E:UpdateColorTable(data)
if not data.r or not data.g or not data.b then
error('UpdateColorTable: Could not unpack color values.')
end
if data.r > 1 or data.r < 0 then data.r = 1 end
if data.g > 1 or data.g < 0 then data.g = 1 end
if data.b > 1 or data.b < 0 then data.b = 1 end
if data.a and (data.a > 1 or data.a < 0) then data.a = 1 end
if data.a then
return data.r, data.g, data.b, data.a
else
return data.r, data.g, data.b
end
end
function E:GetColorTable(data)
if not data.r or not data.g or not data.b then
error('GetColorTable: Could not unpack color values.')
end
if data.r > 1 or data.r < 0 then data.r = 1 end
if data.g > 1 or data.g < 0 then data.g = 1 end
if data.b > 1 or data.b < 0 then data.b = 1 end
if data.a and (data.a > 1 or data.a < 0) then data.a = 1 end
if data.a then
return {data.r, data.g, data.b, data.a}
else
return {data.r, data.g, data.b}
end
end
function E:UpdateMedia()
if not E.db.general or not E.private.general then return end --Prevent rare nil value errors
--Fonts
E.media.normFont = LSM:Fetch('font', E.db.general.font)
E.media.combatFont = LSM:Fetch('font', E.private.general.dmgfont)
--Textures
E.media.blankTex = LSM:Fetch('background', 'ElvUI Blank')
E.media.normTex = LSM:Fetch('statusbar', E.private.general.normTex)
E.media.glossTex = LSM:Fetch('statusbar', E.private.general.glossTex)
--Border Color
local border = E.db.general.bordercolor
if E:CheckClassColor(border.r, border.g, border.b) then
local classColor = E:ClassColor(E.myclass, true)
E.db.general.bordercolor.r = classColor.r
E.db.general.bordercolor.g = classColor.g
E.db.general.bordercolor.b = classColor.b
end
E.media.bordercolor = {border.r, border.g, border.b}
--UnitFrame Border Color
border = E.db.unitframe.colors.borderColor
if E:CheckClassColor(border.r, border.g, border.b) then
local classColor = E:ClassColor(E.myclass, true)
E.db.unitframe.colors.borderColor.r = classColor.r
E.db.unitframe.colors.borderColor.g = classColor.g
E.db.unitframe.colors.borderColor.b = classColor.b
end
E.media.unitframeBorderColor = {border.r, border.g, border.b}
--Backdrop Color
E.media.backdropcolor = E:SetColorTable(E.media.backdropcolor, E.db.general.backdropcolor)
--Backdrop Fade Color
E.media.backdropfadecolor = E:SetColorTable(E.media.backdropfadecolor, E.db.general.backdropfadecolor)
--Value Color
local value = E.db.general.valuecolor
if E:CheckClassColor(value.r, value.g, value.b) then
value = E:ClassColor(E.myclass, true)
E.db.general.valuecolor.r = value.r
E.db.general.valuecolor.g = value.g
E.db.general.valuecolor.b = value.b
end
--Chat Tab Selector Color
local selectorColor = E.db.chat.tabSelectorColor
if E:CheckClassColor(selectorColor.r, selectorColor.g, selectorColor.b) then
selectorColor = E:ClassColor(E.myclass, true)
E.db.chat.tabSelectorColor.r = selectorColor.r
E.db.chat.tabSelectorColor.g = selectorColor.g
E.db.chat.tabSelectorColor.b = selectorColor.b
end
E.media.hexvaluecolor = E:RGBToHex(value.r, value.g, value.b)
E.media.rgbvaluecolor = {value.r, value.g, value.b}
-- Chat Panel Background Texture
local LeftChatPanel, RightChatPanel = _G.LeftChatPanel, _G.RightChatPanel
if LeftChatPanel and LeftChatPanel.tex and RightChatPanel and RightChatPanel.tex then
LeftChatPanel.tex:SetTexture(E.db.chat.panelBackdropNameLeft)
RightChatPanel.tex:SetTexture(E.db.chat.panelBackdropNameRight)
local a = E.db.general.backdropfadecolor.a or 0.5
LeftChatPanel.tex:SetAlpha(a)
RightChatPanel.tex:SetAlpha(a)
end
E:ValueFuncCall()
E:UpdateBlizzardFonts()
end
do --Update font/texture paths when they are registered by the addon providing them
--This helps fix most of the issues with fonts or textures reverting to default because the addon providing them is loading after ElvUI.
--We use a wrapper to avoid errors in :UpdateMedia because 'self' is passed to the function with a value other than ElvUI.
local function LSMCallback() E:UpdateMedia() end
LSM.RegisterCallback(E, 'LibSharedMedia_Registered', LSMCallback)
end
do
local function CVAR_UPDATE(name, value)
if not E.IgnoredCVars[name] then
local locked = E.LockedCVars[name]
if locked ~= nil and locked ~= value then
if InCombatLockdown() then
E.CVarUpdate = true
return
end
SetCVar(name, locked)
end
local func = E.UpdatedCVars[name]
if func then func(value) end
end
end
hooksecurefunc('SetCVar', CVAR_UPDATE)
function E:LockCVar(name, value)
if GetCVar(name) ~= value then
SetCVar(name, value)
end
E.LockedCVars[name] = value
end
function E:UpdatedCVar(name, func)
E.UpdatedCVars[name] = func
end
function E:IgnoreCVar(name, ignore)
E.IgnoredCVars[name] = (not not ignore) -- cast to bool, just in case
end
end
function E:ValueFuncCall()
local hex, r, g, b = E.media.hexvaluecolor, unpack(E.media.rgbvaluecolor)
for func in pairs(E.valueColorUpdateFuncs) do func(hex, r, g, b) end
end
function E:UpdateFrameTemplates()
for frame in pairs(E.frames) do
if frame and frame.template and not frame:IsForbidden() then
if not (frame.ignoreUpdates or frame.ignoreFrameTemplates) then
frame:SetTemplate(frame.template, frame.glossTex, nil, frame.forcePixelMode)
end
else
E.frames[frame] = nil
end
end
for frame in pairs(E.unitFrameElements) do
if frame and frame.template and not frame:IsForbidden() then
if not (frame.ignoreUpdates or frame.ignoreFrameTemplates) then
frame:SetTemplate(frame.template, frame.glossTex, nil, frame.forcePixelMode, frame.isUnitFrameElement)
end
else
E.unitFrameElements[frame] = nil
end
end
end
function E:UpdateBorderColors()
local r, g, b = unpack(E.media.bordercolor)
for frame in pairs(E.frames) do
if frame and frame.template and not frame:IsForbidden() then
if not (frame.ignoreUpdates or frame.forcedBorderColors) and (frame.template == 'Default' or frame.template == 'Transparent') then
frame:SetBackdropBorderColor(r, g, b)
end
else
E.frames[frame] = nil
end
end
local r2, g2, b2 = unpack(E.media.unitframeBorderColor)
for frame in pairs(E.unitFrameElements) do
if frame and frame.template and not frame:IsForbidden() then
if not (frame.ignoreUpdates or frame.forcedBorderColors) and (frame.template == 'Default' or frame.template == 'Transparent') then
frame:SetBackdropBorderColor(r2, g2, b2)
end
else
E.unitFrameElements[frame] = nil
end
end
end
function E:UpdateBackdropColors()
local r, g, b = unpack(E.media.backdropcolor)
local r2, g2, b2, a2 = unpack(E.media.backdropfadecolor)
for frame in pairs(E.frames) do
if frame and frame.template and not frame:IsForbidden() then
if not frame.ignoreUpdates then
if frame.callbackBackdropColor then
frame:callbackBackdropColor()
elseif frame.template == 'Default' then
frame:SetBackdropColor(r, g, b)
elseif frame.template == 'Transparent' then
frame:SetBackdropColor(r2, g2, b2, frame.customBackdropAlpha or a2)
end
end
else
E.frames[frame] = nil
end
end
for frame in pairs(E.unitFrameElements) do
if frame and frame.template and not frame:IsForbidden() then
if not frame.ignoreUpdates then
if frame.callbackBackdropColor then
frame:callbackBackdropColor()
elseif frame.template == 'Default' then
frame:SetBackdropColor(r, g, b)
elseif frame.template == 'Transparent' then
frame:SetBackdropColor(r2, g2, b2, frame.customBackdropAlpha or a2)
end
end
else
E.unitFrameElements[frame] = nil
end
end
end
function E:UpdateFontTemplates()
for text in pairs(E.texts) do
if text then
text:FontTemplate(text.font, text.fontSize, text.fontStyle, true)
else
E.texts[text] = nil
end
end
end
function E:RegisterStatusBar(statusBar)
E.statusBars[statusBar] = true
end
function E:UnregisterStatusBar(statusBar)
E.statusBars[statusBar] = nil
end
function E:UpdateStatusBars()
for statusBar in pairs(E.statusBars) do
if statusBar and statusBar:IsObjectType('StatusBar') then
statusBar:SetStatusBarTexture(E.media.normTex)
elseif statusBar and statusBar:IsObjectType('Texture') then
statusBar:SetTexture(E.media.normTex)
end
end
end
do
local cancel = function(popup)
DisableAddOn(popup.addon)
ReloadUI()
end
function E:IncompatibleAddOn(addon, module, info)
local popup = E.PopupDialogs.INCOMPATIBLE_ADDON
popup.button2 = info.name or module
popup.button1 = addon
popup.module = module
popup.addon = addon
popup.accept = info.accept
popup.cancel = info.cancel or cancel
E:StaticPopup_Show('INCOMPATIBLE_ADDON', popup.button1, popup.button2)
end
end
function E:IsAddOnEnabled(addon)
return GetAddOnEnableState(E.myname, addon) == 2
end
function E:IsIncompatible(module, addons)
for _, addon in ipairs(addons) do
if E:IsAddOnEnabled(addon) then
E:IncompatibleAddOn(addon, module, addons.info)
return true
end
end
end
do
local ADDONS = {
ActionBar = {
info = {
enabled = function() return E.private.actionbar.enable end,
accept = function() E.private.actionbar.enable = false; ReloadUI() end,
name = 'ElvUI ActionBars'
},
'Bartender4',
'Dominos'
},
Chat = {
info = {
enabled = function() return E.private.chat.enable end,
accept = function() E.private.chat.enable = false; ReloadUI() end,
name = 'ElvUI Chat'
},
'Prat-3.0',
'Chatter',
'Glass'
},
NamePlates = {
info = {
enabled = function() return E.private.nameplates.enable end,
accept = function() E.private.nameplates.enable = false; ReloadUI() end,
name = 'ElvUI NamePlates'
},
'TidyPlates',
'TidyPlates_ThreatPlates',
'Healers-Have-To-Die',
'Kui_Nameplates',
'Plater',
'Aloft'
}
}
E.INCOMPATIBLE_ADDONS = ADDONS -- let addons have the ability to alter this list to trigger our popup if they want
function E:AddIncompatible(module, addonName)
if ADDONS[module] then
tinsert(ADDONS[module], addonName)
else
print(module, 'is not in the incompatibility list.')
end
end
function E:CheckIncompatible()
if E.global.ignoreIncompatible then return end
for module, addons in pairs(ADDONS) do
if addons[1] and addons.info.enabled() and E:IsIncompatible(module, addons) then
break
end
end
end
end
function E:CopyTable(current, default)
if type(current) ~= 'table' then
current = {}
end
if type(default) == 'table' then
for option, value in pairs(default) do
current[option] = (type(value) == 'table' and E:CopyTable(current[option], value)) or value
end
end
return current
end
function E:RemoveEmptySubTables(tbl)
if type(tbl) ~= 'table' then
E:Print('Bad argument #1 to \'RemoveEmptySubTables\' (table expected)')
return
end
for k, v in pairs(tbl) do
if type(v) == 'table' then
if next(v) == nil then
tbl[k] = nil
else
E:RemoveEmptySubTables(v)
end
end
end
end
--Compare 2 tables and remove duplicate key/value pairs
--param cleanTable : table you want cleaned
--param checkTable : table you want to check against.
--param generatedKeys : table defined in `Distributor.lua` to allow user generated tables to be exported (customTexts, customCurrencies, etc).
--return : a copy of cleanTable with duplicate key/value pairs removed
function E:RemoveTableDuplicates(cleanTable, checkTable, generatedKeys)
if type(cleanTable) ~= 'table' then
E:Print('Bad argument #1 to \'RemoveTableDuplicates\' (table expected)')
return
end
if type(checkTable) ~= 'table' then
E:Print('Bad argument #2 to \'RemoveTableDuplicates\' (table expected)')
return
end
local rtdCleaned = {}
local keyed = type(generatedKeys) == 'table'
for option, value in pairs(cleanTable) do
local default, genTable, genOption = checkTable[option]
if keyed then genTable = generatedKeys[option] else genOption = generatedKeys end
-- we only want to add settings which are existing in the default table, unless it's allowed by generatedKeys
if default ~= nil or (genTable or genOption ~= nil) then
if type(value) == 'table' and type(default) == 'table' then
if genOption ~= nil then
rtdCleaned[option] = E:RemoveTableDuplicates(value, default, genOption)
else
rtdCleaned[option] = E:RemoveTableDuplicates(value, default, genTable or nil)
end
elseif cleanTable[option] ~= default then
-- add unique data to our clean table
rtdCleaned[option] = value
end
end
end
--Clean out empty sub-tables
E:RemoveEmptySubTables(rtdCleaned)
return rtdCleaned
end
--Compare 2 tables and remove blacklisted key/value pairs
--param cleanTable : table you want cleaned
--param blacklistTable : table you want to check against.
--return : a copy of cleanTable with blacklisted key/value pairs removed
function E:FilterTableFromBlacklist(cleanTable, blacklistTable)
if type(cleanTable) ~= 'table' then
E:Print('Bad argument #1 to \'FilterTableFromBlacklist\' (table expected)')
return
end
if type(blacklistTable) ~= 'table' then
E:Print('Bad argument #2 to \'FilterTableFromBlacklist\' (table expected)')
return
end
local tfbCleaned = {}
for option, value in pairs(cleanTable) do
if type(value) == 'table' and blacklistTable[option] and type(blacklistTable[option]) == 'table' then
tfbCleaned[option] = E:FilterTableFromBlacklist(value, blacklistTable[option])
else
-- Filter out blacklisted keys
if blacklistTable[option] ~= true then
tfbCleaned[option] = value
end
end
end
--Clean out empty sub-tables
E:RemoveEmptySubTables(tfbCleaned)
return tfbCleaned
end
local function keySort(a, b)
local A, B = type(a), type(b)
if A == B then
if A == 'number' or A == 'string' then
return a < b
elseif A == 'boolean' then
return (a and 1 or 0) > (b and 1 or 0)
end
end
return A < B
end
do --The code in this function is from WeakAuras, credit goes to Mirrored and the WeakAuras Team
--Code slightly modified by Simpy, sorting from @sighol
local function recurse(tbl, level, ret)
local tkeys = {}
for i in pairs(tbl) do tinsert(tkeys, i) end
sort(tkeys, keySort)
for _, i in ipairs(tkeys) do
local v = tbl[i]
ret = ret..strrep(' ', level)..'['
if type(i) == 'string' then ret = ret..'"'..i..'"' else ret = ret..i end
ret = ret..'] = '
if type(v) == 'number' then
ret = ret..v..',\n'
elseif type(v) == 'string' then
ret = ret..'"'..v:gsub('\\', '\\\\'):gsub('\n', '\\n'):gsub('"', '\\"'):gsub('\124', '\124\124')..'",\n'
elseif type(v) == 'boolean' then
if v then ret = ret..'true,\n' else ret = ret..'false,\n' end
elseif type(v) == 'table' then
ret = ret..'{\n'
ret = recurse(v, level + 1, ret)
ret = ret..strrep(' ', level)..'},\n'
else
ret = ret..'"'..tostring(v)..'",\n'
end
end
return ret
end
function E:TableToLuaString(inTable)
if type(inTable) ~= 'table' then
E:Print('Invalid argument #1 to E:TableToLuaString (table expected)')
return
end
local ret = '{\n'
if inTable then ret = recurse(inTable, 1, ret) end
ret = ret..'}'
return ret
end
end
do --The code in this function is from WeakAuras, credit goes to Mirrored and the WeakAuras Team
--Code slightly modified by Simpy, sorting from @sighol
local lineStructureTable, profileFormat = {}, {
profile = 'E.db',
private = 'E.private',
global = 'E.global',
filters = 'E.global',
styleFilters = 'E.global'
}
local function buildLineStructure(str) -- str is profileText
for _, v in ipairs(lineStructureTable) do
if type(v) == 'string' then
str = str..'["'..v..'"]'
else
str = str..'['..v..']'
end
end
return str
end
local sameLine
local function recurse(tbl, ret, profileText)
local tkeys = {}
for i in pairs(tbl) do tinsert(tkeys, i) end
sort(tkeys, keySort)
local lineStructure = buildLineStructure(profileText)
for _, k in ipairs(tkeys) do
local v = tbl[k]
if not sameLine then
ret = ret..lineStructure
end
ret = ret..'['
if type(k) == 'string' then
ret = ret..'"'..k..'"'
else
ret = ret..k
end
if type(v) == 'table' then
tinsert(lineStructureTable, k)
sameLine = true
ret = ret..']'
ret = recurse(v, ret, profileText)
else
sameLine = false
ret = ret..'] = '
if type(v) == 'number' then
ret = ret..v..'\n'
elseif type(v) == 'string' then
ret = ret..'"'..v:gsub('\\', '\\\\'):gsub('\n', '\\n'):gsub('"', '\\"'):gsub('\124', '\124\124')..'"\n'
elseif type(v) == 'boolean' then
if v then
ret = ret..'true\n'
else
ret = ret..'false\n'
end
else
ret = ret..'"'..tostring(v)..'"\n'
end
end
end
tremove(lineStructureTable)
return ret
end
function E:ProfileTableToPluginFormat(inTable, profileType)
local profileText = profileFormat[profileType]
if not profileText then return end
wipe(lineStructureTable)
local ret = ''
if inTable and profileType then
sameLine = false
ret = recurse(inTable, ret, profileText)
end
return ret
end
end
do --Split string by multi-character delimiter (the strsplit / string.split function provided by WoW doesn't allow multi-character delimiter)
local splitTable = {}
function E:SplitString(str, delim)
assert(type (delim) == 'string' and strlen(delim) > 0, 'bad delimiter')
local start = 1
wipe(splitTable) -- results table
-- find each instance of a string followed by the delimiter
while true do
local pos = find(str, delim, start, true) -- plain find
if not pos then break end
tinsert(splitTable, sub(str, start, pos - 1))
start = pos + strlen(delim)
end -- while
-- insert final one (after last delimiter)
tinsert(splitTable, sub(str, start))
return unpack(splitTable)
end
end
do
local SendMessageWaiting -- only allow 1 delay at a time regardless of eventing
function E:SendMessage()
if IsInRaid() then
C_ChatInfo_SendAddonMessage('ELVUI_VERSIONCHK', E.version, (not IsInRaid(LE_PARTY_CATEGORY_HOME) and IsInRaid(LE_PARTY_CATEGORY_INSTANCE)) and 'INSTANCE_CHAT' or 'RAID')
elseif IsInGroup() then
C_ChatInfo_SendAddonMessage('ELVUI_VERSIONCHK', E.version, (not IsInGroup(LE_PARTY_CATEGORY_HOME) and IsInGroup(LE_PARTY_CATEGORY_INSTANCE)) and 'INSTANCE_CHAT' or 'PARTY')
elseif IsInGuild() then
C_ChatInfo_SendAddonMessage('ELVUI_VERSIONCHK', E.version, 'GUILD')
end
SendMessageWaiting = nil
end
local SendRecieveGroupSize = 0
local PLAYER_NAME = format('%s-%s', E.myname, E:ShortenRealm(E.myrealm))
local function SendRecieve(_, event, prefix, message, _, sender)
if event == 'CHAT_MSG_ADDON' then
if sender == PLAYER_NAME then return end
if prefix == 'ELVUI_VERSIONCHK' then
local msg, ver = tonumber(message), E.version
local inCombat = InCombatLockdown()
E.UserList[E:StripMyRealm(sender)] = msg
if msg and (msg > ver) and not E.recievedOutOfDateMessage then -- you're outdated D:
E:Print(L["ElvUI is out of date. You can download the newest version from www.tukui.org. Get premium membership and have ElvUI automatically updated with the Tukui Client!"])
if msg and ((msg - ver) >= 0.05) and not inCombat then
E:StaticPopup_Show('ELVUI_UPDATE_AVAILABLE')
end
E.recievedOutOfDateMessage = true
end
end
elseif event == 'GROUP_ROSTER_UPDATE' then
local num = GetNumGroupMembers()
if num ~= SendRecieveGroupSize then
if num > 1 and num > SendRecieveGroupSize then
if not SendMessageWaiting then
SendMessageWaiting = E:Delay(10, E.SendMessage)
end
end
SendRecieveGroupSize = num
end
elseif event == 'PLAYER_ENTERING_WORLD' then
if not SendMessageWaiting then
SendMessageWaiting = E:Delay(10, E.SendMessage)
end
end
end
_G.C_ChatInfo.RegisterAddonMessagePrefix('ELVUI_VERSIONCHK')
local f = CreateFrame('Frame')
f:RegisterEvent('CHAT_MSG_ADDON')
f:RegisterEvent('GROUP_ROSTER_UPDATE')
f:RegisterEvent('PLAYER_ENTERING_WORLD')
f:SetScript('OnEvent', SendRecieve)
end
function E:UpdateStart(skipCallback, skipUpdateDB)
if not skipUpdateDB then
E:UpdateDB()
end
E:UpdateMoverPositions()
E:UpdateMediaItems()
E:UpdateUnitFrames()
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
do -- BFA Convert, deprecated..
local function buffwatchConvert(spell)
if spell.sizeOverride then spell.sizeOverride = nil end
if spell.size then spell.size = nil end
if not spell.sizeOffset then
spell.sizeOffset = 0
end
if spell.styleOverride then
spell.style = spell.styleOverride
spell.styleOverride = nil
elseif not spell.style then
spell.style = 'coloredIcon'
end
end
local ttModSwap
do -- tooltip convert
local swap = {ALL = 'HIDE',NONE = 'SHOW'}
ttModSwap = function(val) return swap[val] end
end
function E:DBConvertBFA()
--Fix issue where UIScale was incorrectly stored as string
E.global.general.UIScale = tonumber(E.global.general.UIScale)
--Not sure how this one happens, but prevent it in any case
if E.global.general.UIScale <= 0 then
E.global.general.UIScale = G.general.UIScale
end
--Combat & Resting Icon options update
if E.db.unitframe.units.player.combatIcon ~= nil then
E.db.unitframe.units.player.CombatIcon.enable = E.db.unitframe.units.player.combatIcon
E.db.unitframe.units.player.combatIcon = nil
end
if E.db.unitframe.units.player.restIcon ~= nil then
E.db.unitframe.units.player.RestIcon.enable = E.db.unitframe.units.player.restIcon
E.db.unitframe.units.player.restIcon = nil
end
-- [Fader] Combat Fade options for Player
if E.db.unitframe.units.player.combatfade ~= nil then
local enabled = E.db.unitframe.units.player.combatfade
E.db.unitframe.units.player.fader.enable = enabled
if enabled then -- use the old min alpha too
E.db.unitframe.units.player.fader.minAlpha = 0
end
E.db.unitframe.units.player.combatfade = nil
end
-- [Fader] Range check options for Units
do
local outsideAlpha
if E.db.unitframe.OORAlpha ~= nil then
outsideAlpha = E.db.unitframe.OORAlpha
E.db.unitframe.OORAlpha = nil
end
for _, unit in ipairs({'target','targettarget','targettargettarget','focus','focustarget','pet','pettarget','boss','arena','party','raid','raid40','raidpet','tank','assist'}) do
if E.db.unitframe.units[unit].rangeCheck ~= nil then
local enabled = E.db.unitframe.units[unit].rangeCheck
E.db.unitframe.units[unit].fader.enable = enabled
E.db.unitframe.units[unit].fader.range = enabled
if outsideAlpha then
E.db.unitframe.units[unit].fader.minAlpha = outsideAlpha
end
E.db.unitframe.units[unit].rangeCheck = nil
end
end
end
--Remove stale font settings from Cooldown system for top auras
if E.db.auras.cooldown.fonts then
E.db.auras.cooldown.fonts = nil
end
--Convert Nameplate Aura Duration to new Cooldown system
if E.db.nameplates.durationFont then
E.db.nameplates.cooldown.fonts.font = E.db.nameplates.durationFont
E.db.nameplates.cooldown.fonts.fontSize = E.db.nameplates.durationFontSize
E.db.nameplates.cooldown.fonts.fontOutline = E.db.nameplates.durationFontOutline
E.db.nameplates.durationFont = nil
E.db.nameplates.durationFontSize = nil
E.db.nameplates.durationFontOutline = nil
end
if E.db.nameplates.lowHealthThreshold > 0.8 then
E.db.nameplates.lowHealthThreshold = 0.8
end
if E.db.nameplates.units.TARGET.nonTargetTransparency ~= nil then
E.global.nameplate.filters.ElvUI_NonTarget.actions.alpha = E.db.nameplates.units.TARGET.nonTargetTransparency * 100
E.db.nameplates.units.TARGET.nonTargetTransparency = nil
end
--Removed additional table in nameplate filters cause it was basically useless
for _, unit in ipairs({'PLAYER','FRIENDLY_PLAYER','ENEMY_PLAYER','FRIENDLY_NPC','ENEMY_NPC'}) do
if E.db.nameplates.units[unit].buffs and E.db.nameplates.units[unit].buffs.filters ~= nil then
E.db.nameplates.units[unit].buffs.minDuration = E.db.nameplates.units[unit].buffs.filters.minDuration or P.nameplates.units[unit].buffs.minDuration
E.db.nameplates.units[unit].buffs.maxDuration = E.db.nameplates.units[unit].buffs.filters.maxDuration or P.nameplates.units[unit].buffs.maxDuration
E.db.nameplates.units[unit].buffs.priority = E.db.nameplates.units[unit].buffs.filters.priority or P.nameplates.units[unit].buffs.priority
E.db.nameplates.units[unit].buffs.filters = nil
end
if E.db.nameplates.units[unit].debuffs and E.db.nameplates.units[unit].debuffs.filters ~= nil then
E.db.nameplates.units[unit].debuffs.minDuration = E.db.nameplates.units[unit].debuffs.filters.minDuration or P.nameplates.units[unit].debuffs.minDuration
E.db.nameplates.units[unit].debuffs.maxDuration = E.db.nameplates.units[unit].debuffs.filters.maxDuration or P.nameplates.units[unit].debuffs.maxDuration
E.db.nameplates.units[unit].debuffs.priority = E.db.nameplates.units[unit].debuffs.filters.priority or P.nameplates.units[unit].debuffs.priority
E.db.nameplates.units[unit].debuffs.filters = nil
end
end
--Moved target scale to a style filter
if E.db.nameplates.units.TARGET.scale ~= nil then
E.global.nameplate.filters.ElvUI_Target.actions.scale = E.db.nameplates.units.TARGET.scale
E.db.nameplates.units.TARGET.scale = nil
end
--Convert cropIcon to tristate
local cropIcon = E.db.general.cropIcon
if type(cropIcon) == 'boolean' then
E.db.general.cropIcon = (cropIcon and 2) or 0
end
--Vendor Greys option is now in bags table
if E.db.general.vendorGrays ~= nil then
E.db.bags.vendorGrays.enable = E.db.general.vendorGrays
E.db.general.vendorGraysDetails = nil
E.db.general.vendorGrays = nil
end
--Heal Prediction is now a table instead of a bool
for _, unit in ipairs({'player','target','focus','pet','arena','party','raid','raid40','raidpet'}) do
if type(E.db.unitframe.units[unit].healPrediction) ~= 'table' then
local enabled = E.db.unitframe.units[unit].healPrediction
E.db.unitframe.units[unit].healPrediction = {}
E.db.unitframe.units[unit].healPrediction.enable = enabled
else
local healPrediction = E.db.unitframe.units[unit].healPrediction
if healPrediction.reversedAbsorbs ~= nil then -- convert the newer setting if it existed
healPrediction.reversedAbsorbs = nil
healPrediction.absorbStyle = 'REVERSED'
-- clear extras
healPrediction.showAbsorbAmount = nil
healPrediction.showOverAbsorbs = nil
elseif healPrediction.showAbsorbAmount ~= nil then -- convert the old setting into the new wrapped setting
healPrediction.showAbsorbAmount = nil
healPrediction.absorbStyle = 'WRAPPED'
-- clear extras
healPrediction.showOverAbsorbs = nil
elseif healPrediction.showOverAbsorbs ~= nil then -- convert the over absorb toggle into the new setting
healPrediction.absorbStyle = 'NORMAL'
healPrediction.showOverAbsorbs = nil
end
end
end
--Health Backdrop Multiplier
if E.db.unitframe.colors.healthmultiplier ~= nil then
if E.db.unitframe.colors.healthmultiplier > 0.75 then
E.db.unitframe.colors.healthMultiplier = 0.75
else
E.db.unitframe.colors.healthMultiplier = E.db.unitframe.colors.healthmultiplier
end
E.db.unitframe.colors.healthmultiplier = nil
end
--Tooltip FactionColors Setting
for i = 1, 8 do
local oldTable = E.db.tooltip.factionColors[''..i]
if oldTable then
local newTable = E:CopyTable({}, P.tooltip.factionColors[i]) -- import full table
E.db.tooltip.factionColors[i] = E:CopyTable(newTable, oldTable)
E.db.tooltip.factionColors[''..i] = nil
end
end
-- Wipe some old variables off profiles
if E.global.uiScaleInformed then E.global.uiScaleInformed = nil end
if E.global.nameplatesResetInformed then E.global.nameplatesResetInformed = nil end
if E.global.userInformedNewChanges1 then E.global.userInformedNewChanges1 = nil end
-- cvar nameplate visibility stuff
if E.db.nameplates.visibility.nameplateShowAll ~= nil then
E.db.nameplates.visibility.showAll = E.db.nameplates.visibility.nameplateShowAll
E.db.nameplates.visibility.nameplateShowAll = nil
end
if E.db.nameplates.units.FRIENDLY_NPC.showAlways ~= nil then
E.db.nameplates.visibility.friendly.npcs = E.db.nameplates.units.FRIENDLY_NPC.showAlways
E.db.nameplates.units.FRIENDLY_NPC.showAlways = nil
end
if E.db.nameplates.units.FRIENDLY_PLAYER.minions ~= nil then
E.db.nameplates.visibility.friendly.minions = E.db.nameplates.units.FRIENDLY_PLAYER.minions
E.db.nameplates.units.FRIENDLY_PLAYER.minions = nil
end
if E.db.nameplates.units.ENEMY_NPC.minors ~= nil then
E.db.nameplates.visibility.enemy.minus = E.db.nameplates.units.ENEMY_NPC.minors
E.db.nameplates.units.ENEMY_NPC.minors = nil
end
if E.db.nameplates.units.ENEMY_PLAYER.minions ~= nil or E.db.nameplates.units.ENEMY_NPC.minions ~= nil then
E.db.nameplates.visibility.enemy.minions = E.db.nameplates.units.ENEMY_PLAYER.minions or E.db.nameplates.units.ENEMY_NPC.minions
E.db.nameplates.units.ENEMY_PLAYER.minions = nil
E.db.nameplates.units.ENEMY_NPC.minions = nil
end
-- removed override stuff from aurawatch
if E.global.unitframe.buffwatch then
for _, spells in pairs(E.global.unitframe.buffwatch) do
for _, spell in pairs(spells) do
buffwatchConvert(spell)
end
end
end
if E.db.unitframe.filters.buffwatch then
for _, spell in pairs(E.db.unitframe.filters.buffwatch) do
buffwatchConvert(spell)
end
end
-- fix aurabars colors
local auraBarColors = E.global.unitframe.AuraBarColors
for spell, info in pairs(auraBarColors) do
if type(spell) == 'string' then
local spellID = select(7, GetSpellInfo(spell))
if spellID and not auraBarColors[spellID] then
auraBarColors[spellID] = info
auraBarColors[spell] = nil
spell = spellID
end
end
if type(info) == 'boolean' then
auraBarColors[spell] = { color = { r = 1, g = 1, b = 1 }, enable = info }
elseif type(info) == 'table' then
if info.r or info.g or info.b then
auraBarColors[spell] = { color = { r = info.r or 1, g = info.g or 1, b = info.b or 1 }, enable = true }
elseif info.color then -- azil created a void hole, delete it -x-
if info.color.color then info.color.color = nil end
if info.color.enable then info.color.enable = nil end
if info.color.a then info.color.a = nil end -- alpha isnt supported by this
end
end
end
if E.db.unitframe.colors.debuffHighlight.blendMode == 'MOD' then
E.db.unitframe.colors.debuffHighlight.blendMode = P.unitframe.colors.debuffHighlight.blendMode
end
do -- tooltip modifier code was dumb, change it but keep the past setting
local swap = ttModSwap(E.db.tooltip.modifierID)
if swap then E.db.tooltip.modifierID = swap end
swap = ttModSwap(E.db.tooltip.visibility.bags)
if swap then E.db.tooltip.visibility.bags = swap end
swap = ttModSwap(E.db.tooltip.visibility.unitFrames)
if swap then E.db.tooltip.visibility.unitFrames = swap end
swap = ttModSwap(E.db.tooltip.visibility.actionbars)
if swap then E.db.tooltip.visibility.actionbars = swap end
swap = ttModSwap(E.db.tooltip.visibility.combatOverride)
if swap then E.db.tooltip.visibility.combatOverride = swap end
-- remove the old combat variable and just use the mod since it supports show/hide states
local hideInCombat = E.db.tooltip.visibility.combat
if hideInCombat ~= nil then
E.db.tooltip.visibility.combat = nil
local override = E.db.tooltip.visibility.combatOverride
if hideInCombat and (override ~= 'SHIFT' and override ~= 'CTRL' and override ~= 'ALT') then -- wouldve been NONE but now it would be HIDE
E.db.tooltip.visibility.combatOverride = 'HIDE'
end
end
end
end
end
function E:DBConvertSL()
if E.private.skins.cleanBossButton ~= nil then
E.db.actionbar.extraActionButton.clean = E.private.skins.cleanBossButton
E.private.skins.cleanBossButton = nil
end
if E.global.unitframe.DebuffHighlightColors then
E:CopyTable(E.global.unitframe.AuraHighlightColors, E.global.unitframe.DebuffHighlightColors)
E.global.unitframe.DebuffHighlightColors = nil
end
if E.db.unitframe.filters.buffwatch then
E.db.unitframe.filters.aurawatch = E:CopyTable({}, E.db.unitframe.filters.buffwatch)
E.db.unitframe.filters.buffwatch = nil
end
if E.global.unitframe.buffwatch then
E:CopyTable(E.global.unitframe.aurawatch, E.global.unitframe.buffwatch)
E.global.unitframe.buffwatch = nil
end
end
function E:UpdateDB()
E.private = E.charSettings.profile
E.global = E.data.global
E.db = E.data.profile
E:DBConversions()
Auras.db = E.db.auras
ActionBars.db = E.db.actionbar
Bags.db = E.db.bags
Chat.db = E.db.chat
DataBars.db = E.db.databars
DataTexts.db = E.db.datatexts
NamePlates.db = E.db.nameplates
Tooltip.db = E.db.tooltip
UnitFrames.db = E.db.unitframe
Totems.db = E.db.general.totems
--Not part of staggered update
end
function E:UpdateMoverPositions()
--The mover is positioned before it is resized, which causes issues for unitframes
--Allow movers to be 'pushed' outside the screen, when they are resized they should be back in the screen area.
--We set movers to be clamped again at the bottom of this function.
E:SetMoversClampedToScreen(false)
E:SetMoversPositions()
--Not part of staggered update
end
function E:UpdateUnitFrames()
if E.private.unitframe.enable then
UnitFrames:Update_AllFrames()
end
--Not part of staggered update
end
function E:UpdateMediaItems(skipCallback)
E:UpdateMedia()
E:UpdateFrameTemplates()
E:UpdateStatusBars()
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
function E:UpdateLayout(skipCallback)
Layout:ToggleChatPanels()
Layout:BottomPanelVisibility()
Layout:TopPanelVisibility()
Layout:SetDataPanelStyle()
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
function E:UpdateActionBars(skipCallback)
ActionBars:ExtraButtons_UpdateAlpha()
ActionBars:ExtraButtons_UpdateScale()
ActionBars:ExtraButtons_GlobalFade()
ActionBars:ToggleCooldownOptions()
ActionBars:UpdateButtonSettings()
ActionBars:UpdateMicroPositionDimensions()
ActionBars:UpdatePetCooldownSettings()
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
function E:UpdateNamePlates(skipCallback)
NamePlates:ConfigureAll()
NamePlates:StyleFilterInitialize()
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
function E:UpdateTooltip()
Tooltip:SetTooltipFonts()
end
function E:UpdateBags(skipCallback)
Bags:Layout()
Bags:Layout(true)
Bags:SizeAndPositionBagBar()
Bags:UpdateCountDisplay()
Bags:UpdateItemLevelDisplay()
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
function E:UpdateChat(skipCallback)
Chat:SetupChat()
Chat:UpdateEditboxAnchors()
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
function E:UpdateDataBars(skipCallback)
DataBars:AzeriteBar_Toggle()
DataBars:ExperienceBar_Toggle()
DataBars:HonorBar_Toggle()
DataBars:ReputationBar_Toggle()
DataBars:ThreatBar_Toggle()
DataBars:UpdateAll()
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
function E:UpdateDataTexts(skipCallback)
DataTexts:LoadDataTexts()
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
function E:UpdateMinimap(skipCallback)
Minimap:UpdateSettings()
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
function E:UpdateAuras(skipCallback)
if ElvUIPlayerBuffs then Auras:UpdateHeader(ElvUIPlayerBuffs) end
if ElvUIPlayerDebuffs then Auras:UpdateHeader(ElvUIPlayerDebuffs) end
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
function E:UpdateMisc(skipCallback)
AFK:Toggle()
Blizzard:SetObjectiveFrameHeight()
Totems:PositionAndSize()
if not skipCallback then
E.callbacks:Fire('StaggeredUpdate')
end
end
function E:UpdateEnd()
E:UpdateCooldownSettings('all')
if E.RefreshGUI then
E:RefreshGUI()
end
E:SetMoversClampedToScreen(true) -- Go back to using clamp after resizing has taken place.
if not E.installSetup and not E.private.install_complete then
E:Install()
end
if E.staggerUpdateRunning then
--We're doing a staggered update, but plugins expect the old UpdateAll to be called
--So call it, but skip updates inside it
E:UpdateAll(false)
end
--Done updating, let code now
E.staggerUpdateRunning = false
end
do
local staggerDelay = 0.02
local staggerTable = {}
local function CallStaggeredUpdate()
local nextUpdate, nextDelay = staggerTable[1]
if nextUpdate then
tremove(staggerTable, 1)
if nextUpdate == 'UpdateNamePlates' or nextUpdate == 'UpdateBags' then
nextDelay = 0.05
end
E:Delay(nextDelay or staggerDelay, E[nextUpdate])
end
end
E:RegisterCallback('StaggeredUpdate', CallStaggeredUpdate)
function E:StaggeredUpdateAll(event, installSetup)
if not E.initialized then
E:Delay(1, E.StaggeredUpdateAll, E, event, installSetup)
return
end
E.installSetup = installSetup
if (installSetup or event and event == 'OnProfileChanged' or event == 'OnProfileCopied') and not E.staggerUpdateRunning then
tinsert(staggerTable, 'UpdateLayout')
if E.private.actionbar.enable then
tinsert(staggerTable, 'UpdateActionBars')
end
if E.private.nameplates.enable then
tinsert(staggerTable, 'UpdateNamePlates')
end
if E.private.bags.enable then
tinsert(staggerTable, 'UpdateBags')
end
if E.private.chat.enable then
tinsert(staggerTable, 'UpdateChat')
end
if E.private.tooltip.enable then
tinsert(staggerTable, 'UpdateTooltip')
end
tinsert(staggerTable, 'UpdateDataBars')
tinsert(staggerTable, 'UpdateDataTexts')
if E.private.general.minimap.enable then
tinsert(staggerTable, 'UpdateMinimap')
end
if ElvUIPlayerBuffs or ElvUIPlayerDebuffs then
tinsert(staggerTable, 'UpdateAuras')
end
tinsert(staggerTable, 'UpdateMisc')
tinsert(staggerTable, 'UpdateEnd')
--Stagger updates
E.staggerUpdateRunning = true
E:UpdateStart()
else
--Fire away
E:UpdateAll(true)
end
end
end
function E:UpdateAll(doUpdates)
if doUpdates then
E:UpdateStart(true)
E:UpdateLayout()
E:UpdateTooltip()
E:UpdateActionBars()
E:UpdateBags()
E:UpdateChat()
E:UpdateDataBars()
E:UpdateDataTexts()
E:UpdateMinimap()
E:UpdateNamePlates()
E:UpdateAuras()
E:UpdateMisc()
E:UpdateEnd()
end
end
do
E.ObjectEventTable, E.ObjectEventFrame = {}, CreateFrame('Frame')
local eventFrame, eventTable = E.ObjectEventFrame, E.ObjectEventTable
eventFrame:SetScript('OnEvent', function(_, event, ...)
local objs = eventTable[event]
if objs then
for object, funcs in pairs(objs) do
for _, func in ipairs(funcs) do
func(object, event, ...)
end
end
end
end)
function E:HasFunctionForObject(event, object, func)
if not (event and object and func) then
E:Print('Error. Usage: HasFunctionForObject(event, object, func)')
return
end
local objs = eventTable[event]
local funcs = objs and objs[object]
return funcs and tContains(funcs, func)
end
function E:IsEventRegisteredForObject(event, object)
if not (event and object) then
E:Print('Error. Usage: IsEventRegisteredForObject(event, object)')
return
end
local objs = eventTable[event]
local funcs = objs and objs[object]
return funcs ~= nil, funcs
end
--- Registers specified event and adds specified func to be called for the specified object.
-- Unless all parameters are supplied it will not register.
-- If the specified object has already been registered for the specified event
-- then it will just add the specified func to a table of functions that should be called.
-- When a registered event is triggered, then the registered function is called with
-- the object as first parameter, then event, and then all the parameters for the event itself.
-- @param event The event you want to register.
-- @param object The object you want to register the event for.
-- @param func The function you want executed for this object.
function E:RegisterEventForObject(event, object, func)
if not (event and object and func) then
E:Print('Error. Usage: RegisterEventForObject(event, object, func)')
return
end
local objs = eventTable[event]
if not objs then
objs = {}
eventTable[event] = objs
pcall(eventFrame.RegisterEvent, eventFrame, event)
end
local funcs = objs[object]
if not funcs then
objs[object] = {func}
elseif not tContains(funcs, func) then
tinsert(funcs, func)
end
end
--- Unregisters specified function for the specified object on the specified event.
-- Unless all parameters are supplied it will not unregister.
-- @param event The event you want to unregister an object from.
-- @param object The object you want to unregister a func from.
-- @param func The function you want unregistered for the object.
function E:UnregisterEventForObject(event, object, func)
if not (event and object and func) then
E:Print('Error. Usage: UnregisterEventForObject(event, object, func)')
return
end
local objs = eventTable[event]
local funcs = objs and objs[object]
if funcs then
for index, fnc in ipairs(funcs) do
if func == fnc then
tremove(funcs, index)
break
end
end
if #funcs == 0 then
objs[object] = nil
end
if not next(funcs) then
eventFrame:UnregisterEvent(event)
eventTable[event] = nil
end
end
end
function E:UnregisterAllEventsForObject(object, func)
if not (object and func) then
E:Print('Error. Usage: UnregisterAllEventsForObject(object, func)')
return
end
for event in pairs(eventTable) do
if E:IsEventRegisteredForObject(event, object) then
E:UnregisterEventForObject(event, object, func)
end
end
end
end
function E:ResetAllUI()
E:ResetMovers()
if E.db.lowresolutionset then
E:SetupResolution(true)
end
if E.db.layoutSet then
E:SetupLayout(E.db.layoutSet, true)
end
end
function E:ResetUI(...)
if InCombatLockdown() then E:Print(ERR_NOT_IN_COMBAT) return end
if ... == '' or ... == ' ' or ... == nil then
E:StaticPopup_Show('RESETUI_CHECK')
return
end
E:ResetMovers(...)
end
do
local function errorhandler(err)
return _G.geterrorhandler()(err)
end
function E:CallLoadFunc(func, ...)
xpcall(func, errorhandler, ...)
end
end
function E:CallLoadedModule(obj, silent, object, index)
local name, func
if type(obj) == 'table' then name, func = unpack(obj) else name = obj end
local module = name and E:GetModule(name, silent)
if not module then return end
if func and type(func) == 'string' then
E:CallLoadFunc(module[func], module)
elseif func and type(func) == 'function' then
E:CallLoadFunc(func, module)
elseif module.Initialize then
E:CallLoadFunc(module.Initialize, module)
end
if object and index then object[index] = nil end
end
function E:RegisterInitialModule(name, func)
E.RegisteredInitialModules[#E.RegisteredInitialModules + 1] = (func and {name, func}) or name
end
function E:RegisterModule(name, func)
if E.initialized then
E:CallLoadedModule((func and {name, func}) or name)
else
E.RegisteredModules[#E.RegisteredModules + 1] = (func and {name, func}) or name
end
end
function E:InitializeInitialModules()
for index, object in ipairs(E.RegisteredInitialModules) do
E:CallLoadedModule(object, true, E.RegisteredInitialModules, index)
end
end
function E:InitializeModules()
for index, object in ipairs(E.RegisteredModules) do
E:CallLoadedModule(object, true, E.RegisteredModules, index)
end
end
function E:DBConversions()
-- release converts, only one call per version
if E.db.dbConverted ~= E.version then
E.db.dbConverted = E.version
E:DBConvertBFA()
E:DBConvertSL()
end
-- development converts, always call
end
function E:RefreshModulesDB()
-- this function is specifically used to reference the new database
-- onto the unitframe module, its useful dont delete! D:
wipe(UnitFrames.db) --old ref, dont need so clear it
UnitFrames.db = E.db.unitframe --new ref
end
do
-- Shamelessly taken from AceDB-3.0 and stripped down by Simpy
function E:CopyDefaults(dest, src)
for k, v in pairs(src) do
if type(v) == 'table' then
if not rawget(dest, k) then rawset(dest, k, {}) end
if type(dest[k]) == 'table' then E:CopyDefaults(dest[k], v) end
elseif rawget(dest, k) == nil then
rawset(dest, k, v)
end
end
end
function E:RemoveDefaults(db, defaults)
setmetatable(db, nil)
for k, v in pairs(defaults) do
if type(v) == 'table' and type(db[k]) == 'table' then
E:RemoveDefaults(db[k], v)
if next(db[k]) == nil then db[k] = nil end
elseif db[k] == defaults[k] then
db[k] = nil
end
end
end
end
function E:Initialize()
wipe(E.db)
wipe(E.global)
wipe(E.private)
local playerGUID = UnitGUID('player')
local _, serverID = strsplit('-', playerGUID)
E.serverID = tonumber(serverID)
E.myguid = playerGUID
E.data = E.Libs.AceDB:New('ElvDB', E.DF, true)
E.data.RegisterCallback(E, 'OnProfileChanged', 'StaggeredUpdateAll')
E.data.RegisterCallback(E, 'OnProfileCopied', 'StaggeredUpdateAll')
E.data.RegisterCallback(E, 'OnProfileReset', 'OnProfileReset')
E.charSettings = E.Libs.AceDB:New('ElvPrivateDB', E.privateVars)
E.charSettings.RegisterCallback(E, 'OnProfileChanged', ReloadUI)
E.charSettings.RegisterCallback(E, 'OnProfileCopied', ReloadUI)
E.charSettings.RegisterCallback(E, 'OnProfileReset', 'OnPrivateProfileReset')
E.private = E.charSettings.profile
E.global = E.data.global
E.db = E.data.profile
E.Libs.DualSpec:EnhanceDatabase(E.data, 'ElvUI')
-- default the non thing pixel border color to 191919, otherwise its 000000
if not E.PixelMode then P.general.bordercolor = { r = 0.1, g = 0.1, b = 0.1 } end
if not E.db.unitframe.thinBorders then P.unitframe.colors.borderColor = { r = 0.1, g = 0.1, b = 0.1 } end
E:DBConversions()
E:UIScale()
E:BuildPrefixValues()
E:LoadAPI()
E:LoadCommands()
E:InitializeModules()
E:RefreshModulesDB()
E:LoadMovers()
E:UpdateMedia()
E:UpdateCooldownSettings('all')
E:Tutorials()
E.initialized = true
if E.db.general.smoothingAmount and (E.db.general.smoothingAmount ~= 0.33) then
E:SetSmoothingAmount(E.db.general.smoothingAmount)
end
if not E.private.install_complete then
E:Install()
end
if E:HelloKittyFixCheck() then
E:HelloKittyFix()
end
if E.db.general.kittys then
E:CreateKittys()
E:Delay(5, E.Print, E, L["Type /hellokitty to revert to old settings."])
end
if GetCVarBool('scriptProfile') then
E:StaticPopup_Show('SCRIPT_PROFILE')
end
if E.db.general.loginmessage then
local msg = format(L["LOGIN_MSG"], E.version)
if Chat.Initialized then msg = select(2, Chat:FindURL('CHAT_MSG_DUMMY', msg)) end
print(msg)
print(L["LOGIN_MSG_HELP"])
end
end