diff --git a/files/builtin_scripts/openmw_aux/settings.lua b/files/builtin_scripts/openmw_aux/settings.lua new file mode 100644 index 0000000000..8ce9929eaa --- /dev/null +++ b/files/builtin_scripts/openmw_aux/settings.lua @@ -0,0 +1,4 @@ +local common = require('scripts.omw.settings.common') +return { + group = common.group, +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 6f6cebb2cf..bc01bc20b5 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -1,23 +1,20 @@ -local prequire = function(path) - local status, result = pcall(function() - return require(path) - end) - return status and result or nil -end - +local storage = require('openmw.storage') local core = require('openmw.core') local types = require('openmw.types') -local storage = require('openmw.storage') -local self = prequire('openmw.self') -local world = prequire('openmw.world') - -local isPlayerScript = self and true or false -local isGlobalScript = world and true or false +local selfObject +do + local success, result = pcall(function() return require('openmw.self') end) + selfObject = success and result or nil +end +local playerObject = selfObject and selfObject.type == types.Player and selfObject or nil +local eventPrefix = 'omwSettings' local EVENTS = { - SettingChanged = 'omwSettingsChanged', - SettingSet = 'omwSettingsGlobalSet', - GroupRegistered = 'omwSettingsGroupRegistered', + SettingChanged = eventPrefix .. 'Changed', + SetValue = eventPrefix .. 'GlobalSetValue', + GroupRegistered = eventPrefix .. 'GroupRegistered', + RegisterGroup = eventPrefix .. 'RegisterGroup', + Subscribe = eventPrefix .. 'Subscribe', } local SCOPE = { @@ -27,124 +24,160 @@ local SCOPE = { SavePlayer = 'SavePlayer', } -local groups = storage.globalSection('OMW_Settings_Groups') -local saveGlobalSection = storage.globalSection('OMW_Settings_SaveGlobal') - -if isGlobalScript then - groups:removeOnExit() - saveGlobalSection:removeOnExit() +local function isPlayerScope(scope) + return scope == SCOPE.Player or scope == SCOPE.SavePlayer end -local savePlayerSection = nil -if isPlayerScript then - savePlayerSection = storage.playerSection('OMW_Setting_SavePlayer') - savePlayerSection:removeOnExit() +local function isSaveScope(scope) + return scope == SCOPE.SaveGlobal or scope == SCOPE.SavePlayer end -local scopes = { - [SCOPE.Global] = storage.globalSection('OMW_Setting_Global'), - [SCOPE.Player] = isPlayerScript and storage.playerSection('OMW_Setting_Player'), - [SCOPE.SaveGlobal] = saveGlobalSection, - [SCOPE.SavePlayer] = savePlayerSection, -} +local prefix = 'omw_settings_' +local settingsPattern = prefix .. 'settings_%s%s' -local function isGlobalScope(scope) - return scope == SCOPE.Global or scope == SCOPE.SaveGlobal +local groupsSection = storage.globalSection(prefix .. 'groups') +if groupsSection.removeOnExit then + groupsSection:removeOnExit() end -local function getSetting(groupKey, settingKey) - local group = groups:get(groupKey) - if not group then - error('Unknown group') - end - local setting = group.settings[settingKey] - if not setting then - error('Unknown setting') - end - return setting -end - -local function getSettingValue(groupKey, settingKey) - local setting = getSetting(groupKey, settingKey) - local scopeSection = scopes[setting.scope] - if not scopeSection then - error(('Setting %s is not available in this context'):format(setting.key)) - end - if not scopeSection:get(groupKey) then - scopeSection:set(groupKey, {}) - end - return scopeSection:get(groupKey)[setting.key] or setting.default -end - -local function notifySettingChange(scope, event) - if isGlobalScope(scope) then - core.sendGlobalEvent(EVENTS.SettingChanged, event) - for _, a in ipairs(world.activeActors) do - if a.type == types.Player then - a:sendEvent(EVENTS.SettingChanged, event) - end - end +local function values(groupKey, scope) + local player = isPlayerScope(scope) + local save = isSaveScope(scope) + local sectionKey = settingsPattern:format(groupKey, save and '_save' or '') + local section + if player then + section = storage.playerSection and storage.playerSection(sectionKey) or nil else - self:sendEvent(EVENTS.SettingChanged, event) + section = storage.globalSection(sectionKey) + end + if save and section and section.removeOnExit then + section:removeOnExit() + end + return section +end + +local function saveScope(scope) + local saved = {} + for _, group in pairs(groupsSection:asTable()) do + saved[group.key] = values(group.key, scope):asTable() + end + return saved +end + +local function loadScope(scope, saved) + if not saved then return end + for _, group in pairs(saved) do + values(group.key, scope):reset(saved[group.key]) end end -local function setSettingValue(groupKey, settingKey, value) - local setting = getSetting(groupKey, settingKey) +local function groupSubscribeEvent(groupKey) + return ('%sSubscribe%s'):format(eventPrefix, groupKey) +end + +local subscriptions = {} +local function handleSubscription(event) + if not subscriptions[event.groupKey] then + subscriptions[event.groupKey] = {} + end + table.insert(subscriptions[event.groupKey], event.object or false) +end + +local function subscribe(self) + local groupKey = rawget(self, 'groupKey') local event = { - groupName = groupKey, - settingName = setting.key, - value = value, + groupKey = groupKey, + object = selfObject, } - if isPlayerScript and isGlobalScope(setting.scope) then - core.sendGlobalEvent(EVENTS.SettingSet, event) - return + core.sendGlobalEvent(EVENTS.Subscribe, event) + if playerObject then + playerObject:sendEvent(EVENTS.Subscribe, event) end - - local scopeSection = scopes[setting.scope] - if not scopeSection:get(groupKey) then - scopeSection:set(groupKey, {}) - end - local copy = scopeSection:getCopy(groupKey) - copy[setting.key] = value - scopeSection:set(groupKey, copy) - - notifySettingChange(setting.scope, event) + return groupSubscribeEvent(groupKey) end local groupMeta = { - __index = { - get = function(self, settingKey) - return getSettingValue(self.key, settingKey) - end, - set = function(self, settingKey, value) - setSettingValue(self.key, settingKey, value) - end, - onChange = function(self, callback) - table.insert(self.__callbacks, callback) - end, - __changed = function(self, settingKey, value) - for _, callback in ipairs(self.__callbacks) do - callback(settingKey, value) + __newindex = function(self, settingKey, value) + local group = groupsSection:get(rawget(self, 'groupKey')) + local setting = group.settings[settingKey] + if not setting then + error(('Setting %s does not exist'):format(settingKey)) + end + local section = values(group.key, setting.scope) + local event = { + groupKey = group.key, + settingKey = settingKey, + value = value, + } + if section.set then + section:set(settingKey, value) + if playerObject then + playerObject:sendEvent(EVENTS.SettingChanged, event) + else + core.sendGlobalEvent(EVENTS.SettingChanged, event) end - end, - }, + if subscriptions[group.key] then + local eventKey = groupSubscribeEvent(group.key) + for _, object in ipairs(subscriptions[group.key]) do + if object then + object:sendEvent(eventKey, event) + else + core.sendGlobalEvent(eventKey, event) + end + end + end + else + if isPlayerScope(setting.scope) then + error(("Can't change player scope setting %s from global scope"):format(settingKey)) + else + core.sendGlobalEvent(EVENTS.SetValue, event) + end + end + end, + __index = function(self, key) + if key == "subscribe" then return subscribe end + local settingKey = key + local group = groupsSection:get(rawget(self, 'groupKey')) + local setting = group.settings[settingKey] + if not setting then + error(('Unknown setting %s'):format(settingKey)) + end + local section = rawget(self, 'sections')[setting.scope] + if not section then + error(("Can't access setting %s from scope %s"):format(settingKey, setting.scope)) + end + return section:get(setting.key) or setting.default + end, } -local cachedGroups = {} -local function getGroup(groupKey) - if not cachedGroups[groupKey] then - cachedGroups[groupKey] = setmetatable({ - key = groupKey, - __callbacks = {}, - }, groupMeta) + +local function group(groupKey) + if not groupsSection:get(groupKey) then + print(("Settings group %s wasn't registered yet"):format(groupKey)) end - return cachedGroups[groupKey] + local s = {} + for _, scope in pairs(SCOPE) do + local section = values(groupKey, scope) + if section then + s[scope] = section + end + end + return setmetatable({ + groupKey = groupKey, + sections = s, + }, groupMeta) end return { - EVENTS = EVENTS, SCOPE = SCOPE, - scopes = scopes, - groups = groups, - getGroup = getGroup, + EVENTS = EVENTS, + isPlayerScope = isPlayerScope, + isSaveScope = isSaveScope, + values = values, + groups = function() + return groupsSection + end, + saveScope = saveScope, + loadScope = loadScope, + group = group, + handleSubscription = handleSubscription, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/global.lua b/files/builtin_scripts/scripts/omw/settings/global.lua index 148d21847a..ec14a07f73 100644 --- a/files/builtin_scripts/scripts/omw/settings/global.lua +++ b/files/builtin_scripts/scripts/omw/settings/global.lua @@ -1,30 +1,44 @@ local common = require('scripts.omw.settings.common') local register = require('scripts.omw.settings.register') +local world = require('openmw.world') +local types = require('openmw.types') -local saveScope = common.scopes[common.SCOPE.SaveGlobal] return { interfaceName = 'Settings', interface = { SCOPE = common.SCOPE, - getGroup = common.getGroup, + group = common.group, registerGroup = register.registerGroup, }, engineHandlers = { onLoad = function(saved) - common.groups:reset() - saveScope:reset(saved) + common.groups():reset() + common.loadScope(common.SCOPE.SaveGlobal, saved) end, onSave = function() - return saveScope:asTable() + common.saveScope(common.SCOPE.SaveGlobal) end, onPlayerAdded = register.onPlayerAdded, }, eventHandlers = { - [common.EVENTS.SettingChanged] = function(e) - common.getGroup(e.groupName):__changed(e.settingName, e.value) + [common.EVENTS.SetValue] = function(event) + local group = common.group(event.groupKey) + group[event.settingKey] = event.value end, - [common.EVENTS.SettingSet] = function(e) - common.getGroup(e.groupName):set(e.settingName, e.value) + [common.EVENTS.RegisterGroup] = function(options) + if common.groups():get(options.key) then return end + register.registerGroup(options) end, - } + [common.EVENTS.SettingChanged] = function(event) + local setting = common.groups():get(event.groupKey).settings[event.settingKey] + if common.isPlayerScope(setting.scope) then + for _, a in ipairs(world.activeActors) do + if a.type == types.Player and a:isValid() then + a:sendEvent(common.EVENTS.SettingChanged, event) + end + end + end + end, + [common.EVENTS.Subscribe] = common.handleSubscription, + }, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/interface.lua b/files/builtin_scripts/scripts/omw/settings/interface.lua deleted file mode 100644 index 4fb60d2c4d..0000000000 --- a/files/builtin_scripts/scripts/omw/settings/interface.lua +++ /dev/null @@ -1,51 +0,0 @@ -return function(player) - local core = require('openmw.core') - local types = require('openmw.types') - local world = not player and require('openmw.world') - - - local sections = require('scripts.omw.settings.sections') - local render = player and require('scripts.omw.settings.render') or nil - - local settingChangeEvent = 'omwSettingChanged' - local globalSetEvent = 'omwSettingGlobalSet' - local registerEvent = 'omwSettingGroupRegistered' - - local groups, scopes, SCOPE, isGlobal = sections.groups, sections.scopes, sections.SCOPE, sections.isGlobal - - - - local saveScope = scopes[player and SCOPE.SavePlayer or SCOPE.SaveGlobal] - return { - interfaceName = 'Settings', - interface = { - getGroup = getGroup, - SCOPE = SCOPE, - registerGroup = not player and require('scripts.omw.settings.register') or nil, - registerType = player and render.registerType or nil, - }, - engineHandlers = { - onLoad = function(saved) - if not player then groups:reset() end - saveScope:reset(saved) - end, - onSave = function() - return saveScope:asTable() - end, - }, - eventHandlers = { - [settingChangeEvent] = function(e) - getGroup(e.groupName):__changed(e.settingName, e.value) - end, - [globalSetEvent] = not player and function(e) - local setting = getSetting(e.groupName, e.settingName) - if isGlobal(setting.scope) then - setSettingValue(e.groupName, e.settingName, e.value) - else - error(('Unexpected Setting event for a non-global setting %s'):format(e.settingName)) - end - end or nil, - [registerEvent] = player and render.onGroupRegistered or nil, - } - } -end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index b4cf75b127..39d989d5cc 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -1,8 +1,10 @@ local common = require('scripts.omw.settings.common') local render = require('scripts.omw.settings.render') + local ui = require('openmw.ui') local async = require('openmw.async') local util = require('openmw.util') +local core = require('openmw.core') render.registerRenderer('text', function(value, set, arg) return { @@ -20,26 +22,27 @@ render.registerRenderer('text', function(value, set, arg) } end) -local saveScope = common.scopes[common.SCOPE.SavePlayer] return { interfaceName = 'Settings', interface = { SCOPE = common.SCOPE, - getGroup = common.getGroup, + group = common.group, registerRenderer = render.registerRenderer, + registerGroup = function(options) + core.sendGlobalEvent(common.EVENTS.RegisterGroup, options) + end, }, engineHandlers = { onLoad = function(saved) - saveScope:reset(saved) + common.loadScope(common.SCOPE.SavePlayer, saved) end, onSave = function() - return saveScope:asTable() + common.saveScope(common.SCOPE.SavePlayer) end, }, eventHandlers = { - [common.EVENTS.SettingChanged] = function(e) - common.getGroup(e.groupName):__changed(e.settingName, e.value) - end, [common.EVENTS.GroupRegistered] = render.onGroupRegistered, + [common.EVENTS.SettingChanged] = render.onSettingChanged, + [common.EVENTS.Subscribe] = common.handleSubscription, } } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua index ca41843e00..02a85cdeca 100644 --- a/files/builtin_scripts/scripts/omw/settings/register.lua +++ b/files/builtin_scripts/scripts/omw/settings/register.lua @@ -3,11 +3,9 @@ local types = require('openmw.types') local common = require('scripts.omw.settings.common') -local groups, SCOPE = common.groups, common.SCOPE - local function validScope(scope) local valid = false - for _, v in pairs(SCOPE) do + for _, v in pairs(common.SCOPE) do if v == scope then valid = true break @@ -41,7 +39,7 @@ local function addSetting(settings, options) end settings[options.key] = { key = options.key, - scope = options.scope or SCOPE.Global, + scope = options.scope, default = options.default, renderer = options.renderer, argument = options.argument, @@ -70,14 +68,14 @@ local function validateGroupOptions(options) end local function registerGroup(options) + local groups = common.groups() validateGroupOptions(options) if groups:get(options.key) then - print(('Overwriting group %s'):format(options.key)) - end + error(('Duplicate group %s'):format(options.key)) + end local group = { key = options.key, localization = options.localization, - name = options.name, description = options.description, @@ -95,7 +93,7 @@ local function registerGroup(options) end local function onPlayerAdded(player) - for groupName in pairs(groups:asTable()) do + for groupName in pairs(common.groups():asTable()) do player:sendEvent(common.EVENTS.GroupRegistered, groupName) end end diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 57368a6a15..bbbb8bfc6d 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -40,10 +40,10 @@ local function renderSetting(groupKey, setting, value) if not renderFunction then error(('Setting %s of %s has unknown renderer %s'):format(setting.key, groupKey, setting.renderer)) end - local group = common.getGroup(groupKey) - value = value or group:get(setting.key) + local group = common.group(groupKey) + value = value or group[setting.key] local set = function(value) - group:set(setting.key, value) + group[setting.key] = value renderSetting(groupKey, setting, value) end local element = groupOptions[groupKey].element @@ -92,7 +92,7 @@ local function renderSetting(groupKey, setting, value) end local function renderGroup(groupKey) - local group = common.groups:get(groupKey) + local group = common.groups():get(groupKey) local element = groupOptions[groupKey].element local localization = groupOptions[groupKey].localization element.layout = { @@ -143,19 +143,27 @@ local function renderGroup(groupKey) end local function onGroupRegistered(groupKey) - local group = common.groups:get(groupKey) + local group = common.groups():get(groupKey) + local loc = core.l10n(group.localization) local options = { - name = groupKey, + name = loc(group.name), element = ui.create{}, searchHints = '', - localization = core.l10n(group.localization), + localization = loc, } groupOptions[groupKey] = options renderGroup(groupKey) ui.registerSettingsPage(options) end +local function onSettingChanged(event) + local group = common.groups():get(event.groupKey) + local setting = group.settings[event.settingKey] + renderSetting(event.groupKey, setting, event.value) +end + return { onGroupRegistered = onGroupRegistered, + onSettingChanged = onSettingChanged, registerRenderer = registerRenderer, } \ No newline at end of file