aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrubenwardy <rw@rubenwardy.com>2022-05-07 16:45:17 +0100
committerrubenwardy <rw@rubenwardy.com>2022-07-14 22:12:54 +0100
commit9f41b4f72d45b0ad1f9de2dfc28e426a2d05f137 (patch)
tree677a2009671dead812879bee5f9e154670e98e2f
parent06de82fd86678e0a1c260c67792c5cd192863edd (diff)
downloadminetest-9f41b4f72d45b0ad1f9de2dfc28e426a2d05f137.tar.gz
minetest-9f41b4f72d45b0ad1f9de2dfc28e426a2d05f137.tar.bz2
minetest-9f41b4f72d45b0ad1f9de2dfc28e426a2d05f137.zip
Add check_mod_configuration to main menu
-rw-r--r--builtin/mainmenu/dlg_config_world.lua89
-rw-r--r--builtin/mainmenu/init.lua1
-rw-r--r--builtin/mainmenu/pkgmgr.lua55
-rw-r--r--doc/menu_lua_api.txt11
-rw-r--r--src/script/common/c_content.cpp32
-rw-r--r--src/script/common/c_content.h3
-rw-r--r--src/script/lua_api/l_mainmenu.cpp97
-rw-r--r--src/script/lua_api/l_mainmenu.h2
-rw-r--r--textures/base/pack/checkbox_16_white.pngbin0 -> 173 bytes
-rw-r--r--textures/base/pack/error_icon_orange.pngbin0 -> 133 bytes
-rw-r--r--textures/base/pack/error_icon_red.pngbin0 -> 133 bytes
11 files changed, 277 insertions, 13 deletions
diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua
index f73256612..e76e10ef7 100644
--- a/builtin/mainmenu/dlg_config_world.lua
+++ b/builtin/mainmenu/dlg_config_world.lua
@@ -61,12 +61,68 @@ local function init_data(data)
data.list:set_sortmode("alphabetic")
end
+
+-- Returns errors errors and a list of all enabled mods (inc. game and world mods)
+--
+-- `with_errors` is a table from mod virtual path to `{ type = "error" | "warning" }`.
+-- `enabled_mods_by_name` is a table from mod virtual path to `true`.
+--
+-- @param world_path Path to the world
+-- @param all_mods List of mods, with `enabled` property.
+-- @returns with_errors, enabled_mods_by_name
+local function check_mod_configuration(world_path, all_mods)
+ -- Build up lookup tables for enabled mods and all mods by vpath
+ local enabled_mod_paths = {}
+ local all_mods_by_vpath = {}
+ for _, mod in ipairs(all_mods) do
+ if mod.type == "mod" then
+ all_mods_by_vpath[mod.virtual_path] = mod
+ end
+ if mod.enabled then
+ enabled_mod_paths[mod.virtual_path] = mod.path
+ end
+ end
+
+ -- Use the engine's mod configuration code to resolve dependencies and return any errors
+ local config_status = core.check_mod_configuration(world_path, enabled_mod_paths)
+
+ -- Build the list of enabled mod virtual paths
+ local enabled_mods_by_name = {}
+ for _, mod in ipairs(config_status.satisfied_mods) do
+ assert(mod.virtual_path ~= "")
+ enabled_mods_by_name[mod.name] = all_mods_by_vpath[mod.virtual_path] or mod
+ end
+ for _, mod in ipairs(config_status.unsatisfied_mods) do
+ assert(mod.virtual_path ~= "")
+ enabled_mods_by_name[mod.name] = all_mods_by_vpath[mod.virtual_path] or mod
+ end
+
+ -- Build the table of errors
+ local with_error = {}
+ for _, mod in ipairs(config_status.unsatisfied_mods) do
+ local error = { type = "warning" }
+ with_error[mod.virtual_path] = error
+
+ for _, depname in ipairs(mod.unsatisfied_depends) do
+ if not enabled_mods_by_name[depname] then
+ error.type = "error"
+ break
+ end
+ end
+ end
+
+ return with_error, enabled_mods_by_name
+end
+
local function get_formspec(data)
if not data.list then
init_data(data)
end
- local mod = data.list:get_list()[data.selected_mod] or {name = ""}
+ local all_mods = data.list:get_list()
+ local with_error, enabled_mods_by_name = check_mod_configuration(data.worldspec.path, all_mods)
+
+ local mod = all_mods[data.selected_mod] or {name = ""}
local retval =
"size[11.5,7.5,true]" ..
@@ -87,6 +143,29 @@ local function get_formspec(data)
"textarea[0.25,0.7;5.75,7.2;;" .. info .. ";]"
else
local hard_deps, soft_deps = pkgmgr.get_dependencies(mod.path)
+
+ -- Add error messages to dep lists
+ if mod.enabled or mod.is_game_content then
+ for i, dep_name in ipairs(hard_deps) do
+ local dep = enabled_mods_by_name[dep_name]
+ if not dep then
+ hard_deps[i] = mt_color_red .. dep_name .. " " .. fgettext("(Unsatisfied)")
+ elseif with_error[dep.virtual_path] then
+ hard_deps[i] = mt_color_orange .. dep_name .. " " .. fgettext("(Enabled, has error)")
+ else
+ hard_deps[i] = mt_color_green .. dep_name
+ end
+ end
+ for i, dep_name in ipairs(soft_deps) do
+ local dep = enabled_mods_by_name[dep_name]
+ if dep and with_error[dep.virtual_path] then
+ soft_deps[i] = mt_color_orange .. dep_name .. " " .. fgettext("(Enabled, has error)")
+ elseif dep then
+ soft_deps[i] = mt_color_green .. dep_name
+ end
+ end
+ end
+
local hard_deps_str = table.concat(hard_deps, ",")
local soft_deps_str = table.concat(soft_deps, ",")
@@ -138,7 +217,6 @@ local function get_formspec(data)
if mod.name ~= "" and not mod.is_game_content then
if mod.is_modpack then
-
if pkgmgr.is_modpack_entirely_enabled(data, mod.name) then
retval = retval ..
"button[5.5,0.125;3,0.5;btn_mp_disable;" ..
@@ -167,9 +245,12 @@ local function get_formspec(data)
local use_technical_names = core.settings:get_bool("show_technical_names")
return retval ..
- "tablecolumns[color;tree;text]" ..
+ "tablecolumns[color;tree;image,align=inline,width=1.5,0=" .. core.formspec_escape(defaulttexturedir .. "blank.png") ..
+ ",1=" .. core.formspec_escape(defaulttexturedir .. "checkbox_16_white.png") ..
+ ",2=" .. core.formspec_escape(defaulttexturedir .. "error_icon_orange.png") ..
+ ",3=" .. core.formspec_escape(defaulttexturedir .. "error_icon_red.png") .. ";text]" ..
"table[5.5,0.75;5.75,6;world_config_modlist;" ..
- pkgmgr.render_packagelist(data.list, use_technical_names) .. ";" .. data.selected_mod .."]"
+ pkgmgr.render_packagelist(data.list, use_technical_names, with_error) .. ";" .. data.selected_mod .."]"
end
local function handle_buttons(this, fields)
diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua
index d073f5a57..f890765fa 100644
--- a/builtin/mainmenu/init.lua
+++ b/builtin/mainmenu/init.lua
@@ -21,6 +21,7 @@ mt_color_lightblue = "#99CCFF"
mt_color_green = "#72FF63"
mt_color_dark_green = "#25C191"
mt_color_orange = "#FF8800"
+mt_color_red = "#FF3300"
local menupath = core.get_mainmenu_path()
local basepath = core.get_builtin_path()
diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua
index b2f3243c4..32a65fd08 100644
--- a/builtin/mainmenu/pkgmgr.lua
+++ b/builtin/mainmenu/pkgmgr.lua
@@ -337,7 +337,7 @@ function pkgmgr.identify_modname(modpath,filename)
return nil
end
--------------------------------------------------------------------------------
-function pkgmgr.render_packagelist(render_list, use_technical_names)
+function pkgmgr.render_packagelist(render_list, use_technical_names, with_error)
if not render_list then
if not pkgmgr.global_mods then
pkgmgr.refresh_globals()
@@ -349,24 +349,59 @@ function pkgmgr.render_packagelist(render_list, use_technical_names)
local retval = {}
for i, v in ipairs(list) do
local color = ""
+ local icon = 0
+ local error = with_error and with_error[v.virtual_path]
+ local function update_error(val)
+ if val and (not error or (error.type == "warning" and val.type == "error")) then
+ error = val
+ end
+ end
+
if v.is_modpack then
local rawlist = render_list:get_raw_list()
color = mt_color_dark_green
- for j = 1, #rawlist, 1 do
- if rawlist[j].modpack == list[i].name and
- not rawlist[j].enabled then
- -- Modpack not entirely enabled so showing as grey
- color = mt_color_grey
- break
+ for j = 1, #rawlist do
+ if rawlist[j].modpack == list[i].name then
+ if with_error then
+ update_error(with_error[rawlist[j].virtual_path])
+ end
+
+ if rawlist[j].enabled then
+ icon = 1
+ else
+ -- Modpack not entirely enabled so showing as grey
+ color = mt_color_grey
+ end
end
end
elseif v.is_game_content or v.type == "game" then
+ icon = 1
color = mt_color_blue
+
+ local rawlist = render_list:get_raw_list()
+ if v.type == "game" and with_error then
+ for j = 1, #rawlist do
+ if rawlist[j].is_game_content then
+ update_error(with_error[rawlist[j].virtual_path])
+ end
+ end
+ end
elseif v.enabled or v.type == "txp" then
+ icon = 1
color = mt_color_green
end
+ if error then
+ if error.type == "warning" then
+ color = mt_color_orange
+ icon = 2
+ else
+ color = mt_color_red
+ icon = 3
+ end
+ end
+
retval[#retval + 1] = color
if v.modpack ~= nil or v.loc == "game" then
retval[#retval + 1] = "1"
@@ -374,6 +409,10 @@ function pkgmgr.render_packagelist(render_list, use_technical_names)
retval[#retval + 1] = "0"
end
+ if with_error then
+ retval[#retval + 1] = icon
+ end
+
if use_technical_names then
retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
else
@@ -503,7 +542,7 @@ function pkgmgr.enable_mod(this, toset)
if not mod_to_enable then
core.log("warning", "Mod dependency \"" .. name ..
"\" not found!")
- else
+ elseif not mod_to_enable.is_game_content then
if not mod_to_enable.enabled then
mod_to_enable.enabled = true
toggled_mods[#toggled_mods+1] = mod_to_enable.name
diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt
index 63e229135..4d495f21e 100644
--- a/doc/menu_lua_api.txt
+++ b/doc/menu_lua_api.txt
@@ -276,7 +276,18 @@ Package - content which is downloadable from the content db, may or may not be i
depends = {"mod", "names"}, -- mods only
optional_depends = {"mod", "names"}, -- mods only
}
+* core.check_mod_configuration(world_path, mod_paths)
+ * Checks whether configuration is valid.
+ * `world_path`: path to the world
+ * `mod_paths`: list of enabled mod paths
+ * returns:
+ {
+ is_consistent = true, -- true is consistent, false otherwise
+ unsatisfied_mods = {}, -- list of mod specs
+ satisfied_mods = {}, -- list of mod specs
+ error_message = "", -- message or nil
+ }
Logging
-------
diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp
index 10670a60a..166980025 100644
--- a/src/script/common/c_content.cpp
+++ b/src/script/common/c_content.cpp
@@ -2143,3 +2143,35 @@ void push_collision_move_result(lua_State *L, const collisionMoveResult &res)
lua_setfield(L, -2, "collisions");
/**/
}
+
+
+void push_mod_spec(lua_State *L, const ModSpec &spec, bool include_unsatisfied)
+{
+ lua_newtable(L);
+
+ lua_pushstring(L, spec.name.c_str());
+ lua_setfield(L, -2, "name");
+
+ lua_pushstring(L, spec.author.c_str());
+ lua_setfield(L, -2, "author");
+
+ lua_pushinteger(L, spec.release);
+ lua_setfield(L, -2, "release");
+
+ lua_pushstring(L, spec.desc.c_str());
+ lua_setfield(L, -2, "description");
+
+ lua_pushstring(L, spec.path.c_str());
+ lua_setfield(L, -2, "path");
+
+ lua_pushstring(L, spec.virtual_path.c_str());
+ lua_setfield(L, -2, "virtual_path");
+
+ lua_newtable(L);
+ int i = 1;
+ for (const auto &dep : spec.unsatisfied_depends) {
+ lua_pushstring(L, dep.c_str());
+ lua_rawseti(L, -2, i++);
+ }
+ lua_setfield(L, -2, "unsatisfied_depends");
+}
diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h
index 06f80328a..ade3e4c1e 100644
--- a/src/script/common/c_content.h
+++ b/src/script/common/c_content.h
@@ -42,6 +42,7 @@ extern "C" {
// We do a explicit path include because by default c_content.h include src/client/hud.h
// prior to the src/hud.h, which is not good on server only build
#include "../../hud.h"
+#include "content/mods.h"
namespace Json { class Value; }
@@ -204,3 +205,5 @@ void push_hud_element (lua_State *L, HudElement *elem);
bool read_hud_change (lua_State *L, HudElementStat &stat, HudElement *elem, void **value);
void push_collision_move_result(lua_State *L, const collisionMoveResult &res);
+
+void push_mod_spec(lua_State *L, const ModSpec &spec, bool include_unsatisfied);
diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp
index 4a847ed6d..cf4a057e1 100644
--- a/src/script/lua_api/l_mainmenu.cpp
+++ b/src/script/lua_api/l_mainmenu.cpp
@@ -38,7 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/client.h"
#include "client/renderingengine.h"
#include "network/networkprotocol.h"
-
+#include "content/mod_configuration.h"
/******************************************************************************/
std::string ModApiMainMenu::getTextData(lua_State *L, std::string name)
@@ -410,6 +410,100 @@ int ModApiMainMenu::l_get_content_info(lua_State *L)
}
/******************************************************************************/
+int ModApiMainMenu::l_check_mod_configuration(lua_State *L)
+{
+ std::string worldpath = luaL_checkstring(L, 1);
+
+ ModConfiguration modmgr;
+
+ // Add all game mods
+ SubgameSpec gamespec = findWorldSubgame(worldpath);
+ modmgr.addGameMods(gamespec);
+ modmgr.addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods");
+
+ // Add user-configured mods
+ std::vector<ModSpec> modSpecs;
+
+ luaL_checktype(L, 2, LUA_TTABLE);
+
+ lua_pushnil(L);
+ while (lua_next(L, 2)) {
+ // Ignore non-string keys
+ if (lua_type(L, -2) != LUA_TSTRING) {
+ throw LuaError(
+ "Unexpected non-string key in table passed to "
+ "core.check_mod_configuration");
+ }
+
+ std::string modpath = luaL_checkstring(L, -1);
+ lua_pop(L, 1);
+ std::string virtual_path = lua_tostring(L, -1);
+
+ modSpecs.emplace_back();
+ ModSpec &spec = modSpecs.back();
+ spec.name = fs::GetFilenameFromPath(modpath.c_str());
+ spec.path = modpath;
+ spec.virtual_path = virtual_path;
+ if (!parseModContents(spec)) {
+ throw LuaError("Not a mod!");
+ }
+ }
+
+ modmgr.addMods(modSpecs);
+ try {
+ modmgr.checkConflictsAndDeps();
+ } catch (const ModError &err) {
+ errorstream << err.what() << std::endl;
+
+ lua_newtable(L);
+
+ lua_pushboolean(L, false);
+ lua_setfield(L, -2, "is_consistent");
+
+ lua_newtable(L);
+ lua_setfield(L, -2, "unsatisfied_mods");
+
+ lua_newtable(L);
+ lua_setfield(L, -2, "satisfied_mods");
+
+ lua_pushstring(L, err.what());
+ lua_setfield(L, -2, "error_message");
+ return 1;
+ }
+
+
+ lua_newtable(L);
+
+ lua_pushboolean(L, modmgr.isConsistent());
+ lua_setfield(L, -2, "is_consistent");
+
+ lua_newtable(L);
+ int top = lua_gettop(L);
+ unsigned int index = 1;
+ for (const auto &spec : modmgr.getUnsatisfiedMods()) {
+ lua_pushnumber(L, index);
+ push_mod_spec(L, spec, true);
+ lua_settable(L, top);
+ index++;
+ }
+
+ lua_setfield(L, -2, "unsatisfied_mods");
+
+ lua_newtable(L);
+ top = lua_gettop(L);
+ index = 1;
+ for (const auto &spec : modmgr.getMods()) {
+ lua_pushnumber(L, index);
+ push_mod_spec(L, spec, false);
+ lua_settable(L, top);
+ index++;
+ }
+ lua_setfield(L, -2, "satisfied_mods");
+
+ return 1;
+}
+
+/******************************************************************************/
int ModApiMainMenu::l_show_keys_menu(lua_State *L)
{
GUIEngine* engine = getGuiEngine(L);
@@ -921,6 +1015,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(get_worlds);
API_FCT(get_games);
API_FCT(get_content_info);
+ API_FCT(check_mod_configuration);
API_FCT(start);
API_FCT(close);
API_FCT(show_keys_menu);
diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h
index 6ceff6dd7..9dc40c7f4 100644
--- a/src/script/lua_api/l_mainmenu.h
+++ b/src/script/lua_api/l_mainmenu.h
@@ -82,6 +82,8 @@ private:
static int l_get_content_info(lua_State *L);
+ static int l_check_mod_configuration(lua_State *L);
+
//gui
static int l_show_keys_menu(lua_State *L);
diff --git a/textures/base/pack/checkbox_16_white.png b/textures/base/pack/checkbox_16_white.png
new file mode 100644
index 000000000..0cf0f3e65
--- /dev/null
+++ b/textures/base/pack/checkbox_16_white.png
Binary files differ
diff --git a/textures/base/pack/error_icon_orange.png b/textures/base/pack/error_icon_orange.png
new file mode 100644
index 000000000..1f1586f21
--- /dev/null
+++ b/textures/base/pack/error_icon_orange.png
Binary files differ
diff --git a/textures/base/pack/error_icon_red.png b/textures/base/pack/error_icon_red.png
new file mode 100644
index 000000000..1f5bafbf4
--- /dev/null
+++ b/textures/base/pack/error_icon_red.png
Binary files differ