Load dependencies and description from mod.conf

This commit is contained in:
Andrew Ward 2018-03-28 22:14:16 +01:00 committed by GitHub
parent dfc8198349
commit 71b2570f09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 237 additions and 140 deletions

View File

@ -271,34 +271,13 @@ function modmgr.render_modlist(render_list)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.get_dependencies(modfolder) function modmgr.get_dependencies(path)
local toadd_hard = "" if path == nil then
local toadd_soft = "" return "", ""
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, ",")
end end
return toadd_hard, toadd_soft local info = core.get_mod_info(path)
return table.concat(info.depends, ","), table.concat(info.optional_depends, ",")
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@ -40,12 +40,11 @@ local function get_formspec(tabview, name, tabdata)
end end
if selected_mod ~= nil then if selected_mod ~= nil then
local modscreenshot = nil
--check for screenshot beeing available --check for screenshot beeing available
local screenshotfilename = selected_mod.path .. DIR_DELIM .. "screenshot.png" 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 if error == nil then
screenshotfile:close() screenshotfile:close()
modscreenshot = screenshotfilename modscreenshot = screenshotfilename
@ -55,33 +54,20 @@ local function get_formspec(tabview, name, tabdata)
modscreenshot = defaulttexturedir .. "no_screenshot.png" modscreenshot = defaulttexturedir .. "no_screenshot.png"
end 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 .. retval = retval ..
"label[5.5,1.7;".. fgettext("Mod Information:") .. "]" .. "image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]" ..
"textlist[5.5,2.2;6.2,2.4;description;" "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]) .. "," retval = retval .. core.formspec_escape(descriptionlines[i]) .. ","
end end
if selected_mod.is_modpack then if selected_mod.is_modpack then
retval = retval .. ";0]" .. retval = retval .. ";0]" ..
"button[9.9,4.65;2,1;btn_mod_mgr_rename_modpack;" .. "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") .. "]" .. fgettext("Uninstall Selected Modpack") .. "]"
else else
--show dependencies --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 if toadd_hard == "" and toadd_soft == "" then
retval = retval .. "," .. fgettext("No dependencies.") retval = retval .. "," .. fgettext("No dependencies.")
else else

View File

@ -130,9 +130,8 @@ Mod directory structure
mods mods
|-- modname |-- modname
| |-- depends.txt | |-- mod.conf
| |-- screenshot.png | |-- screenshot.png
| |-- description.txt
| |-- settingtypes.txt | |-- settingtypes.txt
| |-- init.lua | |-- init.lua
| |-- models | |-- models
@ -145,12 +144,32 @@ Mod directory structure
| `-- <custom data> | `-- <custom data>
`-- another `-- another
### modname ### modname
The location of this directory can be fetched by using The location of this directory can be fetched by using
`minetest.get_modpath(modname)`. `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` ### `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. List of mods that have to be loaded before loading this mod.
A single line contains a single modname. 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 to a single modname. This means that if the specified mod
is missing, it does not prevent this mod from being loaded. 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` ### `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. A file containing a description to be shown in the Mods tab of the mainmenu.
### `settingtypes.txt` ### `settingtypes.txt`

View File

