Load dependencies and description from mod.conf
authorAndrew Ward <rw@rubenwardy.com>
Wed, 28 Mar 2018 21:14:16 +0000 (22:14 +0100)
committerGitHub <noreply@github.com>
Wed, 28 Mar 2018 21:14:16 +0000 (22:14 +0100)
20 files changed:
builtin/mainmenu/modmgr.lua
builtin/mainmenu/tab_mods.lua
doc/lua_api.txt
doc/menu_lua_api.txt
games/minimal/mods/bucket/depends.txt [deleted file]
games/minimal/mods/bucket/mod.conf [new file with mode: 0644]
games/minimal/mods/default/mod.conf [new file with mode: 0644]
games/minimal/mods/experimental/depends.txt [deleted file]
games/minimal/mods/experimental/mod.conf [new file with mode: 0644]
games/minimal/mods/give_initial_stuff/depends.txt [deleted file]
games/minimal/mods/give_initial_stuff/mod.conf [new file with mode: 0644]
games/minimal/mods/legacy/depends.txt [deleted file]
games/minimal/mods/legacy/mod.conf [new file with mode: 0644]
games/minimal/mods/stairs/depends.txt [deleted file]
games/minimal/mods/stairs/mod.conf [new file with mode: 0644]
games/minimal/mods/test/mod.conf [new file with mode: 0644]
src/mods.cpp
src/mods.h
src/script/lua_api/l_mainmenu.cpp
src/script/lua_api/l_mainmenu.h

index dee0489823425d1133ab5b63b0efe70ab15ba6a7..185bcd6394d3a3ab729b11f50e2c806a8a7c321c 100644 (file)
@@ -271,34 +271,13 @@ function modmgr.render_modlist(render_list)
 end
 
 --------------------------------------------------------------------------------
-function modmgr.get_dependencies(modfolder)
-       local toadd_hard = ""
-       local toadd_soft = ""
-       if modfolder ~= nil then
-               local filename = modfolder ..
-                                       DIR_DELIM .. "depends.txt"
-
-               local hard_dependencies = {}
-               local soft_dependencies = {}
-               local dependencyfile = io.open(filename,"r")
-               if dependencyfile then
-                       local dependency = dependencyfile:read("*l")
-                       while dependency do
-                               dependency = dependency:gsub("\r", "")
-                               if string.sub(dependency, -1, -1) == "?" then
-                                       table.insert(soft_dependencies, string.sub(dependency, 1, -2))
-                               else
-                                       table.insert(hard_dependencies, dependency)
-                               end
-                               dependency = dependencyfile:read()
-                       end
-                       dependencyfile:close()
-               end
-               toadd_hard = table.concat(hard_dependencies, ",")
-               toadd_soft = table.concat(soft_dependencies, ",")
+function modmgr.get_dependencies(path)
+       if path == nil then
+               return "", ""
        end
 
-       return toadd_hard, toadd_soft
+       local info = core.get_mod_info(path)
+       return table.concat(info.depends, ","), table.concat(info.optional_depends, ",")
 end
 
 --------------------------------------------------------------------------------
index 60e21ee27dc18f2b6a3a7c1e460e9c813aca3403..7685bfcc4fae82a3b5176de0329dafb534bfd32d 100644 (file)
@@ -40,12 +40,11 @@ local function get_formspec(tabview, name, tabdata)
        end
 
        if selected_mod ~= nil then
-               local modscreenshot = nil
-
                --check for screenshot beeing available
                local screenshotfilename = selected_mod.path .. DIR_DELIM .. "screenshot.png"
-               local error = nil
-               local screenshotfile,error = io.open(screenshotfilename,"r")
+               local screenshotfile, error = io.open(screenshotfilename,"r")
+
+               local modscreenshot
                if error == nil then
                        screenshotfile:close()
                        modscreenshot = screenshotfilename
@@ -55,33 +54,20 @@ local function get_formspec(tabview, name, tabdata)
                                modscreenshot = defaulttexturedir .. "no_screenshot.png"
                end
 
