From 37fd0976ab027f80530bc629a5d10899d55e23c6 Mon Sep 17 00:00:00 2001 From: Leslie Krause Date: Fri, 28 Feb 2020 11:08:31 -0500 Subject: [PATCH] Build 01 - initial beta version --- README.txt | 105 +++++++++++++++++++++++ depends.txt | 2 + init.lua | 239 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 README.txt create mode 100644 depends.txt create mode 100644 init.lua diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..bf8c686 --- /dev/null +++ b/README.txt @@ -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 diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..be3a964 --- /dev/null +++ b/depends.txt @@ -0,0 +1,2 @@ +default +formspecs diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..cf08d64 --- /dev/null +++ b/init.lua @@ -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 +} )