ZO_SavedVars = {} local WILD_CARD_KEY = '*' ZO_SAVED_VARS_CHARACTER_NAME_KEY = 1 ZO_SAVED_VARS_CHARACTER_ID_KEY = 2 local GetNewSavedVars local currentPlayerName local currentDisplayName --[[ Creates an interface around raw saved variable access Usage: local sv = ZO_SavedVars:New(savedVariableTable, version, [, namespace], defaults [, profile]) *savedVariableTable - The string name of the saved variable table *version - The current version. If the saved data is a lower version it is destroyed and replaced with the defaults *namespace - An optional string namespace to separate other variables using the same table *defaults - A table describing the default saved variables, see the example below *profile - An optional string to describe the profile, or "Default" The defaults table will be used when accessing a key that doesn't exist or hasn't been set yet. There is a special wild card key that can be used to make all sibling keys inherit the defaults specified by the wild card. The wild card value can be either a value or a table. Example: local defaults = { firstRun = true containers = { ["*"] = { --these are defaults all containers inherit width = 20, height = 50, } } } Note: SavedVars must be created in the EVENT_ADD_ON_LOADED function event in order for the settings file to properly save. local sv = ZO_SavedVars:New("ZO_Ingame_SavedVariables", 1, "ExampleNamespace", defaults) if sv.firstRun then --initialize for first run local primaryContainerSettings = sv.container[1] --automatically generates a table based off the wild card key local container = self:GetPrimaryContainer() container:SetDimensions(primaryContainerSettings.width, primaryContainerSettings.height) sv.firstRun = false end Note: ZO_SavedVars:NewAccountWide provides the same interface as ZO_SavedVars:New, but is used to save account-wide saved vars. --]] function ZO_SavedVars:New(savedVariableTable, version, namespace, defaults, profile, displayName, characterName, characterId, characterKeyType) displayName = displayName or GetDisplayName() characterName = characterName or GetUnitName("player") characterId = characterId or GetCurrentCharacterId() characterKeyType = characterKeyType or ZO_SAVED_VARS_CHARACTER_NAME_KEY return GetNewSavedVars(savedVariableTable, version, namespace, defaults, profile, displayName, characterName, characterId, characterKeyType) end function ZO_SavedVars:NewCharacterNameSettings(savedVariableTable, version, namespace, defaults, profile) return GetNewSavedVars(savedVariableTable, version, namespace, defaults, profile, GetDisplayName(), GetUnitName("player"), GetCurrentCharacterId(), ZO_SAVED_VARS_CHARACTER_NAME_KEY) end function ZO_SavedVars:NewCharacterIdSettings(savedVariableTable, version, namespace, defaults, profile) return GetNewSavedVars(savedVariableTable, version, namespace, defaults, profile, GetDisplayName(), GetUnitName("player"), GetCurrentCharacterId(), ZO_SAVED_VARS_CHARACTER_ID_KEY) end function ZO_SavedVars:NewAccountWide(savedVariableTable, version, namespace, defaults, profile, displayName) displayName = displayName or GetDisplayName() return GetNewSavedVars(savedVariableTable, version, namespace, defaults, profile, displayName) end local CreateExposedInterface local function SearchPath(t, ...) local current = t for i = 1, select("#", ...) do local key = select(i, ...) if key ~= nil then if not current[key] then return nil end current = current[key] end end return current end local function CreatePath(t, ...) local current = t local container local containerKey for i=1, select("#", ...) do local key = select(i, ...) if key ~= nil then if not current[key] then current[key] = {} end container = current containerKey = key current = current[key] end end return current, container, containerKey end local function SetPath(t, value, ...) if value ~= nil then CreatePath(t, ...) end local current = t local parent local lastKey for i = 1, select("#", ...) do local key = select(i, ...) if key ~= nil then lastKey = key parent = current if current == nil then return end current = current[key] end end if parent ~= nil then parent[lastKey] = value end end function GetNewSavedVars(savedVariableTable, version, namespace, defaults, profile, displayName, characterName, characterId, characterKeyType) if type(savedVariableTable) ~= "table" then if _G[savedVariableTable] == nil then _G[savedVariableTable] = {} end savedVariableTable = _G[savedVariableTable] end if type(savedVariableTable) ~= "table" then error("Can only apply saved variables to a table") end --namespace is an optional argument if defaults == nil and type(namespace) == "table" then profile = defaults defaults = namespace namespace = nil end profile = profile or "Default" if type(profile) ~= "string" then error("Profile must be a string or nil") end local finalKey if characterName == nil then finalKey = "$AccountWide" else --Look for a table matching the opposite key type and if there is, then copy it over. This allows us to preserve the old --character name based settings mainly. local characterKey = characterKeyType == ZO_SAVED_VARS_CHARACTER_NAME_KEY and characterName or characterId local oppositeCharacterKey = characterKeyType == ZO_SAVED_VARS_CHARACTER_NAME_KEY and characterId or characterName local oppositeCharacterKeyTable = SearchPath(savedVariableTable, profile, displayName, oppositeCharacterKey, namespace) if oppositeCharacterKeyTable then SetPath(savedVariableTable, oppositeCharacterKeyTable, profile, displayName, characterKey, namespace) SetPath(savedVariableTable, nil, profile, displayName, oppositeCharacterKey, namespace) end --If an old style name based key is still being used then try to upgrade that based on a name change. Less robust. if characterKeyType == ZO_SAVED_VARS_CHARACTER_NAME_KEY and NAME_CHANGE:DidNameChange() then local oldCharacterName = NAME_CHANGE:GetOldCharacterName() local oldNameTable = SearchPath(savedVariableTable, profile, displayName, oldCharacterName, namespace) if oldNameTable then SetPath(savedVariableTable, oldNameTable, profile, displayName, characterName, namespace) SetPath(savedVariableTable, nil, profile, displayName, oldCharacterName, namespace) end end finalKey = characterKey end local finalSavedVar = CreateExposedInterface(savedVariableTable, version, namespace, defaults, profile, displayName, finalKey) if characterName and characterKeyType == ZO_SAVED_VARS_CHARACTER_ID_KEY then savedVariableTable[profile][displayName][finalKey]["$LastCharacterName"] = characterName end return finalSavedVar end local CopyDefaults local function InitializeWildCardFromDefaults(sv, defaults) --Make sure that all existing tables have the appropriate defaults --It's possible for a table to exist in the sv table (that was created in response to a wild card table), but only certain values have been set --It needs to inherit the rest of the values from the default table for savedVarKey, savedVarValue in pairs(sv) do if type(savedVarValue) == "table" then if not rawget(defaults, savedVarValue) then CopyDefaults(savedVarValue, defaults) end end end setmetatable(sv, { --Indexing this will copy the defaults, assign it to the previously missing key and return it --It's almost like it was always there! __index = function(t, k) if k ~= nil then local newValue = CopyDefaults({}, defaults) rawset(t, k, newValue) return newValue end end, }) end local function CopyPotentialTable(sv, key, defaults) if not rawget(sv, key) then --SVs have nothing, copy and create a new entry rawset(sv, key, CopyDefaults({}, defaults)) elseif type(sv[key]) == "table" then --SV has an entry, and it's a table, set it up for defaults CopyDefaults(sv[key], defaults) end --The SV isn't a table, nothing to do end function CopyDefaults(sv, defaults) for defaultKey, defaultValue in pairs(defaults) do if defaultKey == WILD_CARD_KEY then if type(defaultValue) == "table" then --Wild card value is a subtable, initialize the subtable InitializeWildCardFromDefaults(sv, defaultValue) else --Wild card value is (probably) a primitive, just return a copy when the wild card is indexed setmetatable(sv, { __index = function(t, k) if k ~= nil then return defaultValue end end,}) end elseif type(defaultValue) == "table" then CopyPotentialTable(sv, defaultKey, defaultValue) elseif rawget(sv, defaultKey) == nil then rawset(sv, defaultKey, defaultValue) end end return sv end local function InitializeRawTable(rawSavedTable, profile, namespace, displayName, playerName) return CreatePath(rawSavedTable, profile, displayName, playerName, namespace) end local function ExposeMethods(interface, namespace, rawSavedTable, defaults, profile, cachedInterfaces) --Gets an interface to the same saved variable table, but for a different character and/or world interface.GetInterfaceForCharacter = function(self, displayName, playerName) if currentDisplayName == displayName and currentPlayerName == playerName then return self end if not cachedInterfaces[displayName] then cachedInterfaces[displayName] = {} end if not cachedInterfaces[displayName][playerName] then cachedInterfaces[displayName][playerName] = CreateExposedInterface(rawSavedTable, self.version, namespace, defaults, profile, displayName, playerName, cachedInterfaces) end return cachedInterfaces[displayName][playerName] end interface.ResetToDefaults = function(self) local sv = getmetatable(self).__index if sv then local version = sv.version ZO_ClearTable(sv) sv.version = version if self.default then CopyDefaults(sv, self.default) end end end end function CreateExposedInterface(rawSavedTable, version, namespace, defaults, profile, displayName, playerName, cachedInterfaces) local current, container, containerKey = InitializeRawTable(rawSavedTable, profile, namespace, displayName, playerName) --if the data is unversioned or out of date, nuke the data first if current.version == nil or current.version < version then ZO_ClearTable(current) end current.version = version if defaults then CopyDefaults(current, defaults) end local interfaceMT = { __index = current, __newindex = function(t, k, v) current[k] = v end, } local interface = { default = defaults, } cachedInterfaces = cachedInterfaces or {} ExposeMethods(interface, namespace, rawSavedTable, defaults, profile, cachedInterfaces) return setmetatable(interface, interfaceMT) end