-               retval = retval
-                               .. "image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]"
-                               .. "label[8.25,0.6;" .. selected_mod.name .. "]"
-
-               local descriptionlines = nil
-               error = nil
-               local descriptionfilename = selected_mod.path .. "description.txt"
-               local descriptionfile,error = io.open(descriptionfilename,"r")
-               if error == nil then
-                       local descriptiontext = descriptionfile:read("*all")
-
-                       descriptionlines = core.wrap_text(descriptiontext, 42, true)
-                       descriptionfile:close()
-               else
-                       descriptionlines = {}
-                       descriptionlines[#descriptionlines + 1] = fgettext("No mod description available")
-               end
-
                retval = retval ..
-                       "label[5.5,1.7;".. fgettext("Mod Information:") .. "]" ..
-                       "textlist[5.5,2.2;6.2,2.4;description;"
+                               "image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]" ..
+                               "label[8.25,0.6;" .. selected_mod.name .. "]" ..
+                               "label[5.5,1.7;".. fgettext("Mod Information:") .. "]" ..
+                               "textlist[5.5,2.2;6.2,2.4;description;"
 
-               for i=1,#descriptionlines,1 do
+
+               local info = core.get_mod_info(selected_mod.path)
+               local desc = info.description or fgettext("No mod description available")
+               local descriptionlines = core.wrap_text(desc, 42, true)
+               for i = 1, #descriptionlines do
                        retval = retval .. core.formspec_escape(descriptionlines[i]) .. ","
                end
 
-
                if selected_mod.is_modpack then
                        retval = retval .. ";0]" ..
                                "button[9.9,4.65;2,1;btn_mod_mgr_rename_modpack;" ..
@@ -90,7 +76,8 @@ local function get_formspec(tabview, name, tabdata)
                                .. fgettext("Uninstall Selected Modpack") .. "]"
                else
                        --show dependencies
-                       local toadd_hard, toadd_soft = modmgr.get_dependencies(selected_mod.path)
+                       local toadd_hard = table.concat(info.depends, ",")
+                       local toadd_soft = table.concat(info.optional_depends, ",")
                        if toadd_hard == "" and toadd_soft == "" then
                                retval = retval .. "," .. fgettext("No dependencies.")
                        else
index 413990f61d2e16ef5af37677f3994df178205183..15036848b715d610c7f8753c1849a850030e8e93 100644 (file)
@@ -130,9 +130,8 @@ Mod directory structure
 
     mods
     |-- modname
-    |   |-- depends.txt
+    |   |-- mod.conf
     |   |-- screenshot.png
-    |   |-- description.txt
     |   |-- settingtypes.txt
     |   |-- init.lua
     |   |-- models
@@ -145,12 +144,32 @@ Mod directory structure
     |   `-- <custom data>
     `-- another
 
-
 ### modname
 The location of this directory can be fetched by using
 `minetest.get_modpath(modname)`.
 
+### mod.conf
+A key-value store of mod details.
+
+* `name` - the mod name. Allows Minetest to determine the mod name even if the
+           folder is wrongly named.
+* `description` - Description of mod to be shown in the Mods tab of the mainmenu.
+* `depends` - A comma separated list of dependencies. These are mods that must
+              be loaded before this mod.
+* `optional_depends` - A comma separated list of optional dependencies.
+                       Like a dependency, but no error if the mod doesn't exist.
+
+Note: to support 0.4.x, please also provide depends.txt.
+
+### `screenshot.png`
+A screenshot shown in the mod manager within the main menu. It should
+have an aspect ratio of 3:2 and a minimum size of 300×200 pixels.
+
 ### `depends.txt`
+**Deprecated:** you should use mod.conf instead.
+
+This file is used if there are no dependencies in mod.conf.
+
 List of mods that have to be loaded before loading this mod.
 
 A single line contains a single modname.
@@ -159,11 +178,11 @@ Optional dependencies can be defined by appending a question mark
 to a single modname. This means that if the specified mod
 is missing, it does not prevent this mod from being loaded.
 
-### `screenshot.png`
-A screenshot shown in the mod manager within the main menu. It should
-have an aspect ratio of 3:2 and a minimum size of 300×200 pixels.
-
 ### `description.txt`
+**Deprecated:** you should use mod.conf instead.
+
+This file is used if there is no description in mod.conf.
+
 A file containing a description to be shown in the Mods tab of the mainmenu.
 
 ### `settingtypes.txt`
index 1d158f3049fcfb6d94432d4c10455ebf4549b240..49c6fbef0a8d6012fab5c7d78f5c51c05212fd55 100644 (file)
@@ -111,7 +111,7 @@ core.get_screen_info()
        window_height   = <current window height>
        }
 
