From 71b2570f0919d3bb5575c9ec694ecd004222fcea Mon Sep 17 00:00:00 2001 From: Andrew Ward Date: Wed, 28 Mar 2018 22:14:16 +0100 Subject: [PATCH] Load dependencies and description from mod.conf --- builtin/mainmenu/modmgr.lua | 31 +--- builtin/mainmenu/tab_mods.lua | 41 ++--- doc/lua_api.txt | 33 +++- doc/menu_lua_api.txt | 11 +- games/minimal/mods/bucket/depends.txt | 2 - games/minimal/mods/bucket/mod.conf | 3 + games/minimal/mods/default/mod.conf | 2 + games/minimal/mods/experimental/depends.txt | 2 - games/minimal/mods/experimental/mod.conf | 3 + .../mods/give_initial_stuff/depends.txt | 2 - .../minimal/mods/give_initial_stuff/mod.conf | 3 + games/minimal/mods/legacy/depends.txt | 2 - games/minimal/mods/legacy/mod.conf | 3 + games/minimal/mods/stairs/depends.txt | 1 - games/minimal/mods/stairs/mod.conf | 3 + games/minimal/mods/test/mod.conf | 2 + src/mods.cpp | 74 ++++++--- src/mods.h | 3 + src/script/lua_api/l_mainmenu.cpp | 148 ++++++++++++------ src/script/lua_api/l_mainmenu.h | 8 +- 20 files changed, 237 insertions(+), 140 deletions(-) delete mode 100644 games/minimal/mods/bucket/depends.txt create mode 100644 games/minimal/mods/bucket/mod.conf create mode 100644 games/minimal/mods/default/mod.conf delete mode 100644 games/minimal/mods/experimental/depends.txt create mode 100644 games/minimal/mods/experimental/mod.conf delete mode 100644 games/minimal/mods/give_initial_stuff/depends.txt create mode 100644 games/minimal/mods/give_initial_stuff/mod.conf delete mode 100644 games/minimal/mods/legacy/depends.txt create mode 100644 games/minimal/mods/legacy/mod.conf delete mode 100644 games/minimal/mods/stairs/depends.txt create mode 100644 games/minimal/mods/stairs/mod.conf create mode 100644 games/minimal/mods/test/mod.conf diff --git a/builtin/mainmenu/modmgr.lua b/builtin/mainmenu/modmgr.lua index dee048982..185bcd639 100644 --- a/builtin/mainmenu/modmgr.lua +++ b/builtin/mainmenu/modmgr.lua @@ -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 -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/tab_mods.lua b/builtin/mainmenu/tab_mods.lua index 60e21ee27..7685bfcc4 100644 --- a/builtin/mainmenu/tab_mods.lua +++ b/builtin/mainmenu/tab_mods.lua @@ -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 diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 413990f61..15036848b 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -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 | `-- `-- 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` diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt index 1d158f304..49c6fbef0 100644 --- a/doc/menu_lua_api.txt +++ b/doc/menu_lua_api.txt @@ -111,7 +111,7 @@ core.get_screen_info() window_height = } -Games: +Packages: core.get_game(index) ^ returns { 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 index 3a7daa1d7..000000000 --- a/games/minimal/mods/bucket/depends.txt +++ /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 index 000000000..2a6086775 --- /dev/null +++ b/games/minimal/mods/bucket/mod.conf @@ -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 index 000000000..f6f7ea798 --- /dev/null +++ b/games/minimal/mods/default/mod.conf @@ -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 index 3296b1e0a..000000000 --- a/games/minimal/mods/experimental/depends.txt +++ /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 index 000000000..018e761b3 --- /dev/null +++ b/games/minimal/mods/experimental/mod.conf @@ -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 index 3a7daa1d7..000000000 --- a/games/minimal/mods/give_initial_stuff/depends.txt +++ /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 index 000000000..bd293f418 --- /dev/null +++ b/games/minimal/mods/give_initial_stuff/mod.conf @@ -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 index 3a7daa1d7..000000000 --- a/games/minimal/mods/legacy/depends.txt +++ /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 index 000000000..12102e1ee --- /dev/null +++ b/games/minimal/mods/legacy/mod.conf @@ -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 index 4ad96d515..000000000 --- a/games/minimal/mods/stairs/depends.txt +++ /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 index 000000000..32bda004a --- /dev/null +++ b/games/minimal/mods/stairs/mod.conf @@ -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 index 000000000..0c9722fc3 --- /dev/null +++ b/games/minimal/mods/test/mod.conf @@ -0,0 +1,2 @@ +name = test +description = Adds unit tests for the engine diff --git a/src/mods.cpp b/src/mods.cpp index 2cfc02b66..6fa578f2f 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include #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 &symbols) +bool parseDependsString(std::string &dep, + std::unordered_set &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 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(&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(&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 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 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(is)), + std::istreambuf_iterator()); + } } } diff --git a/src/mods.h b/src/mods.h index 037d6bd1c..3063edaa2 100644 --- a/src/mods.h +++ b/src/mods.h @@ -37,6 +37,8 @@ struct ModSpec { std::string name; std::string path; + std::string desc; + //if normal mod: std::unordered_set depends; std::unordered_set optdepends; @@ -44,6 +46,7 @@ struct ModSpec bool part_of_modpack = false; bool is_modpack = false; + // if modpack: std::map modpack_content; ModSpec(const std::string &name_ = "", const std::string &path_ = ""): diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 95696bc20..027b7b0f8 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -257,56 +257,6 @@ int ModApiMainMenu::l_get_worlds(lua_State *L) return 1; } -/******************************************************************************/ -int ModApiMainMenu::l_get_games(lua_State *L) -{ - std::vector 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 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); diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index ffaab7dca..2faeaf63e 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -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);