1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-21 06:09:42 +00:00

Initial Lua Settings API prototype

This commit is contained in:
uramer 2022-03-07 21:28:05 +01:00
parent 49487a17e6
commit 096255534a
7 changed files with 396 additions and 0 deletions

View file

@ -4,3 +4,5 @@ PLAYER: scripts/omw/console/player.lua
GLOBAL: scripts/omw/console/global.lua
CUSTOM: scripts/omw/console/local.lua
PLAYER: scripts/omw/mwui/init.lua
GLOBAL: scripts/omw/settings/global.lua
PLAYER: scripts/omw/settings/player.lua

View file

@ -0,0 +1,150 @@
local prequire = function(path)
local status, result = pcall(function()
return require(path)
end)
return status and result or nil
end
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 EVENTS = {
SettingChanged = 'omwSettingsChanged',
SettingSet = 'omwSettingsGlobalSet',
GroupRegistered = 'omwSettingsGroupRegistered',
}
local SCOPE = {
Global = 'Global',
Player = 'Player',
SaveGlobal = 'SaveGlobal',
SavePlayer = 'SavePlayer',
}
local groups = storage.globalSection('OMW_Settings_Groups')
local saveGlobalSection = storage.globalSection('OMW_Settings_SaveGlobal')
if isGlobalScript then
groups:removeOnExit()
saveGlobalSection:removeOnExit()
end
local savePlayerSection = nil
if isPlayerScript then
savePlayerSection = storage.playerSection('OMW_Setting_SavePlayer')
savePlayerSection:removeOnExit()
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 function isGlobalScope(scope)
return scope == SCOPE.Global or scope == SCOPE.SaveGlobal
end
local function getSetting(groupName, settingName)
local group = groups:get(groupName)
if not group then
error('Unknown group')
end
local setting = group[settingName]
if not setting then
error('Unknown setting')
end
return setting
end
local function getSettingValue(groupName, settingName)
local setting = getSetting(groupName, settingName)
local scopeSection = scopes[setting.scope]
if not scopeSection then
error(('Setting %s is not available in this context'):format(setting.name))
end
if not scopeSection:get(groupName) then
scopeSection:set(groupName, {})
end
return scopeSection:get(groupName)[setting.name] 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
else
self:sendEvent(EVENTS.SettingChanged, event)
end
end
local function setSettingValue(groupName, settingName, value)
local setting = getSetting(groupName, settingName)
local event = {
groupName = groupName,
settingName = setting.name,
value = value,
}
if isPlayerScript and isGlobalScope(setting.scope) then
core.sendGlobalEvent(EVENTS.SettingSet, event)
return
end
local scopeSection = scopes[setting.scope]
if not scopeSection:get(groupName) then
scopeSection:set(groupName, {})
end
local copy = scopeSection:getCopy(groupName)
copy[setting.name] = value
scopeSection:set(groupName, copy)
notifySettingChange(setting.scope, event)
end
local groupMeta = {
__index = {
get = function(self, settingName)
return getSettingValue(self.name, settingName)
end,
set = function(self, settingName, value)
setSettingValue(self.name, settingName, value)
end,
onChange = function(self, callback)
table.insert(self.__callbacks, callback)
end,
__changed = function(self, settingName, value)
for _, callback in ipairs(self.__callbacks) do
callback(settingName, value)
end
end,
},
}
local cachedGroups = {}
local function getGroup(groupName)
if not cachedGroups[groupName] then
cachedGroups[groupName] = setmetatable({
name = groupName,
__callbacks = {},
}, groupMeta)
end
return cachedGroups[groupName]
end
return {
EVENTS = EVENTS,
SCOPE = SCOPE,
scopes = scopes,
groups = groups,
getGroup = getGroup,
}

View file

@ -0,0 +1,30 @@
local common = require('scripts.omw.settings.common')
local register = require('scripts.omw.settings.register')
local saveScope = common.scopes[common.SCOPE.SaveGlobal]
return {
interfaceName = 'Settings',
interface = {
SCOPE = common.SCOPE,
getGroup = common.getGroup,
registerGroup = register.registerGroup,
},
engineHandlers = {
onLoad = function(saved)
common.groups:reset()
saveScope:reset(saved)
end,
onSave = function()
return saveScope:asTable()
end,
onPlayerAdded = register.onPlayerAdded,
},
eventHandlers = {
[common.EVENTS.SettingChanged] = function(e)
common.getGroup(e.groupName):__changed(e.settingName, e.value)
end,
[common.EVENTS.SettingSet] = function(e)
common.getGroup(e.groupName):set(e.settingName, e.value)
end,
}
}

