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