-Games:
+Packages:
 core.get_game(index)
 ^ returns {
        id               = <id>,
@@ -125,6 +125,15 @@ core.get_game(index)
 core.get_games() -> table of all games in upper format (possible in async calls)
 core.get_mapgen_names([include_hidden=false]) -> table of map generator algorithms
     registered in the core (possible in async calls)
+core.get_mod_info(path)
+^ returns  {
+       name             = "name of mod",
+       type             = "mod" or "modpack",
+       description      = "description",
+       path             = "path/to/mod",
+       depends          = {"mod", "names"},
+       optional_depends = {"mod", "names"},
+}
 
 Favorites:
 core.get_favorites(location) -> list of favorites (possible in async calls)
diff --git a/games/minimal/mods/bucket/depends.txt b/games/minimal/mods/bucket/depends.txt
deleted file mode 100644 (file)
index 3a7daa1..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-default
-
diff --git a/games/minimal/mods/bucket/mod.conf b/games/minimal/mods/bucket/mod.conf
new file mode 100644 (file)
index 0000000..2a60867
--- /dev/null
@@ -0,0 +1,3 @@
+name = bucket
+description = Minimal bucket to place and pick up liquids
+depends = default
diff --git a/games/minimal/mods/default/mod.conf b/games/minimal/mods/default/mod.conf
new file mode 100644 (file)
index 0000000..f6f7ea7
--- /dev/null
@@ -0,0 +1,2 @@
+name = default
+description = Minimal default, adds basic nodes
diff --git a/games/minimal/mods/experimental/depends.txt b/games/minimal/mods/experimental/depends.txt
deleted file mode 100644 (file)
index 3296b1e..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-default
-stairs
diff --git a/games/minimal/mods/experimental/mod.conf b/games/minimal/mods/experimental/mod.conf
new file mode 100644 (file)
index 0000000..018e761
--- /dev/null
@@ -0,0 +1,3 @@
+name = experimental
+description = Minimal mod to test features
+depends = default, stairs
diff --git a/games/minimal/mods/give_initial_stuff/depends.txt b/games/minimal/mods/give_initial_stuff/depends.txt
deleted file mode 100644 (file)
index 3a7daa1..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-default
-
diff --git a/games/minimal/mods/give_initial_stuff/mod.conf b/games/minimal/mods/give_initial_stuff/mod.conf
new file mode 100644 (file)
index 0000000..bd293f4
--- /dev/null
@@ -0,0 +1,3 @@
+name = give_initial_stuff
+description = Gives items to players on join
+depends = default
diff --git a/games/minimal/mods/legacy/depends.txt b/games/minimal/mods/legacy/depends.txt
deleted file mode 100644 (file)
index 3a7daa1..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-default
-
diff --git a/games/minimal/mods/legacy/mod.conf b/games/minimal/mods/legacy/mod.conf
new file mode 100644 (file)
index 0000000..12102e1
--- /dev/null
@@ -0,0 +1,3 @@
+name = legacy
+description = Aliases allowing support for 0.3.x worlds
+depends = default
diff --git a/games/minimal/mods/stairs/depends.txt b/games/minimal/mods/stairs/depends.txt
deleted file mode 100644 (file)
index 4ad96d5..0000000
+++ /dev/null
@@ -1 +0,0 @@
-default
diff --git a/games/minimal/mods/stairs/mod.conf b/games/minimal/mods/stairs/mod.conf
new file mode 100644 (file)
index 0000000..32bda00
--- /dev/null
@@ -0,0 +1,3 @@
+name = stairs
+description = Adds stairs and slabs
+depends = default
diff --git a/games/minimal/mods/test/mod.conf b/games/minimal/mods/test/mod.conf
new file mode 100644 (file)
index 0000000..0c9722f
--- /dev/null
@@ -0,0 +1,2 @@
+name = test
+description = Adds unit tests for the engine
index 2cfc02b666b150c38f56861f128e2514b00e07ed..6fa578f2f1724bb7842fbc43f011d504e1a16500 100644 (file)
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <cctype>
 #include <fstream>
 #include <json/json.h>
+#include <algorithm>
 #include "mods.h"
 #include "filesys.h"
 #include "log.h"
@@ -28,14 +29,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "porting.h"
 #include "convert_json.h"
 
-static bool parseDependsLine(std::istream &is,
-               std::string &dep, std::set<char> &symbols)
+bool parseDependsString(std::string &dep,
+               std::unordered_set<char> &symbols)
 {
-       std::getline(is, dep);
        dep = trim(dep);
        symbols.clear();
        size_t pos = dep.size();
-       while(pos > 0 && !string_allowed(dep.substr(pos-1, 1), MODNAME_ALLOWED_CHARS)){
+       while (pos > 0 && !string_allowed(dep.substr(pos-1, 1), MODNAME_ALLOWED_CHARS)) {
                // last character is a symbol, not part of the modname
                symbols.insert(dep[pos-1]);
                --pos;
@@ -60,28 +60,66 @@ void parseModContents(ModSpec &spec)
 
        // Handle modpacks (defined by containing modpack.txt)
        std::ifstream modpack_is((spec.path+DIR_DELIM+"modpack.txt").c_str());
-       if(modpack_is.good()){ //a modpack, recursively get the mods in it
+       if (modpack_is.good()) { // a modpack, recursively get the mods in it
                modpack_is.close(); // We don't actually need the file
                spec.is_modpack = true;
                spec.modpack_content = getModsInPath(spec.path, true);
-
                // modpacks have no dependencies; they are defined and
                // tracked separately for each mod in the modpack
-       }
-       else{ // not a modpack, parse the dependencies
-               std::ifstream is((spec.path+DIR_DELIM+"depends.txt").c_str());
-               while(is.good()){
-                       std::string dep;
-                       std::set<char> symbols;
-                       if(parseDependsLine(is, dep, symbols)){
-                               if(symbols.count('?') != 0){
-                                       spec.optdepends.insert(dep);
-                               }
-                               else{
-                                       spec.depends.insert(dep);
+
+       } else {
+               // Attempt to load dependencies from mod.conf
+               bool mod_conf_has_depends = false;
+               if (info.exists("depends")) {
+                       mod_conf_has_depends = true;
+                       std::string dep = info.get("depends");
+                       dep.erase(std::remove_if(dep.begin(), dep.end(),
+                                       static_cast<int(*)(int)>(&std::isspace)), dep.end());
+                       for (const auto &dependency : str_split(dep, ',')) {
+                               spec.depends.insert(dependency);
+                       }
+               }
+
+               if (info.exists("optional_depends")) {
+                       mod_conf_has_depends = true;
+                       std::string dep = info.get("optional_depends");
+                       dep.erase(std::remove_if(dep.begin(), dep.end(),
+                                       static_cast<int(*)(int)>(&std::isspace)), dep.end());
+                       for (const auto &dependency : str_split(dep, ',')) {
+                               spec.optdepends.insert(dependency);
+                       }
+               }
+
+               // Fallback to depends.txt
+               if (!mod_conf_has_depends) {
+                       std::vector<std::string> dependencies;
+
+                       std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str());
+                       while (is.good()) {
+                               std::string dep;
+                               std::getline(is, dep);
+                               dependencies.push_back(dep);
+                       }
+
+                       for (auto &dependency : dependencies) {
+                               std::unordered_set<char> symbols;
+                               if (parseDependsString(dependency, symbols)) {
+                                       if (symbols.count('?') != 0) {
+                                               spec.optdepends.insert(dependency);
+                                       } else {
+                                               spec.depends.insert(dependency);
+                                       }
                                }
                        }
                }
+
+               if (info.exists("description")) {
+                       spec.desc = info.get("description");
+               } else {
+                       std::ifstream is((spec.path + DIR_DELIM + "description.txt").c_str());
+                       spec.desc = std::string((std::istreambuf_iterator<char>(is)),
+                                       std::istreambuf_iterator<char>());
+               }
        }
 }
 
index 037d6bd1c56388069e892a59e857dbae96706e8c..3063edaa292f07e16bb4546385572e325f358c5c 100644 (file)
@@ -37,6 +37,8 @@ struct ModSpec
 {
        std::string name;
        std::string path;
+       std::string desc;
+
        //if normal mod:
        std::unordered_set<std::string> depends;
        std::unordered_set<std::string> optdepends;
@@ -44,6 +46,7 @@ struct ModSpec
 
        bool part_of_modpack = false;
        bool is_modpack = false;
+
        // if modpack:
        std::map<std::string,ModSpec> modpack_content;
        ModSpec(const std::string &name_ = "", const std::string &path_ = ""):
index 95696bc20f7bb444f9aacb00fae0a5da3c54f97c..027b7b0f8afde5ef14fcfddf3812a073c5771838 100644 (file)
@@ -257,56 +257,6 @@ int ModApiMainMenu::l_get_worlds(lua_State *L)
        return 1;
 }
 
-/******************************************************************************/
-int ModApiMainMenu::l_get_games(lua_State *L)
-{
-       std::vector<SubgameSpec> games = getAvailableGames();
-
-       lua_newtable(L);
-       int top = lua_gettop(L);
-       unsigned int index = 1;
-
-       for (const SubgameSpec &game : games) {
-               lua_pushnumber(L,index);
-               lua_newtable(L);
-               int top_lvl2 = lua_gettop(L);
-
-               lua_pushstring(L,"id");
-               lua_pushstring(L, game.id.c_str());
-               lua_settable(L, top_lvl2);
-
-               lua_pushstring(L,"path");
-               lua_pushstring(L, game.path.c_str());
-               lua_settable(L, top_lvl2);
-
-               lua_pushstring(L,"gamemods_path");
-               lua_pushstring(L, game.gamemods_path.c_str());
-               lua_settable(L, top_lvl2);
-
-               lua_pushstring(L,"name");
-               lua_pushstring(L, game.name.c_str());
-               lua_settable(L, top_lvl2);
-
-               lua_pushstring(L,"menuicon_path");
-               lua_pushstring(L, game.menuicon_path.c_str());
-               lua_settable(L, top_lvl2);
-
-               lua_pushstring(L,"addon_mods_paths");
-               lua_newtable(L);
-               int table2 = lua_gettop(L);
-               int internal_index=1;
-               for (const std::string &addon_mods_path : game.addon_mods_paths) {
-                       lua_pushnumber(L,internal_index);
-                       lua_pushstring(L, addon_mods_path.c_str());
-                       lua_settable(L, table2);
-                       internal_index++;
-               }
-               lua_settable(L, top_lvl2);
-               lua_settable(L, top);
-               index++;
-       }
-       return 1;
-}
 /******************************************************************************/
 int ModApiMainMenu::l_get_favorites(lua_State *L)
 {
@@ -477,6 +427,103 @@ int ModApiMainMenu::l_delete_favorite(lua_State *L)
        return 0;
 }
 
+/******************************************************************************/
+int ModApiMainMenu::l_get_games(lua_State *L)
+{
+       std::vector<SubgameSpec> games = getAvailableGames();
+
+       lua_newtable(L);
+       int top = lua_gettop(L);
+       unsigned int index = 1;
+
+       for (const SubgameSpec &game : games) {
+               lua_pushnumber(L, index);
+               lua_newtable(L);
+               int top_lvl2 = lua_gettop(L);
+
+               lua_pushstring(L, "id");
+               lua_pushstring(L, game.id.c_str());
+               lua_settable(L,   top_lvl2);
+
+               lua_pushstring(L, "path");
+               lua_pushstring(L, game.path.c_str());
+               lua_settable(L,   top_lvl2);
+
+               lua_pushstring(L, "gamemods_path");
+               lua_pushstring(L, game.gamemods_path.c_str());
+               lua_settable(L,   top_lvl2);
+
+               lua_pushstring(L, "name");
+               lua_pushstring(L, game.name.c_str());
+               lua_settable(L,   top_lvl2);
+
+               lua_pushstring(L, "menuicon_path");
+               lua_pushstring(L, game.menuicon_path.c_str());
+               lua_settable(L,   top_lvl2);
+
+               lua_pushstring(L, "addon_mods_paths");
+               lua_newtable(L);
+               int table2 = lua_gettop(L);
+               int internal_index = 1;
+               for (const std::string &addon_mods_path : game.addon_mods_paths) {
+                       lua_pushnumber(L, internal_index);
+                       lua_pushstring(L, addon_mods_path.c_str());
+                       lua_settable(L,   table2);
+                       internal_index++;
+               }
+               lua_settable(L, top_lvl2);
+               lua_settable(L, top);
+               index++;
+       }
+       return 1;
+}
+
+/******************************************************************************/
+int ModApiMainMenu::l_get_mod_info(lua_State *L)
+{
+       std::string path = luaL_checkstring(L, 1);
+
+       ModSpec spec;
+       spec.path = path;
+       parseModContents(spec);
+
+       lua_newtable(L);
+
+       lua_pushstring(L, spec.name.c_str());
+       lua_setfield(L, -2, "name");
+
+       lua_pushstring(L, spec.is_modpack ? "modpack" : "mod");
+       lua_setfield(L, -2, "type");
+
+       lua_pushstring(L, spec.desc.c_str());
+       lua_setfield(L, -2, "description");
+
+       lua_pushstring(L, spec.path.c_str());
+       lua_setfield(L, -2, "path");
+
+       // Dependencies
+       lua_newtable(L);
+       int i = 1;
+       for (const auto &dep : spec.depends) {
+               lua_pushstring(L, dep.c_str());
+               lua_rawseti(L, -2, i);
+               i++;
+       }
+       lua_setfield(L, -2, "depends");
+
+       // Optional Dependencies
+       lua_newtable(L);
+       i = 1;
+       for (const auto &dep : spec.optdepends) {
+               lua_pushstring(L, dep.c_str());
+               lua_rawseti(L, -2, i);
+               i++;
+       }
+       lua_setfield(L, -2, "optional_depends");
+
+       return 1;
+}
+
 /******************************************************************************/
 int ModApiMainMenu::l_show_keys_menu(lua_State *L)
 {
@@ -968,6 +1015,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
        API_FCT(get_table_index);
        API_FCT(get_worlds);
        API_FCT(get_games);
+       API_FCT(get_mod_info);
        API_FCT(start);
        API_FCT(close);
        API_FCT(get_favorites);
index ffaab7dcafd24429cc5f6c6fed865dd1c80085c8..2faeaf63eb538a1004b7851a4544b12acfc0538f 100644 (file)
@@ -71,8 +71,6 @@ private:
 
        static int l_get_worlds(lua_State *L);
 
-       static int l_get_games(lua_State *L);
-
        static int l_get_mapgen_names(lua_State *L);
 
        static int l_get_favorites(lua_State *L);
@@ -81,6 +79,12 @@ private:
 
        static int l_gettext(lua_State *L);
 
+       //packages
+
+       static int l_get_games(lua_State *L);
+
+       static int l_get_mod_info(lua_State *L);
+
        //gui
 
        static int l_show_keys_menu(lua_State *L);