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:
parent
49487a17e6
commit
096255534a
7 changed files with 396 additions and 0 deletions
|
@ -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
|
||||
|
|
150
files/builtin_scripts/scripts/omw/settings/common.lua
Normal file
150
files/builtin_scripts/scripts/omw/settings/common.lua
Normal 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,
|
||||
}
|
30
files/builtin_scripts/scripts/omw/settings/global.lua
Normal file
30
files/builtin_scripts/scripts/omw/settings/global.lua
Normal 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,
|
||||
}
|
||||
}
|
51
files/builtin_scripts/scripts/omw/settings/interface.lua
Normal file
51
files/builtin_scripts/scripts/omw/settings/interface.lua
Normal 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
|
26
files/builtin_scripts/scripts/omw/settings/player.lua
Normal file
26
files/builtin_scripts/scripts/omw/settings/player.lua
Normal 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,
|
||||
}
|
||||
}
|
76
files/builtin_scripts/scripts/omw/settings/register.lua
Normal file
76
files/builtin_scripts/scripts/omw/settings/register.lua
Normal 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,
|
||||
}
|
61
files/builtin_scripts/scripts/omw/settings/render.lua
Normal file
61
files/builtin_scripts/scripts/omw/settings/render.lua
Normal 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,
|
||||
}
|
Loading…
Reference in a new issue