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
--------------------------------------------------------------------------------
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
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;" ..
.. 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
mods
|-- modname
- | |-- depends.txt
+ | |-- mod.conf
| |-- screenshot.png
- | |-- description.txt
| |-- settingtypes.txt
| |-- init.lua
| |-- models
| `-- <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.
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`
window_height = <current window height>
}
-Games:
+Packages:
core.get_game(index)
^ returns {
id = <id>,
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)
--- /dev/null
+name = bucket
+description = Minimal bucket to place and pick up liquids
+depends = default
--- /dev/null
+name = default
+description = Minimal default, adds basic nodes
+++ /dev/null
-default
-stairs
--- /dev/null
+name = experimental
+description = Minimal mod to test features
+depends = default, stairs
--- /dev/null
+name = give_initial_stuff
+description = Gives items to players on join
+depends = default
--- /dev/null
+name = legacy
+description = Aliases allowing support for 0.3.x worlds
+depends = default
--- /dev/null
+name = stairs
+description = Adds stairs and slabs
+depends = default
--- /dev/null
+name = test
+description = Adds unit tests for the engine
#include <cctype>
#include <fstream>
#include <json/json.h>
+#include <algorithm>
#include "mods.h"
#include "filesys.h"
#include "log.h"
#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;
// 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>());
+ }
}
}
{
std::string name;
std::string path;
+ std::string desc;
+
//if normal mod:
std::unordered_set<std::string> depends;
std::unordered_set<std::string> optdepends;
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_ = ""):
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)
{
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)
{
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);
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);
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);