From 9d056c46581ac789f47e004f5dc16270a2d21559 Mon Sep 17 00:00:00 2001 From: ProunceDev Date: Thu, 10 Apr 2025 13:56:52 -0500 Subject: [PATCH 1/3] Add a client side mod dialog Using this dialog you have the ability to enable or disable client mods without manually editing the file. --- builtin/mainmenu/content/pkgmgr.lua | 49 ++++++ builtin/mainmenu/dlg_csm.lua | 225 ++++++++++++++++++++++++++++ builtin/mainmenu/init.lua | 1 + builtin/mainmenu/tab_content.lua | 11 +- src/script/scripting_mainmenu.cpp | 2 + 5 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 builtin/mainmenu/dlg_csm.lua diff --git a/builtin/mainmenu/content/pkgmgr.lua b/builtin/mainmenu/content/pkgmgr.lua index 986d80398acf9..b519171ac4c27 100644 --- a/builtin/mainmenu/content/pkgmgr.lua +++ b/builtin/mainmenu/content/pkgmgr.lua @@ -711,6 +711,53 @@ function pkgmgr.preparemodlist(data) return retval end +function pkgmgr.prepareclientmodlist(data) + local retval = {} + + local clientmods = {} + + --read clientmods + local modpath = core.get_clientmodpath() + + if modpath ~= nil and modpath ~= "" then + pkgmgr.get_mods(modpath, "clientmods", clientmods) + end + + for i=1,#clientmods,1 do + clientmods[i].type = "mod" + clientmods[i].loc = "global" + clientmods[i].is_clientside = true + retval[#retval + 1] = clientmods[i] + end + + --read mods configuration + local filename = modpath .. + DIR_DELIM .. "mods.conf" + + local conffile = Settings(filename) + + for key,value in pairs(conffile:to_table()) do + if key:sub(1, 9) == "load_mod_" then + key = key:sub(10) + local element = nil + for i=1,#retval,1 do + if retval[i].name == key and + not retval[i].is_modpack then + element = retval[i] + break + end + end + if element ~= nil then + element.enabled = value ~= "false" and value ~= "nil" and value + else + core.log("info", "Clientmod: " .. key .. " " .. dump(value) .. " but not found") + end + end + end + + return retval +end + function pkgmgr.compare_package(a, b) return a and b and a.name == b.name and a.path == b.path end @@ -749,6 +796,8 @@ function pkgmgr.reload_global_mods() end pkgmgr.global_mods = filterlist.create(pkgmgr.preparemodlist, pkgmgr.comparemod, is_equal, nil, {}) + pkgmgr.clientmods = filterlist.create(pkgmgr.prepareclientmodlist, + pkgmgr.comparemod, is_equal, nil, {}) pkgmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list) pkgmgr.global_mods:set_sortmode("alphabetic") end diff --git a/builtin/mainmenu/dlg_csm.lua b/builtin/mainmenu/dlg_csm.lua new file mode 100644 index 0000000000000..2d5d4937a016f --- /dev/null +++ b/builtin/mainmenu/dlg_csm.lua @@ -0,0 +1,225 @@ +--Minetest +--Copyright (C) 2025 ProunceDev +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +local packages_raw, packages, tab_selected_pkg + +local function modname_valid(name) + return not name:find("[^a-z0-9_]") +end + +local function get_formspec(dlgview, name, tabdata) + if pkgmgr.clientmods == nil then + pkgmgr.reload_global_mods() + end + + if packages == nil then + packages_raw = {} + table.insert_all(packages_raw, pkgmgr.clientmods:get_list()) + + local function get_data() + return packages_raw + end + + local function is_equal(element, uid) --uid match + return (element.type == "game" and element.id == uid) or + element.name == uid + end + + packages = filterlist.create(get_data, pkgmgr.compare_package, is_equal, nil, {}) + + local filename = core.get_clientmodpath() .. DIR_DELIM .. "mods.conf" + local conffile = Settings(filename) + local mods = conffile:to_table() + + for i = 1, #packages_raw do + local mod = packages_raw[i] + if mod.is_clientside then + if modname_valid(mod.name) then + conffile:set("load_mod_" .. mod.name, + mod.enabled and "true" or "false") + elseif mod.enabled then + gamedata.errormessage = fgettext_ne("Failed to enable clientmod" .. + " \"$1\" as it contains disallowed characters. " .. + "Only characters [a-z0-9_] are allowed.", + mod.name) + end + mods["load_mod_" .. mod.name] = nil + end + end + + -- Remove mods that are not present anymore + for key in pairs(mods) do + if key:sub(1, 9) == "load_mod_" then + conffile:remove(key) + end + end + if not conffile:write() then + core.log("error", "Failed to write clientmod config file") + end + + local filename = core.get_clientmodpath() .. DIR_DELIM .. "mods.conf" + + local conffile = Settings(filename) + local mods = conffile:to_table() + + for i = 1, #packages_raw do + local mod = packages_raw[i] + if mod.is_clientside then + if modname_valid(mod.name) then + conffile:set("load_mod_" .. mod.name, + mod.enabled and "true" or "false") + elseif mod.enabled then + gamedata.errormessage = fgettext_ne("Failed to enable clientmo" .. + "d \"$1\" as it contains disallowed characters. " .. + "Only characters [a-z0-9_] are allowed.", + mod.name) + end + mods["load_mod_" .. mod.name] = nil + end + end + + -- Remove mods that are not present anymore + for key in pairs(mods) do + if key:sub(1, 9) == "load_mod_" then + conffile:remove(key) + end + end + + if not conffile:write() then + core.log("error", "Failed to write clientmod config file") + end + end + + if tab_selected_pkg == nil then + tab_selected_pkg = 1 + end + + local use_technical_names = core.settings:get_bool("show_technical_names") + + + local fs = { + "size[6.5,7]", + "label[0.25,-0.1;", fgettext("Installed Client Mods"), "]", + "tablecolumns[color;tree;text]", + "table[0.15,0.5;6,5.7;pkglist;", pkgmgr.render_packagelist(packages, use_technical_names), ";", tab_selected_pkg, "]", + "button[0.15,5.5;3,3;back;", fgettext("Back"), "]" + } + + + local selected_pkg + if filterlist.size(packages) >= tab_selected_pkg then + selected_pkg = packages:get_list()[tab_selected_pkg] + end + + if selected_pkg ~= nil then + if selected_pkg.type == "mod" then + if selected_pkg.is_clientside then + if selected_pkg.enabled then + table.insert_all(fs, { + "button[3.36,5.5;3,3;btn_csm_mgr_disable_mod;", fgettext("Disable"), "]" + }) + else + table.insert_all(fs, { + "button[3.36,5.5;3,3;btn_csm_mgr_enable_mod;", fgettext("Enable"), "]" + }) + end + end + end + end + return table.concat(fs, "") +end + +-------------------------------------------------------------------------------- +local function handle_doubleclick(pkg, pkg_name) + pkgmgr.enable_mod({data = {list = packages, selected_mod = pkg_name}}) + packages = nil +end + +-------------------------------------------------------------------------------- +local function handle_buttons(dlgview, fields, tabname, tabdata) + if fields["pkglist"] ~= nil then + local event = core.explode_table_event(fields["pkglist"]) + tab_selected_pkg = event.row + if event.type == "DCL" then + handle_doubleclick(packages:get_list()[tab_selected_pkg], tab_selected_pkg) + end + return true + end + + if fields.btn_csm_mgr_mp_enable ~= nil or fields.btn_csm_mgr_mp_disable ~= nil then + pkgmgr.enable_mod({data = {list = packages, selected_mod = tab_selected_pkg}}, fields.btn_csm_mgr_mp_enable ~= nil) + packages = nil + return true + end + + if fields.btn_csm_mgr_enable_mod ~= nil or fields.btn_csm_mgr_disable_mod ~= nil then + pkgmgr.enable_mod({data = {list = packages, selected_mod = tab_selected_pkg}}, fields.btn_csm_mgr_enable_mod ~= nil) + packages = nil + return true + end + + if fields.back then + dlgview:delete() + return true + end + + if fields["btn_csm_mgr_rename_modpack"] ~= nil then + local mod = packages:get_list()[tab_selected_pkg] + local dlg_renamemp = create_rename_modpack_dlg(mod) + dlg_renamemp:set_parent(dlgview) + dlgview:hide() + dlg_renamemp:show() + packages = nil + return true + end + + if fields["btn_csm_mgr_delete_mod"] ~= nil then + local mod = packages:get_list()[tab_selected_pkg] + local dlg_delmod = create_delete_content_dlg(mod) + dlg_delmod:set_parent(dlgview) + dlgview:hide() + dlg_delmod:show() + packages = nil + return true + end + + if fields.btn_csm_mgr_use_txp or fields.btn_csm_mgr_disable_txp then + local txp_path = "" + if fields.btn_csm_mgr_use_txp then + txp_path = packages:get_list()[tab_selected_pkg].path + end + + core.settings:set("texture_path", txp_path) + packages = nil + + mm_game_theme.init() + mm_game_theme.reset() + return true + end + + return false +end + +function create_csm_dlg() + mm_game_theme.set_engine() + return dialog_create( + "csm", + get_formspec, + handle_buttons, + pkgmgr.update_gamelist + ) +end diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index ec33f33b34b2e..31b6385e14096 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -57,6 +57,7 @@ dofile(menupath .. DIR_DELIM .. "dlg_version_info.lua") dofile(menupath .. DIR_DELIM .. "dlg_reinstall_mtg.lua") dofile(menupath .. DIR_DELIM .. "dlg_clients_list.lua") dofile(menupath .. DIR_DELIM .. "dlg_server_list_mods.lua") +dofile(menupath .. DIR_DELIM .. "dlg_csm.lua") local tabs = { content = dofile(menupath .. DIR_DELIM .. "tab_content.lua"), diff --git a/builtin/mainmenu/tab_content.lua b/builtin/mainmenu/tab_content.lua index cd384c9055c6a..975acbbf7d0b8 100644 --- a/builtin/mainmenu/tab_content.lua +++ b/builtin/mainmenu/tab_content.lua @@ -88,7 +88,8 @@ local function get_formspec(tabview, name, tabdata) pkgmgr.render_packagelist(packages, use_technical_names, update_icons), ";", tabdata.selected_pkg, "]", - "button[0.4,5.8;6.3,0.9;btn_contentdb;", contentdb_label, "]" + "button[0.4,5.8;3.1,0.9;btn_contentdb;", contentdb_label, "]", + "button[3.6,5.8;3.1,0.9;btn_csm;", fgettext("Client side mods"), "]" } local selected_pkg @@ -229,6 +230,14 @@ local function handle_buttons(tabview, fields, tabname, tabdata) return true end + if fields.btn_csm then + local dlg = create_csm_dlg() + dlg:set_parent(tabview) + tabview:hide() + dlg:show() + return true + end + if fields.btn_mod_mgr_rename_modpack then local mod = packages:get_list()[tabdata.selected_pkg] local dlg_renamemp = create_rename_modpack_dlg(mod) diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index d0ab5d374265b..826cac29f9c4e 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -95,6 +95,8 @@ bool MainMenuScripting::mayModifyPath(const std::string &path) return true; if (fs::PathStartsWith(path, path_user + DIR_DELIM "worlds")) return true; + if (fs::PathStartsWith(path, path_user + DIR_DELIM "clientmods")) + return true; if (fs::PathStartsWith(path, fs::AbsolutePathPartial(porting::path_cache))) return true; From b076f03e8ceaa37125229abf89fa1749f834d18d Mon Sep 17 00:00:00 2001 From: ProunceDev Date: Thu, 10 Apr 2025 14:14:07 -0500 Subject: [PATCH 2/3] Fix double paste and whitespace --- builtin/mainmenu/dlg_csm.lua | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/builtin/mainmenu/dlg_csm.lua b/builtin/mainmenu/dlg_csm.lua index 2d5d4937a016f..e433d75edf5e6 100644 --- a/builtin/mainmenu/dlg_csm.lua +++ b/builtin/mainmenu/dlg_csm.lua @@ -44,7 +44,7 @@ local function get_formspec(dlgview, name, tabdata) local filename = core.get_clientmodpath() .. DIR_DELIM .. "mods.conf" local conffile = Settings(filename) local mods = conffile:to_table() - + for i = 1, #packages_raw do local mod = packages_raw[i] if mod.is_clientside then @@ -60,37 +60,6 @@ local function get_formspec(dlgview, name, tabdata) mods["load_mod_" .. mod.name] = nil end end - - -- Remove mods that are not present anymore - for key in pairs(mods) do - if key:sub(1, 9) == "load_mod_" then - conffile:remove(key) - end - end - if not conffile:write() then - core.log("error", "Failed to write clientmod config file") - end - - local filename = core.get_clientmodpath() .. DIR_DELIM .. "mods.conf" - - local conffile = Settings(filename) - local mods = conffile:to_table() - - for i = 1, #packages_raw do - local mod = packages_raw[i] - if mod.is_clientside then - if modname_valid(mod.name) then - conffile:set("load_mod_" .. mod.name, - mod.enabled and "true" or "false") - elseif mod.enabled then - gamedata.errormessage = fgettext_ne("Failed to enable clientmo" .. - "d \"$1\" as it contains disallowed characters. " .. - "Only characters [a-z0-9_] are allowed.", - mod.name) - end - mods["load_mod_" .. mod.name] = nil - end - end -- Remove mods that are not present anymore for key in pairs(mods) do @@ -98,7 +67,6 @@ local function get_formspec(dlgview, name, tabdata) conffile:remove(key) end end - if not conffile:write() then core.log("error", "Failed to write clientmod config file") end From fe5a90b962cdc5e2d63344a1dc8de019d9b47584 Mon Sep 17 00:00:00 2001 From: ProunceDev Date: Thu, 10 Apr 2025 14:35:41 -0500 Subject: [PATCH 3/3] Fix Minetest instead of Luanti in copyright --- builtin/mainmenu/dlg_csm.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/mainmenu/dlg_csm.lua b/builtin/mainmenu/dlg_csm.lua index e433d75edf5e6..9725b447aa4e0 100644 --- a/builtin/mainmenu/dlg_csm.lua +++ b/builtin/mainmenu/dlg_csm.lua @@ -1,4 +1,4 @@ ---Minetest +--Luanti --Copyright (C) 2025 ProunceDev -- --This program is free software; you can redistribute it and/or modify