Files
OEVG 896f46f0d1
enUS Localization Update Announcement / localization-update-announcement (push) Canceled after 0s
Bagshui 1.6.4
2026-10-30 15:55:43 +00:00

762 lines
31 KiB
Lua

-- Bagshui Settings Class Prototype
-- Exposes:
-- * Bagshui.prototypes.Settings
-- * BsSettings - see notes at the end of this file.
--
-- Raises: BAGSHUI_SETTING_UPDATE
--
-- The goal of the Settings class is to have a table that proxies all accesses
-- (get/set) in order to transparently handle setting scoping. This allows simple
-- things like settingsInstance.mySetting = 1, and if mySetting is supposed to be
-- stored at the account level (as defined in Config\Settings.lua), then that will
-- just happen.
--
-- For settingsInstance.mySetting to work, the metatable that runs the show assumes
-- that all keys in the Settings class that don't exist are settings. This means
-- that if there's ever a need to add new class-level keys after instantiation,
-- rawset() must be used.
--
-- In addition, it's recommended that ALL class properties for Settings be prefixed
-- with an underscore to avoid collision with actual settings. Recommended naming
-- convention that will ensure uniqueness:
-- * settingName
-- * _classProperty
-- * ClassFunction
Bagshui:AddComponent(function()
local SETTING_VERSION_KEY = "_settingVersions_"
-- Settings class. See notes at the top of Settings.lua regarding underscore naming.
local Settings = {
-- Private table for storing setting information.
_settingInfo = {},
-- Private table to store references to setting tables (ACCOUNT, CHARACTER, Bags, Bank, etc).
_settingValues = {},
}
Bagshui.prototypes.Settings = Settings
-- Initial setup.
function Settings:Init()
-- The only settings that need to be initialized are those related to Inventory.
self:InitSettingsInfo(Bagshui.config.Settings[BS_SETTING_APPLICABILITY.INVENTORY])
-- Account-wide settings.
if _G.BagshuiData.settings == nil then
_G.BagshuiData.settings = {}
end
self._settingValues[BS_SETTING_SCOPE.ACCOUNT] = _G.BagshuiData.settings
-- Character-wide settings (shared across Inventory classes).
if Bagshui.currentCharacterData.settings == nil then
Bagshui.currentCharacterData.settings = {}
end
self._settingValues[BS_SETTING_SCOPE.CHARACTER] = Bagshui.currentCharacterData.settings
-- Set defaults for account/character settings (3rd parameter EXCLUDES Inventory-level settings)
-- as those will be set during `Settings:New()`.
self:SetDefaults(false, nil, BS_SETTING_SCOPE.INVENTORY)
end
-- Create a new Settings class instance that will magically access and store settings in the appropriate scope-level tables.
---@param inventoryClassInstance table? Instance of the Bagshui Inventory class prototype.
---@param wipeOnLoad boolean? Reset all settings during initialization.
---@return table settings Class instance.
function Settings:New(inventoryClassInstance, wipeOnLoad)
local inventoryType = inventoryClassInstance and inventoryClassInstance.inventoryType
if inventoryType then
-- SavedVariables data for inventory classes is stored under the lowercase
-- version of their inventoryType property.
local inventoryTypeSavedVars = string.lower(inventoryType)
-- Ensure storage is initialized
if Bagshui.currentCharacterData[inventoryTypeSavedVars] == nil then
Bagshui.currentCharacterData[inventoryTypeSavedVars] = {}
end
if Bagshui.currentCharacterData[inventoryTypeSavedVars].settings == nil or wipeOnLoad then
Bagshui.currentCharacterData[inventoryTypeSavedVars].settings = {}
end
-- Store inventory-level tables on the Settings class prototype (self), NOT the instance since docked classes use the settings of the class they're docked to.
-- This is intentionally using the Noun Case version of the inventoryType property for storage on the class object.
self._settingValues[inventoryType] = Bagshui.currentCharacterData[inventoryTypeSavedVars].settings
end
-- Prepare new class object.
-- Using underscore-prefixed keys for reasons outlined in the class introduction.
local classObj = {
-- BS_INVENTORY_TYPE of the Inventory class instance that owns this Settings class instance, if any.
_inventoryType = inventoryType,
-- Inventory class instance that owns this Settings class instance, if any.
_inventoryClassInstance = inventoryClassInstance,
-- BS_INVENTORY_TYPE of the Inventory class instance that where Settings are being stored, based on docked state.
_inventoryTypeForSettingsStorage = inventoryType,
-- Inventory class instance on which settings should be stored, based on docked state..
_inventoryClassSettingsSource = inventoryClassInstance,
-- Cache is used to avoid full lookups when just requesting a setting value.
_settingCache = {},
-- Don't send change events until initialization is complete.
_initialized = false,
}
-- Alter the settings source for docked instances to be the class they're docked to.
if inventoryClassInstance and inventoryClassInstance.dockTo then
classObj._inventoryTypeForSettingsStorage = inventoryClassInstance.dockTo
classObj._inventoryClassSettingsSource = Bagshui.components[inventoryClassInstance.dockTo]
end
-- Metatable that will intercept all accesses to classObj for unknown keys.
-- This is what makes `settingInstance.settingName` work for get/set.
local mt = {
--- Get
---@param _ table The table being accessed.
---@param key any Requested key.
---@return any # Setting value or class property value.
__index = function(_, key)
if not self._settingInfo[key] then
-- If it's not a setting, return the class level function or property.
return self[key]
else
-- Retrieve setting value.
return classObj:Get(key)
end
end,
--- Set
---@param _ table The table being accessed.
---@param settingName string Setting to update.
---@param value any New value.
__newindex = function(_, settingName, value)
--Bagshui:PrintDebug("*update of element " .. tostring(settingName) .. " to " .. tostring(value))
classObj:Set(settingName, value)
end,
}
setmetatable(classObj, mt)
-- Set the defaults for this inventory type.
-- This won't do anything if there isn't an Inventory class instance.
classObj:SetDefaults(false, BS_SETTING_SCOPE.INVENTORY)
-- Initialize the settings cache.
classObj:InitCache()
-- Change events can now be raised.
classObj._initialized = true
-- Receive change events from other Settings class instances.
Bagshui:RegisterEvent("BAGSHUI_SETTING_UPDATE", classObj)
-- Return the new class object.
return classObj
end
--- Recursively process the settings configuration into a flat list.
---
--- The `settingsTable` is an array containing tables that conform to the following formats:
---
--- ## Titles
--- A title table can contain either a `menuTitle` or a `mainTitle` key.
--- ### Standard Title
--- ```
--- {
--- ---@type string Menu text for the title.
--- menuTitle,
--- },
--- ```
--- ### Main Title
--- ```
--- {
--- ---@type boolean Build the top-level Inventory class menu title -- see mainTitle code in Components\Inventory.Menus.Settings.lua.
--- mainTitle = true,
--- },
--- ```
---
--- ## Submenus
--- A submenu is created by adding a table with `submenuName` and `settings` keys.
--- ```
--- {
--- ---@type string Name to display for the submenu.
--- submenuName,
--- ---@type string? Text to use as the tooltip title (will use the `submenuName` if not provided).
--- tooltipTitle,
--- ---@type string? Text to use as the tooltip body.
--- tooltipText,
--- ---@type Setting[] Array of setting tables.
--- settings = {}
--- }
--- ```
---
--- ## Setting
---
--- ### Important note about localization:
--- The preferred way to localize `title`/`tooltipTitle`/`tooltipText` is via localization.
--- * `<settingName>` -> `title`
--- * `<settingName>_TooltipTitle` -> `tooltipTitle`
--- * `<settingName>_TooltipText` -> `tooltipText`
--- The `title`/`tooltipTitle`/`tooltipText` properties should only be used where
--- localization won't cut it. Container hook settings are one example of this.
---
---
--- ### Each setting is defined using a table with the following keys:
--- ```
--- {
--- ---@type string REQUIRED Internal name of the setting. Will also be used for localization as `Setting_<name>`.
--- name,
--- ---@type BS_SETTING_TYPE REQUIRED Setting data type.
--- type,
--- ---@type any Initial setting value.
--- defaultValue,
--- ---@type BS_SETTING_SCOPE? At what level should the setting be stored? Defaults to INVENTORY.
--- scope,
--- ---@type BS_SETTING_PROFILE_SCOPE? When scope is INVENTORY, settings can be further scoped into profiles.
--- profileScope,
--- ---@type boolean Don't expose this setting in the UI.
--- hidden,
--- ---
--- --- * Display Customization *
--- ---@type string? Display name of the setting when the setting does not have a localization.
--- -- This should really be called `text` instead of `title`. Maybe it'll get fixed one day.
--- title,
--- ---@type string? Text to use as the tooltip title when the setting does not have a localization.
--- tooltipTitle,
--- ---@type string? Text to use as the tooltip body when the setting does not have a localization.
--- tooltipText,
--- ---@type boolean? Make the menu entry *look* like a title, but still be clickable.
--- fakeTitle,
--- ---@type boolean? Suppress display of the submenu arrow.
--- hideArrow,
--- ---@type function? Alter the value displayed in the UI without changing anything else.
--- valueDisplayFunc,
--- ---
--- --- * Behaviors *
--- ---@type function(settingName, settingsClassInstance)? -> boolean Return `true` to disable the setting.
--- disableFunc,
--- ---@type function? -> boolean Return `true` to hide the setting.
--- hideFunc,
--- ---@type function(nameString)? -> string Alter the displayed name of the setting during initialization.
--- -- Should be renamed to `textFunc` if this ever gets a consistency pass.
--- nameFunc,
--- ---@type function(text, settingName, settings)? -> string Modify the menu text at display-time.
--- textDisplayFunc,
--- ---@type function(tooltipText, settingName, settings)? -> string Modify the tooltip text at display-time. The `tooltipText` string will contain `~1~` as a placeholder for insertion at the end, but before the reset text.
--- tooltipTextDisplayFunc,
--- ---@type function(settings, settingName, newValue)? -> nil Custom callback to execute after setting change..
--- onChange,
--- ---@type boolean? Trigger the associated inventory class to refresh its item cache.
--- inventoryCacheUpdateOnChange,
--- ---@type boolean? Trigger the associated inventory class to re-sort and re-categorize.
--- inventoryResortOnChange,
--- ---@type boolean? Trigger the associated inventory class to redraw its window.
--- inventoryWindowUpdateOnChange,
--- ---
--- --- > BS_SETTING_TYPE.CHOICES only <
--- ---@type { value: any, text: string }[] Array of choices to display.
--- choices,
--- ---@type BS_AUTO_SPLIT_MENU_TYPE Auto-split menu to display instead of a static list of choices.
--- choicesAutoSplitMenuType,
--- ---@type function See `omitFunc` definition in Components\Menus.lua.
--- choicesAutoSplitMenuOmitFunc,
--- ---@type function See `tooltipTextFunc` definition in Components\Menus.lua.
--- choicesAutoSplitMenuTooltipTextFunc,
--- ---
--- --- > BS_SETTING_TYPE.COLOR only <
--- ---@type boolean Display the opacity slider in the color picker.
--- hasOpacity,
--- ---
--- --- > BS_SETTING_TYPE.NUMBER only <
--- ---@type number Lowest value for the number selection submenu and lowest allowable value.
--- min,
--- ---@type number Highest value for the number selection submenu and highest allowable value.
--- max,
--- ---@type number Increment to use when building the number selection submenu.
--- step,
--- ---@type { value: any, text: string }[] Array of choices to display. Will be used instead of automatically generating a list based on min/max.
--- choices,
--- }
--- ```
---@param settingsTable table<string,any> Settings configuration as defined just above.
function Settings:InitSettingsInfo(settingsTable)
for _, settingInfo in pairs(settingsTable) do
if type(settingInfo.settings) == "table" then
-- This setting has a `settings` table on it, so we need to recurse.
Settings:InitSettingsInfo(settingInfo.settings)
elseif settingInfo.name then
-- This is a normal setting.
if settingInfo.type then
-- Add to _settingInfo table.
settingInfo.scope = BS_SETTING_SCOPE[settingInfo.scope] or BS_SETTING_SCOPE.INVENTORY -- Default scope
settingInfo.settingVersion = type(settingInfo.settingVersion) == "number" and settingInfo.settingVersion or 1
self._settingInfo[settingInfo.name] = settingInfo
-- When a setting is added after startup, make sure it has a default value.
if self._initialized then
self:SetDefaults(false, nil, nil, settingInfo.name)
end
else
Bagshui:PrintWarning("Setting " .. settingInfo.name .. " is missing the 'type' property and cannot be used.")
end
end
end
end
--- Prepare the cache for this settings instance.
--- Cache is referenced by `Settings:Get()`.
function Settings:InitCache()
BsUtil.TableClear(self._settingCache)
for settingName, _ in pairs(self._settingInfo) do
local value = self:Get(settingName, false, true, true) -- final trues are noCache/noDefault so the actual value is obtained.
self._settingCache[settingName] = value
end
end
--- Event handling.
---@param event string Event identifier.
---@param arg1 any? Argument 1 from the event.
---@param arg2 any? Argument 2 from the event.
---@param arg3 any? Argument 3 from the event.
function Settings:OnEvent(event, arg1, arg2, arg3, arg4)
-- Update cache for non-Inventory level setting changes that come from other
-- Settings class instances. Without this, settings will get out of sync as
-- they're changed and won't be synced back up until a reload. (For example,
-- an account-level setting is changed from the Bags settings menu, but the
-- Bank's Settings class instance would still have the old value in its cache.)
-- arg1 is settingName.
-- arg2 is settingInfo.
-- arg3 is newValue.
-- arg4 is the Settings class instance that raised the event.
if
self._settingCache
and event == "BAGSHUI_SETTING_UPDATE"
and arg4 ~= self
and (
arg2.scope ~= BS_SETTING_SCOPE.INVENTORY
or (
-- Keep profile-scoped settings in sync when the change was made to the profile we're using.
self._inventoryClassInstance
and arg2.profileScope
and self["profile" .. BS_PROFILE_TYPE[arg2.profileScope]]
== arg4["profile" .. BS_PROFILE_TYPE[arg2.profileScope]]
)
)
then
self._settingCache[arg1] = arg3
end
end
--- Retrieve the info table for a setting.
---@param settingName any
---@return table settingInfo
function Settings:GetSettingInfo(settingName)
assert(settingName ~= nil, "settingName is required")
local settingInfo = self._settingInfo[settingName]
assert(settingInfo, "Invalid setting " .. settingName)
return settingInfo
end
--- Retrieve the storage table for a given setting.
---@param setting string|table Setting name or settingInfo table.
---@return table|nil
function Settings:GetTableForSetting(setting)
local settingInfo = type(setting) == "table" and setting or self:GetSettingInfo(setting)
-- Non-Inventory-scoped settings.
-- We have to use the class prototype here because _settingValues isn't stored at the instance level.
local settingTable = Settings._settingValues[settingInfo.scope]
-- Inventory-scoped settings need special handling.
if settingInfo.scope == BS_SETTING_SCOPE.INVENTORY then
-- Safety check - can't return an inventory-scoped setting if we don't have an inventory instance.
if self._inventoryClassInstance then
-- profileScope should be populated in Settings config using the BS_SETTING_PROFILE_SCOPE enum.
if settingInfo.profileScope then
-- Get the profile-level setting table. Using _inventoryClassSettingsSource,
-- which is the Inventory class instance on which settings should be stored,
-- regardless of docked state.
settingTable = self._inventoryClassSettingsSource
and self._inventoryClassSettingsSource.profiles
and self._inventoryClassSettingsSource.profiles[BS_PROFILE_TYPE[settingInfo.profileScope]]
or nil
else
-- Non-profile settings.
settingTable = Settings._settingValues[self._inventoryTypeForSettingsStorage]
end
else
-- Safety check failed.
settingTable = nil
end
end
return settingTable
end
-- Retrieve the value of a setting.
---@param settingName string
---@param includeVersion boolean? The second return value will be the setting version. Only works when `noCache` is true.
---@param noCache boolean? Do not pull from the cache.
---@param noDefault boolean? When the setting value is nil, just return nil instead of the default value.
---@return any value
---@return integer? settingVersion
function Settings:Get(settingName, includeVersion, noCache, noDefault)
local value
-- Cached value.
if not (includeVersion or noCache) then
value = self._settingCache[settingName]
if value ~= nil then
return value
end
end
-- Non-cached value.
local settingTable = self:GetTableForSetting(settingName)
if settingTable then
value = settingTable[settingName]
end
-- Grab default value if we haven't found a saved value.
if value == nil and not noDefault then
-- Using self._settingInfo instead of self:GetSettingInfo() because we
-- already know it's a valid setting name from calling GetTableForSetting().
value = self._settingInfo[settingName].defaultValue
end
if includeVersion then
return value,
((settingTable and settingTable[SETTING_VERSION_KEY] and settingTable[SETTING_VERSION_KEY][settingName]) or 0)
end
return value
end
--- Update the value for a setting.
---@param settingName string
---@param value any New value.
---@param skipValidation boolean? Use `value` as given without potentially changing it via validation.
---@param force boolean? Save the setting even if the value hasn't changed.
function Settings:Set(settingName, value, skipValidation, force)
local settingInfo = self:GetSettingInfo(settingName)
local settingTable = self:GetTableForSetting(settingInfo)
local previousValue = settingTable and settingTable[settingName]
local finalValue = value
-- Validate.
if not skipValidation then
finalValue = self:Validate(settingInfo, value)
end
-- Make sure there's a change.
if finalValue == previousValue and not force then
return
end
if
-- Only save if a save location is available.
settingTable
-- Trigger settings don't get changes saved since their only purpose is to raise the change event.
and settingInfo.type ~= BS_SETTING_TYPE.TRIGGER
then
if not settingTable[SETTING_VERSION_KEY] then
settingTable[SETTING_VERSION_KEY] = {}
end
-- Save setting and version
settingTable[settingName] = finalValue
settingTable[SETTING_VERSION_KEY][settingName] = settingInfo.settingVersion
-- Update cache.
if self._settingCache then
self._settingCache[settingName] = finalValue
end
end
-- Trigger change callback if provided.
if settingInfo.onChange and self._initialized then
settingInfo.onChange(self, settingName, finalValue)
end
-- Send change notification.
if self._initialized then
Bagshui:RaiseEvent("BAGSHUI_SETTING_UPDATE", nil, settingName, settingInfo, finalValue, self)
end
end
--- Ensure all settings are initialized and reset to default if requested.
---@param forceReset boolean? By default, settings are only reset if they are nil or have invalid values. When `forceReset` is true, all settings matching the scope filters are reset.
---@param scope BS_SETTING_SCOPE? Only reset settings matching this scope.
---@param omitScope BS_SETTING_SCOPE? Only reset settings NOT matching this scope.
---@param limitToSetting string? Reset only the specified setting (scope-based filtering still applies).
---@param silent boolean? Don't print a message when settings are changed.
function Settings:SetDefaults(forceReset, scope, omitScope, limitToSetting, silent)
--Bagshui:PrintDebug(string.format("SetDefaults() forceReset=%s scope=%s omitScope=%s", tostring(forceReset), tostring(scope), tostring(omitScope)))
local qualifiedSettingName, currentVersion, newVersion, currentValue, newValue, changeReason
-- Loop through all settings since we don't store indexes of setting scopes.
for settingName, settingInfo in pairs(self._settingInfo) do
-- Only process requested scopes.
if
(
-- Safety check - Can't do inventory-level scopes if we don't have
-- an inventory type and setting table for the given setting.
settingInfo.scope ~= BS_SETTING_SCOPE.INVENTORY
or (self._inventoryClassInstance ~= nil and self:GetTableForSetting(settingInfo))
)
and (not scope or (scope ~= nil and settingInfo.scope == scope))
and (not omitScope or (omitScope ~= nil and settingInfo.scope ~= omitScope))
-- Skip settings that don't have values.
and settingInfo.type ~= BS_SETTING_TYPE.TRIGGER
and settingInfo.type ~= BS_SETTING_TYPE.PLACEHOLDER
then
if not limitToSetting or limitToSetting == settingName then
-- Initialize variables
changeReason = nil
currentValue, currentVersion = self:Get(settingName, true, true, true)
newValue = nil
newVersion = settingInfo.settingVersion
-- No need to validate current value if resetting.
if not forceReset then
if currentValue ~= nil then
newValue = self:Validate(settingInfo, currentValue)
if type(currentValue) == "table" then
for key, _ in pairs(currentValue) do
if currentValue[key] ~= newValue[key] then
changeReason = L.SettingReset_InvalidValue
end
end
end
end
end
-- Reasons to reset.
if currentValue == nil then
-- Not yet initialized.
-- Won't be printed to chat due to currentValue ~= nil check,
-- but changeReason needs to have a value to trigger initialization.
changeReason = "init"
elseif currentVersion < newVersion then
-- Outdated version.
changeReason = L.SettingReset_Outdated
--Bagshui:PrintDebug("old version: "..tostring(currentVersion)..", settingVersion: "..tostring(newVersion))
end
-- Need to reset.
if changeReason or forceReset then
-- Grab default value if it wasn't corrected by validation above.
if newValue == nil then
newValue = (
-- Setting override per-inventoryType
(
self._inventoryType
and Bagshui.config.InventorySettingOverrides[self._inventoryType]
and Bagshui.config.InventorySettingOverrides[self._inventoryType][settingName]
) or settingInfo.defaultValue
)
end
-- Set value, skipping validation since we know it's good.
if currentValue ~= newValue then
self:Set(settingName, newValue, true, true)
-- Logging.
if not silent and currentValue ~= nil then
qualifiedSettingName = tostring(
(settingInfo.scope ~= BS_SETTING_SCOPE.INVENTORY) and settingInfo.scope or self._inventoryType
) .. "." .. settingName
-- "<Setting> reset [<reason>] '<old value>' => '<new value>'"
Bagshui:PrintInfo(
string.format(
"%s%s '%s' => '%s'",
string.format(L.SettingReset_LogStart, qualifiedSettingName),
changeReason and string.format(" [%s]", changeReason) or "",
tostring(currentValue),
tostring(newValue)
)
)
end
end
end
--else
--Bagshui:PrintDebug("skipping " .. settingName .. " because scope is " .. settingInfo.scope .. " and scope = " .. tostring(scope) .. " or omitScope = " .. tostring(omitScope))
end
end
end
end
--- Ensure the given setting value is valid and provide the value it should be.
---@param settingInfo any Configuration for the setting being validated.
---@param value any Value to validate.
---@return any validatedValue Current or default value, depending on whether it needed to be corrected.
function Settings:Validate(settingInfo, value)
local validatedValue = value
-- Call validation function if available for the setting's data type.
if settingInfo.type then
local validationFunctionName = "Validate" .. settingInfo.type
if self[validationFunctionName] then
validatedValue = self[validationFunctionName](self, value, settingInfo)
end
end
return validatedValue
end
--- Ensure `value` is within the allowed `min` - `max` range.
---@param value any Number to validate.
---@param min number Lowest allowed value.
---@param max number Highest allowed value.
---@param default number Fallback if `value` is invalid.
---@return number validatedValue
function Settings:ValidateNumericRange(value, min, max, default)
if value == nil then
return default
end
local validatedValue = value
if type(validatedValue) ~= "number" then
validatedValue = tonumber(validatedValue)
end
if min then
validatedValue = math.max(validatedValue, min)
end
if max then
validatedValue = math.min(validatedValue, max)
end
return validatedValue
end
--- Validation: BS_SETTING_TYPE.NUMBER
--- Ensure `value` is within the allowed `min` - `max` range.
---@param value any Setting value to validate.
---@param settingInfo any Configuration for the setting being validated.
---@return number validatedValue
function Settings:ValidateNumber(value, settingInfo)
return self:ValidateNumericRange(value, settingInfo.min, settingInfo.max, settingInfo.defaultValue)
end
--- Validation: BS_SETTING_TYPE.BOOL
--- Understands 1 -> true, "true" -> true.
---@param value any Setting value to validate.
---@param settingInfo any Configuration for the setting being validated.
---@return boolean validatedValue
function Settings:ValidateBool(value, settingInfo)
local validatedValue = value
if type(validatedValue) == "number" then
validatedValue = (validatedValue == 1)
end
if type(validatedValue) == "string" then
validatedValue = (validatedValue == string.lower("true"))
end
if type(validatedValue) ~= "boolean" then
validatedValue = settingInfo.defaultValue
end
return value
end
--- Validation: BS_SETTING_TYPE.COLOR
--- ` { r, g, b, [a] }` where all values are between 0 and 1.
---@param value any Setting value to validate.
---@param settingInfo any Configuration for the setting being validated.
---@return number[] validatedValue
function Settings:ValidateColorRgba(value, settingInfo)
local validatedValue = value
if
type(validatedValue) ~= "table"
or (type(validatedValue) == "table" and table.getn(validatedValue) < table.getn(settingInfo.defaultValue))
then
return BsUtil.TableCopy(settingInfo.defaultValue)
end
for i = 1, table.getn(settingInfo.defaultValue) do
validatedValue[i] = self:ValidateNumericRange(validatedValue[i], 0, 1, settingInfo.defaultValue[i])
end
return validatedValue
end
--- Validation: BS_SETTING_TYPE.CHOICES
---@param value any Setting value to validate.
---@param settingInfo any Configuration for the setting being validated.
---@return any validatedValue
function Settings:ValidateChoices(value, settingInfo)
if not settingInfo.choices then
return nil
end
local validatedValue = nil
if value ~= nil then
local menuTableFound = false
-- There are two possible formats for valid choices.
-- One is the menu format of `{ { value = "Val2", text = "Text1" }, { value = "Val2", text = "Text2" } }`.
for _, choice in ipairs(settingInfo.choices) do
if choice.value then
menuTableFound = true
end
if
(not settingInfo.caseSensitive and string.lower(value) == string.lower(choice.value or ""))
or (settingInfo.caseSensitive and value == choice.value)
then
validatedValue = choice.value
end
end
-- The other way to specify valid choices is a list where the keys are valid values.
-- (Sort Orders uses this)
if not menuTableFound then
for choice, _ in pairs(settingInfo.choices) do
if
(not settingInfo.caseSensitive and string.lower(value) == string.lower(choice))
or (settingInfo.caseSensitive and value == choice)
then
validatedValue = choice
end
end
end
end
-- Couldn't find a valid value, so fall back to default.
if validatedValue == nil then
validatedValue = settingInfo.defaultValue
end
return validatedValue
end
--- Validation: BS_SETTING_TYPE.TRIGGER
---@param value any Setting value to validate.
---@param settingInfo any Configuration for the setting being validated.
---@return true|nil validatedValue
function Settings:ValidateTrigger(value, settingInfo)
-- The only valid values for a Trigger setting are nil and true.
return (value == true) or nil
end
-- Initialize class.
Settings:Init()
-- This is a Settings class instance available in the Bagshui
-- environment that can only access ACCOUNT and CHARACTER-scoped settings.
-- Inventory class instances must use their self.settings Settings class
-- instances; BsSettings is only for use outside the Inventory classes.
-- See the Profiles class for an example of it in action.
Bagshui.environment.BsSettings = Settings:New()
end)