@ -111,7 +111,7 @@ core.get_screen_info()
window_height = <current window height> window_height = <current window height>
} }
Games: Packages:
core.get_game(index) core.get_game(index)
^ returns { ^ returns {
id = <id>, 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_games() -> table of all games in upper format (possible in async calls)
core.get_mapgen_names([include_hidden=false]) -> table of map generator algorithms core.get_mapgen_names([include_hidden=false]) -> table of map generator algorithms
registered in the core (possible in async calls) 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: Favorites:
core.get_favorites(location) -> list of favorites (possible in async calls) core.get_favorites(location) -> list of favorites (possible in async calls)

View File

@ -1,2 +0,0 @@
default

View File

@ -0,0 +1,3 @@
name = bucket
description = Minimal bucket to place and pick up liquids
depends = default

View File

@ -0,0 +1,2 @@
name = default
description = Minimal default, adds basic nodes

View File

@ -1,2 +0,0 @@
default
stairs

View File

@ -0,0 +1,3 @@
name = experimental
description = Minimal mod to test features
depends = default, stairs

View File

@ -1,2 +0,0 @@
default

View File

@ -0,0 +1,3 @@
name = give_initial_stuff
description = Gives items to players on join
depends = default

View File

@ -1,2 +0,0 @@
default

View File

@ -0,0 +1,3 @@
name = legacy
description = Aliases allowing support for 0.3.x worlds
depends = default

View File

@ -1 +0,0 @@
default

View File

@ -0,0 +1,3 @@
name = stairs
description = Adds stairs and slabs
depends = default

View File

@ -0,0 +1,2 @@
name = test
description = Adds unit tests for the engine

View File

@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cctype> #include <cctype>
#include <fstream> #include <fstream>
#include <json/json.h> #include <json/json.h>
#include <algorithm>
#include "mods.h" #include "mods.h"
#include "filesys.h" #include "filesys.h"
#include "log.h" #include "log.h"
@ -28,14 +29,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "porting.h" #include "porting.h"
#include "convert_json.h" #include "convert_json.h"
static bool parseDependsLine(std::istream &is, bool parseDependsString(std::string &dep,
std::string &dep, std::set<char> &symbols) std::unordered_set<char> &symbols)
{ {
std::getline(is, dep);
dep = trim(dep); dep = trim(dep);
symbols.clear(); symbols.clear();
size_t pos = dep.size(); 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 // last character is a symbol, not part of the modname
symbols.insert(dep[pos-1]); symbols.insert(dep[pos-1]);
--pos; --pos;
@ -60,28 +60,66 @@ void parseModContents(ModSpec &spec)
// Handle modpacks (defined by containing modpack.txt) // Handle modpacks (defined by containing modpack.txt)
std::ifstream modpack_is((spec.path+DIR_DELIM+"modpack.txt").c_str()); 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 modpack_is.close(); // We don't actually need the file
spec.is_modpack = true; spec.is_modpack = true;
spec.modpack_content = getModsInPath(spec.path, true); spec.modpack_content = getModsInPath(spec.path, true);
// modpacks have no dependencies; they are defined and // modpacks have no dependencies; they are defined and
// tracked separately for each mod in the modpack // tracked separately for each mod in the modpack
}
else{ // not a modpack, parse the dependencies } else {
std::ifstream is((spec.path+DIR_DELIM+"depends.txt").c_str()); // Attempt to load dependencies from mod.conf
while(is.good()){ bool mod_conf_has_depends = false;
std::string dep; if (info.exists("depends")) {
std::set<char> symbols; mod_conf_has_depends = true;
if(parseDependsLine(is, dep, symbols)){ std::string dep = info.get("depends");
if(symbols.count('?') != 0){ dep.erase(std::remove_if(dep.begin(), dep.end(),
spec.optdepends.insert(dep); static_cast<int(*)(int)>(&std::isspace)), dep.end());
} for (const auto &dependency : str_split(dep, ',')) {
else{ spec.depends.insert(dependency);
spec.depends.insert(dep); }
}
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>());
}
} }
} }

View File

@ -37,6 +37,8 @@ struct ModSpec
{ {
std::string name; std::string name;
std::string path; std::string path;
std::string desc;
//if normal mod: //if normal mod:
std::unordered_set<std::string> depends; std::unordered_set<std::string> depends;
std::unordered_set<std::string> optdepends; std::unordered_set<std::string> optdepends;
@ -44,6 +46,7 @@ struct ModSpec
bool part_of_modpack = false; bool part_of_modpack = false;
bool is_modpack = false; bool is_modpack = false;
// if modpack: // if modpack:
std::map<std::string,ModSpec> modpack_content; std::map<std::string,ModSpec> modpack_content;
ModSpec(const std::string &name_ = "", const std::string &path_ = ""): ModSpec(const std::string &name_ = "", const std::string &path_ = ""):

View File

@ -257,56 +257,6 @@ int ModApiMainMenu::l_get_worlds(lua_State *L)
return 1; 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) int ModApiMainMenu::l_get_favorites(lua_State *L)
{ {
@ -477,6 +427,103 @@ int ModApiMainMenu::l_delete_favorite(lua_State *L)
return 0; 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) 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_table_index);
API_FCT(get_worlds); API_FCT(get_worlds);
API_FCT(get_games); API_FCT(get_games);
API_FCT(get_mod_info);
API_FCT(start); API_FCT(start);
API_FCT(close); API_FCT(close);
API_FCT(get_favorites); API_FCT(get_favorites);

View File

@ -71,8 +71,6 @@ private:
static int l_get_worlds(lua_State *L); 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_mapgen_names(lua_State *L);
static int l_get_favorites(lua_State *L); static int l_get_favorites(lua_State *L);
@ -81,6 +79,12 @@ private:
static int l_gettext(lua_State *L); 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 //gui
static int l_show_keys_menu(lua_State *L); static int l_show_keys_menu(lua_State *L);