--[[ /$$$$$$$ /$$ /$$ | $$__ $$ |__/ | $$ | $$ \ $$ /$$$$$$ /$$$$$$ /$$ /$$$$$$$/$$$$$$ /$$$$$$ /$$ /$$ | $$$$$$$/ /$$__ $$ /$$__ $$| $$ /$$_____/_ $$_/ /$$__ $$| $$ | $$ | $$__ $$| $$$$$$$$| $$ \ $$| $$| $$$$$$ | $$ | $$ \__/| $$ | $$ | $$ \ $$| $$_____/| $$ | $$| $$ \____ $$ | $$ /$$| $$ | $$ | $$ | $$ | $$| $$$$$$$| $$$$$$$| $$ /$$$$$$$/ | $$$$/| $$ | $$$$$$$ |__/ |__/ \_______/ \____ $$|__/|_______/ \___/ |__/ \____ $$ /$$ \ $$ /$$ | $$ | $$$$$$/ | $$$$$$/ \______/ \______/ Registry is a component used to manage packages and scripts embedded into the SVUI core addon. It's main purpose is to keep all methods and logic needed to properly keep core add-ins functioning outside of the core object. --]] --[[ LOCALIZED GLOBALS ]]-- --GLOBAL NAMESPACE local _G = getfenv(0); --LUA local unpack = _G.unpack; local select = _G.select; local assert = _G.assert; local type = _G.type; local error = _G.error; local pcall = _G.pcall; local print = _G.print; local ipairs = _G.ipairs; local pairs = _G.pairs; local next = _G.next; local rawset = _G.rawset; local rawget = _G.rawget; local tostring = _G.tostring; local tonumber = _G.tonumber; local getmetatable = _G.getmetatable; local setmetatable = _G.setmetatable; --STRING local string = _G.string; local upper = string.upper; local format = string.format; local find = string.find; local match = string.match; local gsub = string.gsub; --MATH local math = _G.math; local floor = math.floor --TABLE local table = _G.table; local tsort = table.sort; local tconcat = table.concat; local tinsert = _G.tinsert; local tremove = _G.tremove; local twipe = _G.wipe; --BLIZZARD API local ReloadUI = _G.ReloadUI; local GetLocale = _G.GetLocale; local CreateFrame = _G.CreateFrame; local IsAddOnLoaded = _G.IsAddOnLoaded; local GetNumAddOns = _G.GetNumAddOns; local GetAddOnInfo = _G.GetAddOnInfo; local LoadAddOn = _G.LoadAddOn; local EnableAddOn = _G.EnableAddOn; local GetSpecialization = _G.GetSpecialization; local GetAddOnMetadata = _G.GetAddOnMetadata; local IsAddOnLoadOnDemand = _G.IsAddOnLoadOnDemand; --[[ LIB CONSTRUCT ]]-- local lib = LibSuperVillain:NewLibrary("Registry") if not lib then return end -- No upgrade needed --[[ ADDON DATA ]]-- local CoreName, CoreObject = ... local AddonVersion = GetAddOnMetadata(..., "Version"); local SchemaFromMeta = "X-" .. CoreName .. "-Schema"; local HeaderFromMeta = "X-" .. CoreName .. "-Header"; local InterfaceVersion = select(4, GetBuildInfo()); --[[ COMMON LOCAL VARS ]]-- local GLOBAL_FILENAME = CoreName.."_Global"; local ERROR_FILENAME = CoreName.."_Errors"; local PROFILE_FILENAME = CoreName.."_Profile"; local CACHE_FILENAME = CoreName.."_Cache"; local SOURCE_KEY = 1; local GLOBAL_SV, PROFILE_SV, CACHE_SV, ERROR_CACHE, PLUGINS, MODULES; local PluginString = "" local AllowedIndexes, LoadOnDemand = {},{}; local Callbacks, ScriptQueue = {},{}; local playerClass = select(2,UnitClass("player")); local INFO_FORMAT = "|cffFFFF00%s|r\n |cff33FF00Version: %s|r |cff0099FFby %s|r"; if GetLocale() == "ruRU" then INFO_FORMAT = "|cffFFFF00%s|r\n |cff33FF00Версия: %s|r |cff0099FFот %s|r"; end --[[ LIB EVENT LISTENER ]]-- lib.EventManager = CreateFrame("Frame", nil) --[[ COMMON META METHODS ]]-- local rootstring = function(self) return self.NameID end --[[ CUSTOM LUA METHODS ]]-- --LOCAL HELPERS local function formatValueString(text) if "string" == type(text) then text = gsub(text,"\n","\\n") if match(gsub(text,"[^'\"]",""),'^"+$') then return "'"..text.."'"; else return '"'..gsub(text,'"','\\"')..'"'; end else return tostring(text); end end local function formatKeyString(text) if("string" == type(text) and match(text,"^[_%a][_%a%d]*$")) then return text; else return "["..formatValueString(text).."]"; end end --APPENDED METHODS function table.dump(targetTable) local dumpTable = {}; local dumpCheck = {}; for key,value in ipairs(targetTable) do tinsert(dumpTable, formatValueString(value)); dumpCheck[key] = true; end for key,value in pairs(targetTable) do if not dumpCheck[key] then tinsert(dumpTable, "\n "..formatKeyString(key).." = "..formatValueString(value)); end end local output = tconcat(dumpTable, ", "); return "{ "..output.." }"; end function math.parsefloat(value,decimal) if(decimal and decimal > 0) then local calc1 = 10 ^ decimal; local calc2 = (value * calc1) + 0.5; return floor(calc2) / calc1 end return floor(value + 0.5) end function table.copy(targetTable,deepCopy,mergeTable) mergeTable = mergeTable or {}; if(targetTable == nil) then return nil end if(mergeTable[targetTable]) then return mergeTable[targetTable] end local replacementTable = {} for key,value in pairs(targetTable)do if deepCopy and type(value) == "table" then replacementTable[key] = table.copy(value, deepCopy, mergeTable) else replacementTable[key] = value end end setmetatable(replacementTable, table.copy(getmetatable(targetTable), deepCopy, mergeTable)) mergeTable[targetTable] = replacementTable; return replacementTable end function string.trim(this) return find(this, '^%s*$') and '' or match(this, '^%s*(.*%S)') end function string.color(this, color) return format("|cff%s%s|r", color, this) end function string.link(this, prefix, text, color) text = tostring(text) local colorstring = tostring(this):color(color or "ffffff") return format("|H%s:%s|h%s|h", prefix, text, colorstring) end function string.explode(str, delim) local res = {} local pattern = format("([^%s]+)%s()", delim, delim) local line, pos while (true) do line, pos = match(str, pattern, pos) if line == nil then break end tinsert(res, line) end return res end --[[ /$$$$$$$ /$$ /$$ | $$__ $$ | $$ | $$ | $$ \ $$ /$$$$$$ /$$$$$$ /$$$$$$ | $$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$ | $$ | $$|____ $$|_ $$_/ |____ $$| $$__ $$|____ $$ /$$_____/ /$$__ $$ | $$ | $$ /$$$$$$$ | $$ /$$$$$$$| $$ \ $$ /$$$$$$$| $$$$$$ | $$$$$$$$ | $$ | $$/$$__ $$ | $$ /$$/$$__ $$| $$ | $$/$$__ $$ \____ $$| $$_____/ | $$$$$$$/ $$$$$$$ | $$$$/ $$$$$$$| $$$$$$$/ $$$$$$$ /$$$$$$$/| $$$$$$$ |_______/ \_______/ \___/ \_______/|_______/ \_______/|_______/ \_______/ DataBase is a component used to create and manage SVUI data objects. It's main purpose is to keep all methods and logic needed to properly maintain valid data outside of the core object. --]] --DATABASE LOCAL HELPERS local function SanitizeStorage(data) for k,v in pairs(data) do if(k == "STORED" or k == "SAFEDATA" or k == "LAYOUT") then data[k] = nil end end end local function copydefaults(d, s) if(type(s) ~= "table") then return end if(type(d) ~= "table") then return end for k, v in pairs(s) do local saved = rawget(d, k) if type(v) == "table" then if not saved then rawset(d, k, {}) end copydefaults(d[k], v) else rawset(d, k, v) end end end local function tablecopy(d, s, debug) if(debug) then print(debug) assert(type(s) == "table", "tablecopy ERROR: source (" .. debug .. ") is not a table") assert(type(d) == "table", "tablecopy ERROR: destination (" .. debug .. ") is not a table") end if(type(s) ~= "table") then return end if(type(d) ~= "table") then return end for k, v in pairs(s) do local saved = rawget(d, k) if type(v) == "table" then if not saved then rawset(d, k, {}) end tablecopy(d[k], v) elseif(saved == nil or (saved and type(saved) ~= type(v))) then rawset(d, k, v) end end end local function tablesplice(targetTable, mergeTable) if type(targetTable) ~= "table" then targetTable = {} end if type(mergeTable) == 'table' then for key,val in pairs(mergeTable) do if type(val) == "table" then val = tablesplice(targetTable[key], val) end targetTable[key] = val end end return targetTable end local function importdata(s, d) if type(d) ~= "table" then d = {} end if type(s) == "table" then for k,v in pairs(s) do if type(v) == "table" then v = importdata(v, d[k]) end d[k] = v end end return d end local function removedefaults(db, src, nometa) if(type(src) ~= "table") then if(db == src) then db = nil end return end if(not nometa) then setmetatable(db, nil) end for k,v in pairs(src) do if type(v) == "table" and type(db[k]) == "table" then removedefaults(db[k], v, nometa) if next(db[k]) == nil then db[k] = nil end else if db[k] == v then db[k] = nil end end end end --DATABASE META METHODS local meta_transdata = { __index = function(t, k) if(not k or k == "") then return end local sv = rawget(t, "data") local dv = rawget(t, "defaults") local src = dv and dv[k] if(src ~= nil) then if(type(src) == "table") then if(sv[k] == nil or (sv[k] ~= nil and type(sv[k]) ~= "table")) then sv[k] = {} end tablecopy(sv[k], src) else if(sv[k] == nil or (sv[k] ~= nil and type(sv[k]) ~= type(src))) then sv[k] = src end end end rawset(t, k, sv[k]) return rawget(t, k) end, } local meta_database = { __index = function(t, k) if(not k or k == "") then return end local sv = rawget(t, "data") if(not sv[k]) then sv[k] = {} end rawset(t, k, sv[k]) return rawget(t, k) end, } local function LiveProfileChange() local LastKey = SOURCE_KEY if(PROFILE_SV.SAFEDATA and PROFILE_SV.SAFEDATA.dualSpecEnabled) then SOURCE_KEY = GetSpecialization() lib.EventManager:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED") if(not SOURCE_KEY) then SOURCE_KEY = 1 end if(LastKey ~= SOURCE_KEY) then --construct core dataset local db = setmetatable({}, meta_transdata) db.data = PROFILE_SV.STORED[SOURCE_KEY] db.defaults = CoreObject.configs wipe(CoreObject.db) CoreObject.db = db local cache = setmetatable({}, meta_database) cache.data = CACHE_SV.STORED[SOURCE_KEY] wipe(CoreObject.cache) CoreObject.cache = cache if(CoreObject.ReLoad) then CoreObject:ReLoad() end lib:RefreshAll() end else SOURCE_KEY = 1 lib.EventManager:UnregisterEvent("ACTIVE_TALENT_GROUP_CHANGED") end end --DATABASE PUBLIC METHODS function lib:Remove(key) if(GLOBAL_SV.profiles[key]) then GLOBAL_SV.profiles[key] = nil end if(GLOBAL_SV.cache[key]) then GLOBAL_SV.cache[key] = nil end twipe(GLOBAL_SV.profileKeys) for k,v in pairs(GLOBAL_SV.profiles) do GLOBAL_SV.profileKeys[k] = k end for k,v in pairs(GLOBAL_SV.cache) do GLOBAL_SV.profileKeys[k] = k end end function lib:GetProfiles() local list = GLOBAL_SV.profileKeys or {} return list end function lib:CheckProfiles() local hasProfile = false local list = GLOBAL_SV.profileKeys or {} for key,_ in pairs(list) do hasProfile = true end return hasProfile end function lib:ImportDatabase(key) if(not GLOBAL_SV.profiles[key]) then GLOBAL_SV.profiles[key] = {} end; PROFILE_SV.STORED[SOURCE_KEY] = GLOBAL_SV.profiles[key] if(not GLOBAL_SV.cache[key]) then GLOBAL_SV.cache[key] = {} end; CACHE_SV.STORED[SOURCE_KEY] = GLOBAL_SV.cache[key] ReloadUI() end function lib:ExportDatabase(key) local export, saved if(not GLOBAL_SV.profiles[key]) then GLOBAL_SV.profiles[key] = {} end; export = rawget(CoreObject.db, "data"); saved = GLOBAL_SV.profiles[key]; tablecopy(saved, export); if not GLOBAL_SV.cache[key] then GLOBAL_SV.cache[key] = {} end export = rawget(CoreObject.cache, "data") saved = GLOBAL_SV.cache[key] tablecopy(saved, export); twipe(GLOBAL_SV.profileKeys) for k,v in pairs(GLOBAL_SV.profiles) do GLOBAL_SV.profileKeys[k] = k end for k,v in pairs(GLOBAL_SV.cache) do GLOBAL_SV.profileKeys[k] = k end end function lib:WipeDatabase() for k,v in pairs(PROFILE_SV.STORED[SOURCE_KEY]) do PROFILE_SV.STORED[SOURCE_KEY][k] = nil end end function lib:WipeCache(index) if(index) then CACHE_SV.STORED[SOURCE_KEY][index] = nil else for k,v in pairs(CACHE_SV.STORED[SOURCE_KEY]) do CACHE_SV.STORED[SOURCE_KEY][k] = nil end end end function lib:WipeGlobal() for k,v in pairs(GLOBAL_SV) do GLOBAL_SV[k] = nil end end function lib:GetSafeData(index) if(index) then return PROFILE_SV.SAFEDATA[index] else return PROFILE_SV.SAFEDATA end end function lib:SaveSafeData(index, value) PROFILE_SV.SAFEDATA[index] = value end function lib:CheckData(schema, key) local file = PROFILE_SV.STORED[SOURCE_KEY][schema] print("______" .. schema .. ".db[" .. key .. "]_____") print(file[key]) print("______SAVED_____") end function lib:NewGlobal(index) index = index or CoreObject.Schema if(not GLOBAL_SV[index]) then GLOBAL_SV[index] = {} end return GLOBAL_SV[index] end --REGISTRY LOCAL HELPERS local function HandleErrors(schema, action, catch) schema = schema or "LibSuperVillain:Registry" action = action or "Unknown Function" local timestamp = date("%m/%d/%y %H:%M:%S") local err_message = ("%s [%s] - (%s) %s"):format(schema, action, timestamp, catch) tinsert(ERROR_CACHE.FOUND, err_message) if(CoreObject.DebugMode == true) then --ScriptErrorsFrame_OnError(catch, false, true) CoreObject:Debugger(err_message) end end local function LoadingProxy(schema, obj) if(not obj) then return end if(not obj.initialized) then if(obj.Load and type(obj.Load) == "function") then local _, catch = pcall(obj.Load, obj) if(catch) then HandleErrors(schema, "Load", catch) else obj.initialized = true end end else if(obj.ReLoad and type(obj.ReLoad) == "function") then local _, catch = pcall(obj.ReLoad, obj) if(catch) then HandleErrors(schema, "ReLoad", catch) end end end end --OBJECT INTERNALS local changeDBVar = function(self, value, key, sub1, sub2, sub3) local db = CoreObject.db[self.Schema] if((sub1 and sub2 and sub3) and (db[sub1] and db[sub1][sub2] and db[sub1][sub2][sub3])) then db[sub1][sub2][sub3][key] = value elseif((sub1 and sub2) and (db[sub1] and db[sub1][sub2])) then db[sub1][sub2][key] = value elseif(sub1 and db[sub1]) then db[sub1][key] = value else db[key] = value end if(self.UpdateLocals) then self:UpdateLocals() end end local changePluginDBVar = function(self, value, key, sub1, sub2, sub3) local db = self.db if((sub1 and sub2 and sub3) and (db[sub1] and db[sub1][sub2] and db[sub1][sub2][sub3])) then db[sub1][sub2][sub3][key] = value elseif((sub1 and sub2) and (db[sub1] and db[sub1][sub2])) then db[sub1][sub2][key] = value elseif(sub1 and db[sub1]) then db[sub1][key] = value else db[key] = value end if(self.UpdateLocals) then self:UpdateLocals() end end local innerOnEvent = function(self, event, ...) local obj = self.___owner local fn = self[event] if(fn and type(fn) == "function" and obj.initialized) then local _, catch = pcall(fn, obj, event, ...) if(catch) then local schema = obj.Schema HandleErrors(schema, event, catch) end end end local registerEvent = function(self, eventname, eventfunc) if not self.___eventframe then self.___eventframe = CreateFrame("Frame", nil) self.___eventframe.___owner = self self.___eventframe:SetScript("OnEvent", innerOnEvent) end if(not self.___eventframe[eventname]) then local fn = eventfunc if type(eventfunc) == "string" then fn = self[eventfunc] elseif(not fn and self[eventname]) then fn = self[eventname] end self.___eventframe[eventname] = fn end self.___eventframe:RegisterEvent(eventname) end local unregisterEvent = function(self, event, ...) if(self.___eventframe) then self.___eventframe:UnregisterEvent(event) end end local innerOnUpdate = function(self, elapsed) if self.elapsed and self.elapsed > (self.throttle) then local obj = self.___owner local callbacks = self.callbacks for name, fn in pairs(callbacks) do local _, catch = pcall(fn, obj) if(catch and CoreObject.Debugging) then print(catch) end end self.elapsed = 0 else self.elapsed = (self.elapsed or 0) + elapsed end end local registerUpdate = function(self, updatefunc, throttle) if not self.___updateframe then self.___updateframe = CreateFrame("Frame", nil); self.___updateframe.___owner = self; self.___updateframe.callbacks = {}; self.___updateframe.elapsed = 0; self.___updateframe.throttle = throttle or 0.2; end if(updatefunc and type(updatefunc) == "string" and self[updatefunc]) then self.___updateframe.callbacks[updatefunc] = self[updatefunc] end self.___updateframe:SetScript("OnUpdate", innerOnUpdate) end local unregisterUpdate = function(self, updatefunc) if(updatefunc and type(updatefunc) == "string" and self.___updateframe.callbacks[updatefunc]) then self.___updateframe.callbacks[updatefunc] = nil if(#self.___updateframe.callbacks == 0) then self.___updateframe:SetScript("OnUpdate", nil) end else self.___updateframe:SetScript("OnUpdate", nil) end end local function SetPluginString(addonName) local author = GetAddOnMetadata(addonName, "Author") or "Unknown" local name = GetAddOnMetadata(addonName, "Title") or addonName local version = GetAddOnMetadata(addonName, "Version") or "???" return INFO_FORMAT:format(name, version, author) end --REGISTRY PUBLIC METHODS function lib:RunCallbacks() for i=1, #Callbacks do local fn = Callbacks[i] if(fn and type(fn) == "function") then local _, catch = pcall(fn) if(catch) then HandleErrors("LibSuperVillain:Registry", "Callback", catch) end end end end function lib:RefreshModule(schema) local obj = CoreObject[schema] LoadingProxy(schema, obj) end function lib:RefreshPlugin(schema) local obj = _G[schema] LoadingProxy(schema, obj) end function lib:RefreshAll() for _,schema in pairs(MODULES) do local obj = CoreObject[schema] LoadingProxy(schema, obj) end for schema,_ in pairs(PLUGINS) do local obj = _G[schema] LoadingProxy(schema, obj) end end function lib:LiveUpdate() if(PROFILE_SV.SAFEDATA.NEEDSLIVEUPDATE and not C_PetBattles.IsInBattle()) then self:RefreshAll() PROFILE_SV.SAFEDATA.NEEDSLIVEUPDATE = false end end function lib:GetModuletable() return MODULES end function lib:GetPlugins() return PluginString end function lib:CheckDualProfile() return PROFILE_SV.SAFEDATA.dualSpecEnabled end function lib:ToggleDualProfile(enabled) PROFILE_SV.SAFEDATA.dualSpecEnabled = enabled if(enabled) then self.EventManager:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED") LiveProfileChange() else self.EventManager:UnregisterEvent("ACTIVE_TALENT_GROUP_CHANGED") end end function lib:LoadQueuedModules() if MODULES then for i=1,#MODULES do local schema = MODULES[i] local obj = CoreObject[schema] local data = CoreObject.db[schema] if(obj and data and data.enable and (not obj.initialized)) then local halt = false if(data.incompatible) then for addon,_ in pairs(data.incompatible) do if IsAddOnLoaded(addon) then halt = true end end end if(not halt) then LoadingProxy(schema, obj) end end end end end function lib:LoadQueuedPlugins() if PLUGINS then for schema,files in pairs(PLUGINS) do local obj = _G[schema] local enabled = PROFILE_SV.SAFEDATA[schema].enable if(obj and enabled and (not obj.initialized)) then local halt = false if((not obj.db) and files.PROFILE and _G[files.PROFILE]) then local db = setmetatable({}, meta_transdata) db.data = _G[files.PROFILE] db.defaults = obj.configs obj.db = db end if((not obj.cache) and files.CACHE and _G[files.CACHE]) then local cache = setmetatable({}, meta_database) cache.data = _G[files.CACHE] obj.cache = cache end if((not obj.public) and files.GLOBAL and _G[files.GLOBAL]) then local public = setmetatable({}, meta_database) public.data = _G[files.GLOBAL] obj.public = public end if(obj.db and obj.db.incompatible) then for addon,_ in pairs(obj.db.incompatible) do if IsAddOnLoaded(addon) then halt = true end end end if(not halt) then LoadingProxy(schema, obj) end end end end end --[[ CONSTRUCTORS ]]-- function lib:NewPlugin(addonName, addonObject, pfile, gfile, cfile) local version = GetAddOnMetadata(addonName, "Version") local header = GetAddOnMetadata(addonName, HeaderFromMeta) local schema = GetAddOnMetadata(addonName, SchemaFromMeta) local lod = IsAddOnLoadOnDemand(addonName) PROFILE_SV.SAFEDATA[schema] = PROFILE_SV.SAFEDATA[schema] or {["enable"] = true} local addonmeta = {} local oldmeta = getmetatable(addonObject) if oldmeta then for k, v in pairs(oldmeta) do addonmeta[k] = v end end addonmeta.__tostring = rootstring setmetatable( addonObject, addonmeta ) addonObject.Version = version addonObject.NameID = addonName addonObject.TitleID = header addonObject.Schema = schema addonObject.LoD = lod addonObject.initialized = false addonObject.CombatLocked = false addonObject.ChangeDBVar = changePluginDBVar addonObject.RegisterEvent = registerEvent addonObject.UnregisterEvent = unregisterEvent addonObject.RegisterUpdate = registerUpdate addonObject.UnregisterUpdate = unregisterUpdate if(IsAddOnLoaded(addonName) and not lod) then CoreObject.Options.args.plugins.args.pluginOptions.args[schema] = { type = "group", name = header, childGroups = "tree", args = { enable = { order = 1, type = "toggle", name = "Enable", get = function() return PROFILE_SV.SAFEDATA[schema].enable end, set = function(key, value) PROFILE_SV.SAFEDATA[schema].enable = value; CoreObject:StaticPopup_Show("RL_CLIENT") end, } } } end _G[schema] = addonObject if(not PLUGINS) then PLUGINS = {} end PLUGINS[schema] = { ["GLOBAL"] = gfile, ["PROFILE"] = pfile, ["CACHE"] = cfile, }; local infoString = SetPluginString(addonName) local oldString = PluginString PluginString = ("%s%s\n"):format(oldString, infoString) return addonObject end local function NewLoadOnDemand(addonName, schema, header) LoadOnDemand[schema] = addonName; PROFILE_SV.SAFEDATA[schema] = PROFILE_SV.SAFEDATA[schema] or {["enable"] = false} CoreObject.Options.args.plugins.args.pluginOptions.args[schema] = { type = "group", name = header, childGroups = "tree", args = { enable = { order = 1, type = "execute", width = "full", name = function() local nameString = "Disable" if(not IsAddOnLoaded(addonName)) then nameString = "Enable" end return nameString end, func = function() if(not IsAddOnLoaded(addonName)) then local loaded, reason = LoadAddOn(addonName) PROFILE_SV.SAFEDATA[schema].enable = true lib:LoadQueuedPlugins() else PROFILE_SV.SAFEDATA[schema].enable = false CoreObject:StaticPopup_Show("RL_CLIENT") end end, } } } end --LIBRARY EVENT HANDLING local Library_OnEvent = function(self, event, arg, ...) if(event == "PLAYER_LOGOUT") then local sv = rawget(CoreObject.db, "data") local src = rawget(CoreObject.db, "defaults") for k,v in pairs(sv) do if(not src[k]) then sv[k] = nil elseif(src[k] ~= nil and (not LoadOnDemand[k])) then removedefaults(sv[k], src[k]) end end elseif(event == "ADDON_LOADED") then if(arg == CoreName) then if(not CoreObject.___loaded and CoreObject.Load) then CoreObject:Load() CoreObject.___loaded = true self:UnregisterEvent("ADDON_LOADED") end end elseif(event == "PLAYER_LOGIN") then if(not CoreObject.___initialized and CoreObject.Initialize and IsLoggedIn()) then CoreObject:Initialize() CoreObject.___initialized = true self:UnregisterEvent("PLAYER_LOGIN") end elseif(event == "ACTIVE_TALENT_GROUP_CHANGED") then LiveProfileChange() end end -- CORE OBJECT CONSTRUCT local Core_NewCallback = function(self, fn) if(fn and type(fn) == "function") then Callbacks[#Callbacks+1] = fn end end local Core_NewScript = function(self, fn) if(fn and type(fn) == "function") then ScriptQueue[#ScriptQueue+1] = fn end end local Core_NewPackage = function(self, schema, header) if(self[schema]) then return end if(not MODULES) then MODULES = {} end MODULES[#MODULES+1] = schema AllowedIndexes[schema] = schema local addonName = ("SVUI [%s]"):format(schema) local obj = { NameID = addonName, TitleID = header, Schema = schema, initialized = false, CombatLocked = false, ChangeDBVar = changeDBVar, RegisterEvent = registerEvent, UnregisterEvent = unregisterEvent, RegisterUpdate = registerUpdate, UnregisterUpdate = unregisterUpdate } local addonmeta = {} local oldmeta = getmetatable(obj) if oldmeta then for k, v in pairs(oldmeta) do addonmeta[k] = v end end addonmeta.__tostring = rootstring setmetatable( obj, addonmeta ) self[schema] = obj return self[schema] end local Core_ResetData = function(self, sub, sub2, sub3) local data = self.db local sv = rawget(data, "data") local src = rawget(data, "defaults") local targetData if(sub3 and sub2 and sv and sv[sub] and sv[sub][sub2]) then targetData = sv[sub][sub2][sub3] elseif(sub2 and sv and sv[sub]) then targetData = sv[sub][sub2] elseif(sub and sv) then targetData = sv[sub] else targetData = sv end if(targetData) then for k,v in pairs(targetData) do targetData[k] = nil end else sv = {} end tablecopy(sv, src) end function lib:NewCore(gfile, efile, pfile, cfile) --meta assurance local mt = {}; local old = getmetatable(CoreObject); if old then for k, v in pairs(old) do mt[k] = v end end mt.__tostring = rootstring; setmetatable(CoreObject, mt); --database GLOBAL_FILENAME = gfile or GLOBAL_FILENAME ERROR_FILENAME = efile or ERROR_FILENAME PROFILE_FILENAME = pfile or PROFILE_FILENAME CACHE_FILENAME = cfile or CACHE_FILENAME --events if(not self.EventManager.Initialized) then self.EventManager:RegisterEvent("ADDON_LOADED") self.EventManager:RegisterEvent("PLAYER_LOGIN") self.EventManager:RegisterEvent("PLAYER_LOGOUT") self.EventManager:SetScript("OnEvent", Library_OnEvent) self.EventManager.Initialized = true end --internals CoreObject.___errors = {}; CoreObject.NameID = CoreName; CoreObject.Version = AddonVersion; CoreObject.GameVersion = tonumber(InterfaceVersion); CoreObject.DebugMode = false; CoreObject.Schema = GetAddOnMetadata(CoreName, SchemaFromMeta); CoreObject.TitleID = GetAddOnMetadata(CoreName, HeaderFromMeta); CoreObject.RegisterEvent = registerEvent CoreObject.UnregisterEvent = unregisterEvent CoreObject.RegisterUpdate = registerUpdate CoreObject.UnregisterUpdate = unregisterUpdate CoreObject.NewCallback = Core_NewCallback CoreObject.NewScript = Core_NewScript CoreObject.NewPackage = Core_NewPackage CoreObject.ResetData = Core_ResetData CoreObject.db = tablesplice(CoreObject.configs, {}) --set global _G[CoreName] = CoreObject; return _G[CoreName] end -- INITIALIZE AND LAUNCH function lib:Initialize() local coreSchema = CoreObject.Schema --GLOBAL SAVED VARIABLES if not _G[GLOBAL_FILENAME] then _G[GLOBAL_FILENAME] = {} end GLOBAL_SV = _G[GLOBAL_FILENAME] if(GLOBAL_SV.profileKeys) then twipe(GLOBAL_SV.profileKeys) else GLOBAL_SV.profileKeys = {} end GLOBAL_SV.profiles = GLOBAL_SV.profiles or {} for k,v in pairs(GLOBAL_SV.profiles) do GLOBAL_SV.profileKeys[k] = k end GLOBAL_SV.cache = GLOBAL_SV.cache or {} for k,v in pairs(GLOBAL_SV.cache) do GLOBAL_SV.profileKeys[k] = k end --SAVED ERRORS if not _G[ERROR_FILENAME] then _G[ERROR_FILENAME] = {} end ERROR_CACHE = _G[ERROR_FILENAME] local datestamp = date("%m_%d_%y") if(ERROR_CACHE.TODAY and ERROR_CACHE.TODAY ~= datestamp) then ERROR_CACHE.FOUND = {} end if(not ERROR_CACHE.FOUND) then ERROR_CACHE.FOUND = {} end ERROR_CACHE.TODAY = datestamp --CACHE SAVED VARIABLES if not _G[CACHE_FILENAME] then _G[CACHE_FILENAME] = {} end CACHE_SV = _G[CACHE_FILENAME] if(not CACHE_SV.STORED) then CACHE_SV.STORED = {} CACHE_SV.STORED[1] = {} CACHE_SV.STORED[2] = {} CACHE_SV.STORED[3] = {} if playerClass == "DRUID" then CACHE_SV.STORED[4] = {} end for k,v in pairs(CACHE_SV) do if(k ~= "STORED") then CACHE_SV.STORED[1][k] = v end end end --PROFILE SAVED VARIABLES if not _G[PROFILE_FILENAME] then _G[PROFILE_FILENAME] = {} end PROFILE_SV = _G[PROFILE_FILENAME] PROFILE_SV.SAFEDATA = PROFILE_SV.SAFEDATA or {dualSpecEnabled = false} if not PROFILE_SV.SAFEDATA.NEEDSLIVEUPDATE then PROFILE_SV.SAFEDATA.NEEDSLIVEUPDATE = false end if(PROFILE_SV.SAFEDATA and PROFILE_SV.SAFEDATA.dualSpecEnabled) then SOURCE_KEY = GetSpecialization() or 1 self.EventManager:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED") else SOURCE_KEY = 1 self.EventManager:UnregisterEvent("ACTIVE_TALENT_GROUP_CHANGED") end if(not PROFILE_SV.STORED) then PROFILE_SV.STORED = {} PROFILE_SV.STORED[1] = {} PROFILE_SV.STORED[1][coreSchema] = {} PROFILE_SV.STORED[2] = {} PROFILE_SV.STORED[2][coreSchema] = {} PROFILE_SV.STORED[3] = {} PROFILE_SV.STORED[3][coreSchema] = {} if playerClass == "DRUID" then PROFILE_SV.STORED[4] = {} PROFILE_SV.STORED[4][coreSchema] = {} end --Attempt to copy any prior variables, even outdated if(PROFILE_SV.system or (MODULES[1] and PROFILE_SV[MODULES[1]])) then for k,v in pairs(PROFILE_SV) do if(k == "system") then tablecopy(v, PROFILE_SV.STORED[1][coreSchema]) elseif(k == "media" or k == "filter") then PROFILE_SV.STORED[1][coreSchema][k] = v elseif(AllowedIndexes[k]) then PROFILE_SV.STORED[1][k] = v end end end else PROFILE_SV.STORED[1] = PROFILE_SV.STORED[1] or {} PROFILE_SV.STORED[1][coreSchema] = PROFILE_SV.STORED[1][coreSchema] or {} SanitizeStorage(PROFILE_SV.STORED[1]) PROFILE_SV.STORED[2] = PROFILE_SV.STORED[2] or {} PROFILE_SV.STORED[2][coreSchema] = PROFILE_SV.STORED[2][coreSchema] or {} SanitizeStorage(PROFILE_SV.STORED[2]) PROFILE_SV.STORED[3] = PROFILE_SV.STORED[3] or {} PROFILE_SV.STORED[3][coreSchema] = PROFILE_SV.STORED[3][coreSchema] or {} SanitizeStorage(PROFILE_SV.STORED[3]) if playerClass == "DRUID" then PROFILE_SV.STORED[4] = PROFILE_SV.STORED[4] or {} PROFILE_SV.STORED[4][coreSchema] = PROFILE_SV.STORED[4][coreSchema] or {} SanitizeStorage(PROFILE_SV.STORED[4]) elseif PROFILE_SV.STORED[4] then PROFILE_SV.STORED[4] = nil end end for k,v in pairs(PROFILE_SV) do if(k ~= "STORED" and k ~= "SAFEDATA") then PROFILE_SV[k] = nil end end --construct core dataset local db = setmetatable({}, meta_transdata) db.data = PROFILE_SV.STORED[SOURCE_KEY] db.defaults = CoreObject.configs CoreObject.db = db local cache = setmetatable({}, meta_database) cache.data = CACHE_SV.STORED[SOURCE_KEY] CoreObject.cache = cache --check for LOD plugins local addonCount = GetNumAddOns() for i = 1, addonCount do local addonName, _, _, _, _, reason = GetAddOnInfo(i) local lod = IsAddOnLoadOnDemand(i) local header = GetAddOnMetadata(i, HeaderFromMeta) local schema = GetAddOnMetadata(i, SchemaFromMeta) if(lod and schema) then NewLoadOnDemand(addonName, schema, header) end end end function lib:Launch() if LoadOnDemand then for schema,name in pairs(LoadOnDemand) do local db = PROFILE_SV.SAFEDATA[schema] if(db and (db.enable or db.enable ~= false)) then if(not IsAddOnLoaded(name)) then local loaded, reason = LoadAddOn(name) end EnableAddOn(name) end end end self:LoadQueuedModules() self:LoadQueuedPlugins() if ScriptQueue then for i=1, #ScriptQueue do local fn = ScriptQueue[i] if(fn and type(fn) == "function") then fn() end end ScriptQueue = nil end PROFILE_SV.SAFEDATA.NEEDSLIVEUPDATE = C_PetBattles.IsInBattle() end