commit
37fd0976ab
|
@ -0,0 +1,105 @@
|
||||||
|
Configuration Panel Mod v1.0
|
||||||
|
By Leslie Krause
|
||||||
|
|
||||||
|
Configuration Panel is an API extension for Minetest that allows seamless loading of mod
|
||||||
|
configuration at runtime. It provides much greater flexibility than Minetest's builtin
|
||||||
|
'Settings' interface, since the mod configuration itself is read as a native Lua script.
|
||||||
|
Hence it's possible to include simple data structures without the need for serialization,
|
||||||
|
in addition to temporary variables, mathematical expressions, and conditional branching.
|
||||||
|
|
||||||
|
While some might be opposed to using Lua for configuration purposes, arguing that it is
|
||||||
|
anti-pattern, that's not actually true. In fact, Lua itself was originally intended to
|
||||||
|
double as a configuration language, like JSON, so it is very befitting of its purpose.
|
||||||
|
|
||||||
|
"An important use of Lua is as a configuration language...."
|
||||||
|
from Programming in Lua: Extending your Application (https://www.lua.org/pil/25.html)
|
||||||
|
|
||||||
|
There is only one function call necessary to load your mod's configuration:
|
||||||
|
|
||||||
|
minetest.load_config( base_config, options )
|
||||||
|
Automatically loads the mod configuration at server-startup according to the options
|
||||||
|
and returns a table of key-value pairs.
|
||||||
|
|
||||||
|
* 'base_config' is the default mod configuration (optional)
|
||||||
|
* 'options' are the additional options that effect loading behavior (optional)
|
||||||
|
|
||||||
|
By default, the configuration is first loaded from the 'config.lua' script within the mod
|
||||||
|
directory. If not found, it will instead be loaded from a script residing within the
|
||||||
|
'config' subdirectory of the current world. That script must be named the same as the mod
|
||||||
|
but with a '.lua' extension, of course.
|
||||||
|
|
||||||
|
This would be the typical order of loading on Linux distros of Minetest:
|
||||||
|
|
||||||
|
/usr/local/share/minetest/games/minetest_game/mods/sample_mod/config.lua
|
||||||
|
/home/minetest/.minetest/worlds/sample_world/config/sample_mod.lua
|
||||||
|
|
||||||
|
By specifying the option 'can_override = true', both scripts will be loaded, allowing for
|
||||||
|
the world configuration to override the game configuration. So for example
|
||||||
|
|
||||||
|
/usr/local/share/minetest/games/minetest_game/mods/sample_mod/config.lua
|
||||||
|
> allow_fly = false
|
||||||
|
> allow_walk = false
|
||||||
|
> allow_swim = false
|
||||||
|
|
||||||
|
/home/minetest/.minetest/worlds/sample_world/config/sample_mod.lua
|
||||||
|
> allow_swim = true
|
||||||
|
|
||||||
|
Hence, 'allow_fly' and 'allow_walk' will be false, whereas 'allow_swim' will be true. By
|
||||||
|
default, however, all three would remain false since the world configuration is ignored
|
||||||
|
whenever the game configuration is found.
|
||||||
|
|
||||||
|
A chat command is also available for editing the configuration directly in-game. Simply
|
||||||
|
type '/config' followed by the mod name to configure. The interface is self-explanatory.
|
||||||
|
|
||||||
|
|
||||||
|
Repository
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Browse source code:
|
||||||
|
https://bitbucket.org/sorcerykid/config
|
||||||
|
|
||||||
|
Download archive:
|
||||||
|
https://bitbucket.org/sorcerykid/config/get/master.zip
|
||||||
|
https://bitbucket.org/sorcerykid/config/get/master.tar.gz
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Default Mod (required)
|
||||||
|
https://github.com/minetest-game-mods/default
|
||||||
|
|
||||||
|
ActiveFormspecs Mod v2.6 (required)
|
||||||
|
https://bitbucket.org/sorcerykid/formspecs
|
||||||
|
|
||||||
|
Installation
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
1) Unzip the archive into the mods directory of your game.
|
||||||
|
2) Rename the config-master directory to "config".
|
||||||
|
3) Add "config" as a dependency to any mods using the API.
|
||||||
|
|
||||||
|
Source Code License
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2020, Leslie Krause (leslie@searstower.org)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more details:
|
||||||
|
https://opensource.org/licenses/MIT
|
|
@ -0,0 +1,2 @@
|
||||||
|
default
|
||||||
|
formspecs
|
|
@ -0,0 +1,239 @@
|
||||||
|
--------------------------------------------------------
|
||||||
|
-- Minetest :: Configuration Panel Mod (config)
|
||||||
|
--
|
||||||
|
-- See README.txt for licensing and other information.
|
||||||
|
-- Copyright (c) 2016-2020, Leslie E. Krause
|
||||||
|
--
|
||||||
|
-- ./games/minetest_game/mods/config/init.lua
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
local world_path = minetest.get_worldpath( )
|
||||||
|
local configured_mods = { }
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
-- Private Methods --
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
local function import( config, filename )
|
||||||
|
local func = loadfile( filename )
|
||||||
|
if func then
|
||||||
|
setfenv( func, config )
|
||||||
|
local status = pcall( func )
|
||||||
|
if not status then
|
||||||
|
error( "Syntax error in configuration file: " .. filename )
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function load_world_config( mod_name )
|
||||||
|
local file = io.open( world_path .. "/config/" .. mod_name .. ".lua", "r" )
|
||||||
|
if not file then return nil end
|
||||||
|
|
||||||
|
local data = file:read( "*all" )
|
||||||
|
file:close( )
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
local function load_game_config( mod_name )
|
||||||
|
local file = io.open( minetest.get_modpath( mod_name ) .. "/config.lua", "r" )
|
||||||
|
if not file then return nil end
|
||||||
|
|
||||||
|
local data = file:read( "*all" )
|
||||||
|
file:close( )
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
local function save_world_config( mod_name, data )
|
||||||
|
local file = io.open( world_path .. "/config/" .. mod_name .. ".lua", "w" )
|
||||||
|
if not file then return false end
|
||||||
|
|
||||||
|
file:write( data )
|
||||||
|
file:close( )
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function save_game_config( mod_name, data )
|
||||||
|
local file = io.open( minetest.get_modpath( mod_name ) .. "/config.lua", "w" )
|
||||||
|
if not file then return false end
|
||||||
|
|
||||||
|
file:write( data )
|
||||||
|
file:close( )
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_world_config( mod_name )
|
||||||
|
local file = io.open( world_path .. "/config/" .. mod_name .. ".lua", "w" )
|
||||||
|
if not file then return false end
|
||||||
|
|
||||||
|
file:close( )
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_game_config( mod_name )
|
||||||
|
local file = io.open( minetest.get_modpath( mod_name ) .. "/config.lua", "w" )
|
||||||
|
if not file then return false end
|
||||||
|
|
||||||
|
file:close( )
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function delete_world_config( mod_name )
|
||||||
|
return os.remove( world_path .. "/config/" .. mod_name .. ".lua" ) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function delete_game_config( mod_name )
|
||||||
|
return os.remove( minetest.get_modpath( mod_name ) .. "/config.lua" ) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function open_config_editor( player_name, mod_name )
|
||||||
|
local origin_idx
|
||||||
|
local content
|
||||||
|
local delete_config, create_config, load_config, save_config
|
||||||
|
|
||||||
|
local function reset_origin_map( idx )
|
||||||
|
origin_idx = idx
|
||||||
|
|
||||||
|
if origin_idx == 1 then
|
||||||
|
delete_config = delete_game_config
|
||||||
|
create_config = create_game_config
|
||||||
|
load_config = load_game_config
|
||||||
|
save_config = save_game_config
|
||||||
|
else
|
||||||
|
delete_config = delete_world_config
|
||||||
|
create_config = create_world_config
|
||||||
|
load_config = load_world_config
|
||||||
|
save_config = save_world_config
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_formspec( )
|
||||||
|
local formspec =
|
||||||
|
"size[10.0,8.5]" ..
|
||||||
|
default.gui_bg ..
|
||||||
|
default.gui_bg_img ..
|
||||||
|
|
||||||
|
string.format( "label[0.0,0.1;Configuration for mod '%s']", mod_name ) ..
|
||||||
|
"label[2.2,8.0;Origin:]" ..
|
||||||
|
string.format( "dropdown[3.1,7.9;2.6,1.0;origin;Game Config,World Config;%d;true]", origin_idx )
|
||||||
|
|
||||||
|
if not content then
|
||||||
|
formspec = formspec ..
|
||||||
|
"box[0.0,0.8;9.8.0,6.6;#222222]" ..
|
||||||
|
"label[4.0,3.8;File does not exist.]" ..
|
||||||
|
|
||||||
|
"button_exit[0.0,7.8;2.0,1.0;close;Close]" ..
|
||||||
|
"button[8.0,7.8;2.0,1.0;create;Create]"
|
||||||
|
else
|
||||||
|
formspec = formspec ..
|
||||||
|
"textarea[0.3,0.8;10.0,7.6;content;;" .. minetest.formspec_escape( content ) .. "]" ..
|
||||||
|
|
||||||
|
"button_exit[0.0,7.8;2.0,1.0;close;Close]" ..
|
||||||
|
"button[6.0,7.8;2.0,1.0;delete;Delete]" ..
|
||||||
|
"button[8.0,7.8;2.0,1.0;save;Save]"
|
||||||
|
end
|
||||||
|
|
||||||
|
return formspec
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_close( state, player, fields )
|
||||||
|
if fields.quit then return end -- short-circuit on form closure
|
||||||
|
|
||||||
|
if fields.save then
|
||||||
|
content = fields.content
|
||||||
|
save_config( mod_name, content )
|
||||||
|
|
||||||
|
elseif fields.load then
|
||||||
|
content = load_config( mod_name )
|
||||||
|
minetest.update_form( player_name, get_formspec ( ) )
|
||||||
|
|
||||||
|
elseif fields.delete then
|
||||||
|
if not delete_config( mod_name ) then
|
||||||
|
minetest.destroy_form( player_name )
|
||||||
|
minetest.chat_send_player( player_name, "Unable to delete configuration file." )
|
||||||
|
end
|
||||||
|
content = nil
|
||||||
|
minetest.update_form( player_name, get_formspec ( ) )
|
||||||
|
|
||||||
|
elseif fields.create then
|
||||||
|
if not create_config( mod_name ) then
|
||||||
|
minetest.destroy_form( player_name )
|
||||||
|
minetest.chat_send_player( player_name, "Unable to create configuration file." )
|
||||||
|
end
|
||||||
|
content = ""
|
||||||
|
minetest.update_form( player_name, get_formspec ( ) )
|
||||||
|
|
||||||
|
elseif fields.origin then
|
||||||
|
reset_origin_map( fields.origin )
|
||||||
|
content = load_config( mod_name )
|
||||||
|
minetest.update_form( player_name, get_formspec ( ) )
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
reset_origin_map( 1 )
|
||||||
|
content = load_config( mod_name )
|
||||||
|
|
||||||
|
minetest.create_form( nil, player_name, get_formspec( ), on_close )
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
-- Public Methods --
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
minetest.load_config = function ( base_config, options )
|
||||||
|
local name = minetest.get_current_modname( )
|
||||||
|
local path = minetest.get_modpath( name )
|
||||||
|
local config = { } or base_config
|
||||||
|
local status
|
||||||
|
|
||||||
|
if not options then options = { } end
|
||||||
|
|
||||||
|
config.core = {
|
||||||
|
MOD_NAME = name,
|
||||||
|
MOD_PATH = path,
|
||||||
|
WORLD_PATH = world_path,
|
||||||
|
sprintf = string.format,
|
||||||
|
date = os.date,
|
||||||
|
time = os.time,
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.can_override then
|
||||||
|
status = import( config, path .. "/config.lua" )
|
||||||
|
status = import( config, world_path .. "/config/" .. name .. ".lua" ) or status
|
||||||
|
else
|
||||||
|
status = import( config, path .. "/config.lua" ) or import( config, world_path .. "/config/" .. name .. ".lua" )
|
||||||
|
end
|
||||||
|
|
||||||
|
if not status then
|
||||||
|
minetest.log( "warning", "Missing configuration file for mod \"" .. name .. "\"" )
|
||||||
|
end
|
||||||
|
|
||||||
|
configured_mods[ name ] = {
|
||||||
|
base_config = base_config,
|
||||||
|
can_refresh = options.can_refresh,
|
||||||
|
can_override = options.can_override,
|
||||||
|
}
|
||||||
|
config.core = nil
|
||||||
|
|
||||||
|
return config
|
||||||
|
end
|
||||||
|
|
||||||
|
------------------------------
|
||||||
|
-- Registered Chat Commands --
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
minetest.register_chatcommand( "config", {
|
||||||
|
description = "View and edit the configuration for a given mod.",
|
||||||
|
privs = { server = true },
|
||||||
|
func = function( player_name, param )
|
||||||
|
if not string.match( param, "^[a-zA-Z0-9_]+$" ) then
|
||||||
|
return false, "Invalid mod name."
|
||||||
|
elseif not configured_mods[ param ] then
|
||||||
|
return false, "Configuration not available."
|
||||||
|
end
|
||||||
|
|
||||||
|
open_config_editor( player_name, param )
|
||||||
|
end
|
||||||
|
} )
|
Loading…
Reference in New Issue