diff --git a/builtin/mainmenu/content/pkgmgr.lua b/builtin/mainmenu/content/pkgmgr.lua index 318d1e3230..4adbe4207c 100644 --- a/builtin/mainmenu/content/pkgmgr.lua +++ b/builtin/mainmenu/content/pkgmgr.lua @@ -61,13 +61,17 @@ end local function load_texture_packs(txtpath, retval) local list = core.get_dir_list(txtpath, true) - local current_texture_path = core.settings:get("texture_path") + local enabled_packs = pkgmgr.get_enabled_texture_packs() for _, item in ipairs(list) do if item ~= "base" then local path = txtpath .. DIR_DELIM .. item .. DIR_DELIM local conf = Settings(path .. "texture_pack.conf") - local enabled = path == current_texture_path + local order = table.indexof(enabled_packs, path) + if order == -1 then + order = nil + end + local enabled = order ~= nil local title = conf:get("title") or item @@ -75,13 +79,14 @@ local function load_texture_packs(txtpath, retval) retval[#retval + 1] = { name = conf:get("name") or item, title = title, - list_name = enabled and fgettext("$1 (Enabled)", item) or nil, - list_title = enabled and fgettext("$1 (Enabled)", title) or nil, + list_name = enabled and ("(" .. order .. ") " .. item) or nil, + list_title = enabled and ("(" .. order .. ") " .. title) or nil, author = conf:get("author"), release = tonumber(conf:get("release")) or 0, type = "txp", path = path, enabled = enabled, + order = order, } end end @@ -116,6 +121,48 @@ function pkgmgr.get_mods(path, virtual_path, listing) pkgmgr.update_translations(mods) end +-- Must match PATH_DELIM in filesys.h +local PATH_DELIM = PLATFORM == "Windows" and ";" or ":" + +--- Returns the ordered list of enabled texture pack paths, highest priority first. +function pkgmgr.get_enabled_texture_packs() + return (core.settings:get("texture_path") or ""):split(PATH_DELIM) +end + +function pkgmgr.set_enabled_texture_packs(list) + core.settings:set("texture_path", table.concat(list, PATH_DELIM)) +end + +--- Newly enabled packs are appended at the end, lowest priority. +function pkgmgr.set_texture_pack_enabled(path, enabled) + local list = pkgmgr.get_enabled_texture_packs() + + local index = table.indexof(list, path) + if index ~= -1 then + table.remove(list, index) + end + + if enabled then + list[#list + 1] = path + end + + pkgmgr.set_enabled_texture_packs(list) +end + +--- Moves an enabled texture pack by `delta` places in the priority list. +function pkgmgr.move_texture_pack(path, delta) + local list = pkgmgr.get_enabled_texture_packs() + + local index = table.indexof(list, path) + if index == -1 then + return + end + + local new_index = math.max(1, math.min(#list, index + delta)) + table.insert(list, new_index, table.remove(list, index)) + pkgmgr.set_enabled_texture_packs(list) +end + -------------------------------------------------------------------------------- function pkgmgr.reload_texture_packs() local txtpath = core.get_texturepath() @@ -307,7 +354,14 @@ function pkgmgr.render_packagelist(render_list, use_technical_names, with_icon) end end end - elseif v.enabled or v.type == "txp" then + elseif v.type == "txp" then + if v.enabled then + icon = 1 + color = mt_color_green + else + color = mt_color_grey + end + elseif v.enabled then icon = 1 color = mt_color_green end diff --git a/builtin/mainmenu/game_theme.lua b/builtin/mainmenu/game_theme.lua index 30d76cd7a5..b72c929604 100644 --- a/builtin/mainmenu/game_theme.lua +++ b/builtin/mainmenu/game_theme.lua @@ -11,7 +11,8 @@ local COLORS = { -------------------------------------------------------------------------------- function mm_game_theme.init() - mm_game_theme.texturepack = core.settings:get("texture_path") + -- TODO: scan texture packs in order to get game theme images + mm_game_theme.texturepack = pkgmgr.get_enabled_texture_packs()[1] mm_game_theme.gameid = nil diff --git a/builtin/mainmenu/tab_content.lua b/builtin/mainmenu/tab_content.lua index b90b156848..9f0c41573c 100644 --- a/builtin/mainmenu/tab_content.lua +++ b/builtin/mainmenu/tab_content.lua @@ -13,6 +13,8 @@ local function get_content_icons(packages_with_updates) end +local SUBTAB_KEYS = { "games", "mods", "txps" } + local packages_raw, packages local function update_packages() @@ -32,8 +34,25 @@ local function update_packages() element.name == uid end - packages = filterlist.create(get_data, pkgmgr.compare_package, - is_equal, nil, {}) + local category_types = { game = "games", txp = "txps", mod = "mods", modpack = "mods" } + local function filter_by_category(element, category) + return category_types[element.type] == category + end + + packages = filterlist.create(get_data, pkgmgr.compare_package, is_equal, filter_by_category, {}) +end + +-- Enabled packs first ordered by priority, then disabled packs alphabetically +local function sort_resources_list(list) + table.sort(list, function(a, b) + if a.enabled ~= b.enabled then + return a.enabled + end + if a.enabled then + return a.order < b.order + end + return a.title:lower() < b.title:lower() + end) end local function on_change(type) @@ -48,8 +67,23 @@ local function get_formspec(tabview, name, tabdata) update_packages() end - if not tabdata.selected_pkg then - tabdata.selected_pkg = 1 + tabdata.selected_pkg = tabdata.selected_pkg or 1 + tabdata.subtab = tabdata.subtab or 1 + + local subtab_key = SUBTAB_KEYS[tabdata.subtab] + packages:set_filtercriteria(subtab_key) + if subtab_key == "txps" then + sort_resources_list(packages:get_list()) + end + + if tabdata.selected_path then + for i, pkg in ipairs(packages:get_list()) do + if pkg.path == tabdata.selected_path then + tabdata.selected_pkg = i + break + end + end + tabdata.selected_path = nil end local use_technical_names = core.settings:get_bool("show_technical_names") @@ -66,19 +100,29 @@ local function get_formspec(tabview, name, tabdata) end local retval = { - "label[0.4,0.4;", fgettext("Installed Packages:"), "]", + string.format("tabheader[0.4,0.9;6.3,0.6;content_subtab;%s,%s,%s;%i;true;false]", + fgettext("Games"), fgettext("Mods"), fgettext("Texture packs"), tabdata.subtab), "tablecolumns[color;tree;image,align=inline,width=1.5", ",tooltip=", fgettext("Update available?"), ",0=", core.formspec_escape(defaulttexturedir .. "blank.png"), ",4=", core.formspec_escape(defaulttexturedir .. "cdb_update_cropped.png"), ";text]", - "table[0.4,0.8;6.3,4.8;pkglist;", + "table[0.4,1.0;6.3,4.6;pkglist;", 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, "]" } + if subtab_key == "txps" then + local priority_tooltip = fgettext("Enabled texture packs are applied in priority order.") .. + "\n" .. fgettext("If two packs provide the same texture, the one listed first wins.") + table.insert_all(retval, { + "image[6.2,0.4;0.5,0.5;", core.formspec_escape(defaulttexturedir .. "settings_info.png"), "]", + "tooltip[6.2,0.4;0.5,0.5;", priority_tooltip, "]", + }) + end + local selected_pkg if filterlist.size(packages) >= tabdata.selected_pkg then selected_pkg = packages:get_list()[tabdata.selected_pkg] @@ -157,13 +201,27 @@ local function get_formspec(tabview, name, tabdata) if selected_pkg.enabled then table.insert_all(retval, { - "button[7.1,4.7;8,0.9;btn_mod_mgr_disable_txp;", + "button[7.1,4.7;4,0.9;btn_mod_mgr_disable_txp;", fgettext("Disable Texture Pack"), "]" }) + + local enabled_count = #pkgmgr.get_enabled_texture_packs() + if selected_pkg.order > 1 then + table.insert_all(retval, { + "button[11.1,4.7;2,0.9;btn_mod_mgr_txp_move_up;", + fgettext("Move Up"), "]" + }) + end + if selected_pkg.order < enabled_count then + table.insert_all(retval, { + "button[13.1,4.7;2,0.9;btn_mod_mgr_txp_move_down;", + fgettext("Move Down"), "]" + }) + end else table.insert_all(retval, { "button[7.1,4.7;8,0.9;btn_mod_mgr_use_txp;", - fgettext("Use Texture Pack"), "]" + fgettext("Enable Texture Pack"), "]" }) end end @@ -193,13 +251,10 @@ local function get_formspec(tabview, name, tabdata) return table.concat(retval) end -local function handle_doubleclick(pkg) +local function handle_doubleclick(pkg, tabdata) if pkg.type == "txp" then - if core.settings:get("texture_path") == pkg.path then - core.settings:set("texture_path", "") - else - core.settings:set("texture_path", pkg.path) - end + pkgmgr.set_texture_pack_enabled(pkg.path, not pkg.enabled) + tabdata.selected_path = pkg.path packages = nil pkgmgr.reload_texture_packs() @@ -210,11 +265,17 @@ end local function handle_buttons(tabview, fields, tabname, tabdata) + if fields.content_subtab then + tabdata.subtab = tonumber(fields.content_subtab) + tabdata.selected_pkg = 1 + return true + end + if fields.pkglist then local event = core.explode_table_event(fields.pkglist) tabdata.selected_pkg = event.row if event.type == "DCL" then - handle_doubleclick(packages:get_list()[tabdata.selected_pkg]) + handle_doubleclick(packages:get_list()[tabdata.selected_pkg], tabdata) end return true end @@ -259,12 +320,9 @@ local function handle_buttons(tabview, fields, tabname, tabdata) end if fields.btn_mod_mgr_use_txp or fields.btn_mod_mgr_disable_txp then - local txp_path = "" - if fields.btn_mod_mgr_use_txp then - txp_path = packages:get_list()[tabdata.selected_pkg].path - end - - core.settings:set("texture_path", txp_path) + local pkg = packages:get_list()[tabdata.selected_pkg] + pkgmgr.set_texture_pack_enabled(pkg.path, fields.btn_mod_mgr_use_txp ~= nil) + tabdata.selected_path = pkg.path packages = nil pkgmgr.reload_texture_packs() @@ -273,6 +331,16 @@ local function handle_buttons(tabview, fields, tabname, tabdata) return true end + if fields.btn_mod_mgr_txp_move_up or fields.btn_mod_mgr_txp_move_down then + local pkg = packages:get_list()[tabdata.selected_pkg] + local delta = fields.btn_mod_mgr_txp_move_up and -1 or 1 + pkgmgr.move_texture_pack(pkg.path, delta) + tabdata.selected_path = pkg.path + packages = nil + pkgmgr.reload_texture_packs() + return true + end + return false end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 69ed1e85f2..52a422b0cc 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2503,8 +2503,11 @@ serverlist_file (Serverlist file) [client] string favoriteservers.json [*Hide: Temporary Settings] [common] -# Path to texture directory. All textures are first searched from here. -texture_path (Texture path) path +# Ordered list of texture pack paths, separated by ";" on Windows +# and ":" on other platforms. +# Earlier entries take priority: if multiple packs provide the same +# texture, the first one wins. +texture_path (Texture path) string # Enables minimap. enable_minimap (Minimap) bool true diff --git a/src/client/client.cpp b/src/client/client.cpp index 4170db9e55..a770a6ccb1 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -15,7 +15,6 @@ #include "client/particles.h" #include "client/renderingengine.h" #include "client/sound.h" -#include "client/texturepaths.h" #include "client/texturesource.h" #include "camera.h" #include "filesys.h" @@ -1915,7 +1914,8 @@ void Client::afterContentReceived() m_rendering_engine->draw_load_screen(wstrgettext("Initializing nodes..."), guienv, m_tsrc, 0, 70); m_nodedef->updateAliases(m_itemdef); - for (const auto &path : getTextureDirs()) { + // Apply texture overrides from texturepack/override.txt + for (const std::string &path : getTexturePathsReversed()) { TextureOverrideSource override_source(path + DIR_DELIM + "override.txt"); m_nodedef->applyTextureOverrides(override_source.getNodeTileOverrides()); m_itemdef->applyTextureOverrides(override_source.getItemTextureOverrides()); diff --git a/src/client/texturepaths.cpp b/src/client/texturepaths.cpp index e850ccd8db..8b9861b1eb 100644 --- a/src/client/texturepaths.cpp +++ b/src/client/texturepaths.cpp @@ -99,7 +99,8 @@ std::vector getTextureDirs() { std::vector ret = g_texturedirs_cache.get(); if (ret.empty()) { - ret = fs::GetRecursiveDirs(g_settings->get("texture_path")); + for (const std::string &pack_path : str_split(g_settings->get("texture_path"), PATH_DELIM[0])) + fs::GetRecursiveDirs(ret, pack_path); g_texturedirs_cache.set(ret); } return ret; diff --git a/src/server.cpp b/src/server.cpp index a8a295ad3b..4a43deee07 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -538,8 +538,7 @@ void Server::init() m_nodedef->updateAliases(m_itemdef); // Apply texture overrides from texturepack/override.txt - std::vector paths; - fs::GetRecursiveDirs(paths, g_settings->get("texture_path")); + std::vector paths = getTexturePathsReversed(); fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures"); for (const std::string &path : paths) { TextureOverrideSource override_source(path + DIR_DELIM + "override.txt"); diff --git a/src/texture_override.cpp b/src/texture_override.cpp index 95e1d7df30..2824c53881 100644 --- a/src/texture_override.cpp +++ b/src/texture_override.cpp @@ -6,6 +6,7 @@ #include "log.h" #include "filesys.h" +#include "settings.h" #include "util/string.h" #include @@ -133,3 +134,13 @@ std::vector TextureOverrideSource::getNodeTileOverrides() const return found_overrides; } + +std::vector getTexturePathsReversed() +{ + std::vector paths; + // reversed: applyTextureOverrides() so highest priority gets applied last, aka overrides others + std::vector pack_paths = str_split(g_settings->get("texture_path"), PATH_DELIM[0]); + for (auto it = pack_paths.rbegin(); it != pack_paths.rend(); ++it) + fs::GetRecursiveDirs(paths, *it); + return paths; +} diff --git a/src/texture_override.h b/src/texture_override.h index 4e9442a72f..e6733d6415 100644 --- a/src/texture_override.h +++ b/src/texture_override.h @@ -74,3 +74,6 @@ public: private: std::vector m_overrides; }; + +//! Texture pack dirs from the texture_path setting, lowest priority pack first +std::vector getTexturePathsReversed();