View file

@ -0,0 +1,51 @@
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

View file

@ -0,0 +1,26 @@
local common = require('scripts.omw.settings.common')
local render = require('scripts.omw.settings.render')
local saveScope = common.scopes[common.SCOPE.SavePlayer]
return {
interfaceName = 'Settings',
interface = {
SCOPE = common.SCOPE,
getGroup = common.getGroup,
registerRenderer = render.registerRenderer,
},
engineHandlers = {
onLoad = function(saved)
saveScope:reset(saved)
end,
onSave = function()
return saveScope:asTable()
end,
},
eventHandlers = {
[common.EVENTS.SettingChanged] = function(e)
common.getGroup(e.groupName):__changed(e.settingName, e.value)
end,
[common.EVENTS.GroupRegistered] = render.onGroupRegistered,
}
}

View file

@ -0,0 +1,76 @@
local world = require('openmw.world')
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
if v == scope then
valid = true
break
end
end
return valid
end
local function validateSettingOptions(options)
if type(options.name) ~= 'string' then
error('Setting must have a name')
end
if options.default == nil then
error('Setting must have a default value')
end
if type(options.description) ~= 'string' then
error('Setting must have a description')
end
if not validScope(options.scope) then
error(('Invalid setting scope %s'):format(options.scope))
end
if type(options.renderer) ~= 'string' then
error('Setting must have a renderer')
end
end
local function addSetting(group, options)
validateSettingOptions(options)
if group[options.name] then
error(('Duplicate setting name %s'):format(options.name))
end
group[options.name] = {
name = options.name,
scope = options.scope or SCOPE.Global,
default = options.default,
description = options.description,
renderer = options.renderer,
}
end
local function registerGroup(groupName, list)
if groups:get(groupName) then
print(('Overwriting group %s'):format(groupName))
end
local settings = {}
for _, opt in ipairs(list) do
addSetting(settings, opt)
end
groups:set(groupName, settings)
for _, a in ipairs(world.activeActors) do
if a.type == types.Player and a:isValid() then
a:sendEvent(common.EVENTS.GroupRegistered, groupName)
end
end
end
local function onPlayerAdded(player)
for groupName in pairs(groups:asTable()) do
player:sendEvent(common.EVENTS.GroupRegistered, groupName)
end
end
return {
registerGroup = registerGroup,
onPlayerAdded = onPlayerAdded,
}

View file

@ -0,0 +1,61 @@
local ui = require('openmw.ui')
local util = require('openmw.util')
local common = require('scripts.omw.settings.common')
local renderers = {}
local function registerRenderer(name, renderFunction)
renderers[name] = renderFunction
end
local groupOptions = {}
local function renderSetting(groupName, setting, value, index)
local renderFunction = renderers[setting.renderer]
if not renderFunction then
error(('Setting %s of %s has unknown renderer %s'):format(setting.name, groupName, setting.renderer))
end
local layout = renderFunction(setting, value or setting.default, function(value)
local group = common.getGroup(groupName)
group:set(setting.name, value)
local element = groupOptions[groupName].element
element.layout.content[setting.name] = renderSetting(groupName, setting, value, index)
element:update()
end)
layout.name = setting.name
-- temporary hacky position and size
layout.props = layout.props or {}
layout.props.position = util.vector2(0, 100 * (index - 1))
layout.props.size = util.vector2(400, 100)
return layout
end
local function onGroupRegistered(groupName)
local group = common.groups:get(groupName)
local layout = {
content = ui.content{},
}
local searchHints = { groupName }
local count = 0
for _, setting in pairs(group) do
count = count + 1
layout.content:add(renderSetting(groupName, setting, setting.default, count))
table.insert(searchHints, setting.name)
end
layout.props = {
size = util.vector2(400, 100 * count)
}
local options = {
name = groupName,
element = ui.create(layout),
searchHints = table.concat(searchHints, ' '),
}
groupOptions[groupName] = options
print(('registering group %s'):format(groupName))
ui.registerSettingsPage(options)
end
return {
onGroupRegistered = onGroupRegistered,
registerRenderer = registerRenderer,
}