Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use virtual paths to specify exact mod to enable #11784

Merged
merged 7 commits into from Jan 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 22 additions & 3 deletions builtin/mainmenu/dlg_config_world.lua
Expand Up @@ -205,14 +205,19 @@ local function handle_buttons(this, fields)
local mods = worldfile:to_table()

local rawlist = this.data.list:get_raw_list()
local was_set = {}

for i = 1, #rawlist do
local mod = rawlist[i]
if not mod.is_modpack and
not mod.is_game_content then
if modname_valid(mod.name) then
worldfile:set("load_mod_" .. mod.name,
mod.enabled and "true" or "false")
if mod.enabled then
worldfile:set("load_mod_" .. mod.name, mod.virtual_path)
was_set[mod.name] = true
elseif not was_set[mod.name] then
worldfile:set("load_mod_" .. mod.name, "false")
rubenwardy marked this conversation as resolved.
Show resolved Hide resolved
end
elseif mod.enabled then
gamedata.errormessage = fgettext_ne("Failed to enable mo" ..
"d \"$1\" as it contains disallowed characters. " ..
Expand Down Expand Up @@ -256,12 +261,26 @@ local function handle_buttons(this, fields)
if fields.btn_enable_all_mods then
local list = this.data.list:get_raw_list()

-- When multiple copies of a mod are installed, we need to avoid enabling multiple of them
-- at a time. So lets first collect all the enabled mods, and then use this to exclude
-- multiple enables.

local was_enabled = {}
for i = 1, #list do
if not list[i].is_game_content
and not list[i].is_modpack then
and not list[i].is_modpack and list[i].enabled then
was_enabled[list[i].name] = true
end
end

for i = 1, #list do
if not list[i].is_game_content and not list[i].is_modpack and
not was_enabled[list[i].name] then
list[i].enabled = true
was_enabled[list[i].name] = true
end
end

enabled_all = true
return true
end
Expand Down
2 changes: 1 addition & 1 deletion builtin/mainmenu/dlg_settings_advanced.lua
Expand Up @@ -378,7 +378,7 @@ local function parse_config_file(read_all, parse_mods)
-- Parse mods
local mods_category_initialized = false
local mods = {}
get_mods(core.get_modpath(), mods)
get_mods(core.get_modpath(), "mods", mods)
for _, mod in ipairs(mods) do
local path = mod.path .. DIR_DELIM .. FILENAME
local file = io.open(path, "r")
Expand Down
72 changes: 50 additions & 22 deletions builtin/mainmenu/pkgmgr.lua
Expand Up @@ -100,12 +100,13 @@ local function load_texture_packs(txtpath, retval)
end
end

function get_mods(path,retval,modpack)
function get_mods(path, virtual_path, retval, modpack)
local mods = core.get_dir_list(path, true)

for _, name in ipairs(mods) do
if name:sub(1, 1) ~= "." then
local prefix = path .. DIR_DELIM .. name
local mod_path = path .. DIR_DELIM .. name
local mod_virtual_path = virtual_path .. "/" .. name
local toadd = {
dir_name = name,
parent_dir = path,
Expand All @@ -114,18 +115,18 @@ function get_mods(path,retval,modpack)

-- Get config file
local mod_conf
local modpack_conf = io.open(prefix .. DIR_DELIM .. "modpack.conf")
local modpack_conf = io.open(mod_path .. DIR_DELIM .. "modpack.conf")
if modpack_conf then
toadd.is_modpack = true
modpack_conf:close()

mod_conf = Settings(prefix .. DIR_DELIM .. "modpack.conf"):to_table()
mod_conf = Settings(mod_path .. DIR_DELIM .. "modpack.conf"):to_table()
if mod_conf.name then
name = mod_conf.name
toadd.is_name_explicit = true
end
else
mod_conf = Settings(prefix .. DIR_DELIM .. "mod.conf"):to_table()
mod_conf = Settings(mod_path .. DIR_DELIM .. "mod.conf"):to_table()
if mod_conf.name then
name = mod_conf.name
toadd.is_name_explicit = true
Expand All @@ -136,12 +137,13 @@ function get_mods(path,retval,modpack)
toadd.name = name
toadd.author = mod_conf.author
toadd.release = tonumber(mod_conf.release) or 0
toadd.path = prefix
toadd.path = mod_path
toadd.virtual_path = mod_virtual_path
toadd.type = "mod"

-- Check modpack.txt
-- Note: modpack.conf is already checked above
local modpackfile = io.open(prefix .. DIR_DELIM .. "modpack.txt")
local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt")
if modpackfile then
modpackfile:close()
toadd.is_modpack = true
Expand All @@ -153,7 +155,7 @@ function get_mods(path,retval,modpack)
elseif toadd.is_modpack then
toadd.type = "modpack"
toadd.is_modpack = true
get_mods(prefix, retval, name)
get_mods(mod_path, mod_virtual_path, retval, name)
end
end
end
Expand Down Expand Up @@ -397,13 +399,24 @@ function pkgmgr.is_modpack_entirely_enabled(data, name)
return true
end

local function disable_all_by_name(list, name, except)
for i=1, #list do
if list[i].name == name and list[i] ~= except then
list[i].enabled = false
end
end
end

---------- toggles or en/disables a mod or modpack and its dependencies --------
local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod)
if not mod.is_modpack then
-- Toggle or en/disable the mod
if toset == nil then
toset = not mod.enabled
end
if toset then
disable_all_by_name(list, mod.name, mod)
end
if mod.enabled ~= toset then
mod.enabled = toset
toggled_mods[#toggled_mods+1] = mod.name
Expand Down Expand Up @@ -647,8 +660,8 @@ function pkgmgr.preparemodlist(data)

--read global mods
local modpaths = core.get_modpaths()
for _, modpath in ipairs(modpaths) do
get_mods(modpath, global_mods)
for key, modpath in pairs(modpaths) do
get_mods(modpath, key, global_mods)
end

for i=1,#global_mods,1 do
Expand Down Expand Up @@ -687,22 +700,37 @@ function pkgmgr.preparemodlist(data)
DIR_DELIM .. "world.mt"

local worldfile = Settings(filename)

for key,value in pairs(worldfile:to_table()) do
for key, value in pairs(worldfile:to_table()) do
if key:sub(1, 9) == "load_mod_" then
key = key:sub(10)
local element = nil
for i=1,#retval,1 do
local mod_found = false

local fallback_found = false
local fallback_mod = nil

for i=1, #retval do
if retval[i].name == key and
not retval[i].is_modpack then
element = retval[i]
break
not retval[i].is_modpack then
if core.is_yes(value) or retval[i].virtual_path == value then
retval[i].enabled = true
mod_found = true
break
elseif fallback_found then
-- Only allow fallback if only one mod matches
fallback_mod = nil
else
fallback_found = true
fallback_mod = retval[i]
end
end
end
if element ~= nil then
element.enabled = value ~= "false" and value ~= "nil" and value
else
core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")

if not mod_found then
if fallback_mod and value:find("/") then
fallback_mod.enabled = true
else
core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
end
end
end
end
Expand Down Expand Up @@ -796,7 +824,7 @@ function pkgmgr.get_game_mods(gamespec, retval)
if gamespec ~= nil and
gamespec.gamemods_path ~= nil and
gamespec.gamemods_path ~= "" then
get_mods(gamespec.gamemods_path, retval)
get_mods(gamespec.gamemods_path, ("games/%s/mods"):format(gamespec.id), retval)
end
end

Expand Down
17 changes: 14 additions & 3 deletions doc/menu_lua_api.txt
Expand Up @@ -221,13 +221,24 @@ Package - content which is downloadable from the content db, may or may not be i
* returns path to global user data,
the directory that contains user-provided mods, worlds, games, and texture packs.
* core.get_modpath() (possible in async calls)
* returns path to global modpath, where mods can be installed
* returns path to global modpath in the user path, where mods can be installed
* core.get_modpaths() (possible in async calls)
* returns list of paths to global modpaths, where mods have been installed

* returns table of virtual path to global modpaths, where mods have been installed
The difference with "core.get_modpath" is that no mods should be installed in these
directories by Minetest -- they might be read-only.

Ex:

```
{
mods = "/home/user/.minetest/mods",
share = "/usr/share/minetest/mods",

-- Custom dirs can be specified by the MINETEST_MOD_DIR env variable
["/path/to/custom/dir"] = "/path/to/custom/dir",
}
```

* core.get_clientmodpath() (possible in async calls)
* returns path to global client-side modpath
* core.get_gamepath() (possible in async calls)
Expand Down
13 changes: 13 additions & 0 deletions doc/world_format.txt
Expand Up @@ -133,6 +133,19 @@ Example content (added indentation and - explanations):
load_mod_<mod> = false - whether <mod> is to be loaded in this world
auth_backend = files - which DB backend to use for authentication data

For load_mod_<mod>, the possible values are:

* `false` - Do not load the mod.
* `true` - Load the mod from wherever it is found (may cause conflicts if the same mod appears also in some other place).
* `mods/modpack/moddir` - Relative path to the mod
* Must be one of the following:
* `mods/`: mods in the user path's mods folder (ex `/home/user/.minetest/mods`)
* `share/`: mods in the share's mods folder (ex: `/usr/share/minetest/mods`)
* `/path/to/env`: you can use absolute paths to mods inside folders specified with the `MINETEST_MOD_PATH` env variable.
* Other locations and absolute paths are not supported
* Note that `moddir` is the directory name, not the mod name specified in mod.conf.


Player File Format
===================

Expand Down