]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - builtin/mainmenu/pkgmgr.lua
Update credits for 5.5.0 release (#12001)
[dragonfireclient.git] / builtin / mainmenu / pkgmgr.lua
index 49ab78c24c0685f77f50ae2142f0cf304925a5a8..6de6715296a4b42f2085a802806ceb70cc381ebb 100644 (file)
 --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 --------------------------------------------------------------------------------
-function get_mods(path,retval,modpack)
-       local mods = core.get_dir_list(path, true)
+local function get_last_folder(text,count)
+       local parts = text:split(DIR_DELIM)
 
-       for _, name in ipairs(mods) do
-               if name:sub(1, 1) ~= "." then
-                       local prefix = path .. DIR_DELIM .. name .. DIR_DELIM
-                       local toadd = {}
-                       retval[#retval + 1] = toadd
+       if count == nil then
+               return parts[#parts]
+       end
 
-                       local mod_conf = Settings(prefix .. "mod.conf"):to_table()
-                       if mod_conf.name then
-                               name = mod_conf.name
-                       end
+       local retval = ""
+       for i=1,count,1 do
+               retval = retval .. parts[#parts - (count-i)] .. DIR_DELIM
+       end
 
-                       toadd.name = name
-                       toadd.author = mod_conf.author
-                       toadd.release = tonumber(mod_conf.release or "0")
-                       toadd.path = prefix
-                       toadd.type = "mod"
+       return retval
+end
 
-                       if modpack ~= nil and modpack ~= "" then
-                               toadd.modpack = modpack
-                       else
-                               local modpackfile = io.open(prefix .. "modpack.txt")
-                               if modpackfile then
-                                       modpackfile:close()
-                                       toadd.type = "modpack"
-                                       toadd.is_modpack = true
-                                       get_mods(prefix, retval, name)
-                               end
-                       end
+local function cleanup_path(temppath)
+
+       local parts = temppath:split("-")
+       temppath = ""
+       for i=1,#parts,1 do
+               if temppath ~= "" then
+                       temppath = temppath .. "_"
                end
+               temppath = temppath .. parts[i]
        end
-end
 
---modmanager implementation
-pkgmgr = {}
+       parts = temppath:split(".")
+       temppath = ""
+       for i=1,#parts,1 do
+               if temppath ~= "" then
+                       temppath = temppath .. "_"
+               end
+               temppath = temppath .. parts[i]
+       end
 
-function pkgmgr.get_texture_packs()
-       local txtpath = core.get_texturepath()
-       local list = core.get_dir_list(txtpath, true)
-       local retval = {}
+       parts = temppath:split("'")
+       temppath = ""
+       for i=1,#parts,1 do
+               if temppath ~= "" then
+                       temppath = temppath .. ""
+               end
+               temppath = temppath .. parts[i]
+       end
 
+       parts = temppath:split(" ")
+       temppath = ""
+       for i=1,#parts,1 do
+               if temppath ~= "" then
+                       temppath = temppath
+               end
+               temppath = temppath .. parts[i]
+       end
+
+       return temppath
+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")
 
        for _, item in ipairs(list) do
@@ -75,7 +90,7 @@ function pkgmgr.get_texture_packs()
                        retval[#retval + 1] = {
                                name = item,
                                author = conf:get("author"),
-                               release = tonumber(conf:get("release") or "0"),
+                               release = tonumber(conf:get("release")) or 0,
                                list_name = name,
                                type = "txp",
                                path = path,
@@ -83,6 +98,80 @@ function pkgmgr.get_texture_packs()
                        }
                end
        end
+end
+
+function get_mods(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 toadd = {
+                               dir_name = name,
+                               parent_dir = path,
+                       }
+                       retval[#retval + 1] = toadd
+
+                       -- Get config file
+                       local mod_conf
+                       local modpack_conf = io.open(prefix .. 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()
+                               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()
+                               if mod_conf.name then
+                                       name = mod_conf.name
+                                       toadd.is_name_explicit = true
+                               end
+                       end
+
+                       -- Read from config
+                       toadd.name = name
+                       toadd.author = mod_conf.author
+                       toadd.release = tonumber(mod_conf.release) or 0
+                       toadd.path = prefix
+                       toadd.type = "mod"
+
+                       -- Check modpack.txt
+                       -- Note: modpack.conf is already checked above
+                       local modpackfile = io.open(prefix .. DIR_DELIM .. "modpack.txt")
+                       if modpackfile then
+                               modpackfile:close()
+                               toadd.is_modpack = true
+                       end
+
+                       -- Deal with modpack contents
+                       if modpack and modpack ~= "" then
+                               toadd.modpack = modpack
+                       elseif toadd.is_modpack then
+                               toadd.type = "modpack"
+                               toadd.is_modpack = true
+                               get_mods(prefix, retval, name)
+                       end
+               end
+       end
+end
+
+--modmanager implementation
+pkgmgr = {}
+
+function pkgmgr.get_texture_packs()
+       local txtpath = core.get_texturepath()
+       local txtpath_system = core.get_texturepath_share()
+       local retval = {}
+
+       load_texture_packs(txtpath, retval)
+       -- on portable versions these two paths coincide. It avoids loading the path twice
+       if txtpath ~= txtpath_system then
+               load_texture_packs(txtpath_system, retval)
+       end
 
        table.sort(retval, function(a, b)
                return a.name > b.name
@@ -92,21 +181,6 @@ function pkgmgr.get_texture_packs()
 end
 
 --------------------------------------------------------------------------------
-function pkgmgr.extract(modfile)
-       if modfile.type == "zip" then
-               local tempfolder = os.tempfolder()
-
-               if tempfolder ~= nil and
-                       tempfolder ~= "" then
-                       core.create_dir(tempfolder)
-                       if core.extract_zip(modfile.name,tempfolder) then
-                               return tempfolder
-                       end
-               end
-       end
-       return nil
-end
-
 function pkgmgr.get_folder_type(path)
        local testfile = io.open(path .. DIR_DELIM .. "init.lua","r")
        if testfile ~= nil then
@@ -114,6 +188,12 @@ function pkgmgr.get_folder_type(path)
                return { type = "mod", path = path }
        end
 
+       testfile = io.open(path .. DIR_DELIM .. "modpack.conf","r")
+       if testfile ~= nil then
+               testfile:close()
+               return { type = "modpack", path = path }
+       end
+
        testfile = io.open(path .. DIR_DELIM .. "modpack.txt","r")
        if testfile ~= nil then
                testfile:close()
@@ -255,17 +335,14 @@ function pkgmgr.identify_modname(modpath,filename)
 end
 --------------------------------------------------------------------------------
 function pkgmgr.render_packagelist(render_list)
-       local retval = ""
-
-       if render_list == nil then
-               if pkgmgr.global_mods == nil then
+       if not render_list then
+               if not pkgmgr.global_mods then
                        pkgmgr.refresh_globals()
                end
                render_list = pkgmgr.global_mods
        end
 
        local list = render_list:get_list()
-       local last_modpack = nil
        local retval = {}
        for i, v in ipairs(list) do
                local color = ""
@@ -275,7 +352,7 @@ function pkgmgr.render_packagelist(render_list)
 
                        for j = 1, #rawlist, 1 do
                                if rawlist[j].modpack == list[i].name and
-                                               rawlist[j].enabled ~= true then
+                                               not rawlist[j].enabled then
                                        -- Modpack not entirely enabled so showing as grey
                                        color = mt_color_grey
                                        break
@@ -302,11 +379,11 @@ end
 --------------------------------------------------------------------------------
 function pkgmgr.get_dependencies(path)
        if path == nil then
-               return "", ""
+               return {}, {}
        end
 
        local info = core.get_content_info(path)
-       return table.concat(info.depends or {}, ","), table.concat(info.optional_depends or {}, ",")
+       return info.depends or {}, info.optional_depends or {}
 end
 
 ----------- tests whether all of the mods in the modpack are enabled -----------
@@ -320,35 +397,109 @@ function pkgmgr.is_modpack_entirely_enabled(data, name)
        return true
 end
 
----------- toggles or en/disables a mod or modpack -----------------------------
+---------- 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 mod.enabled ~= toset then
+                       mod.enabled = toset
+                       toggled_mods[#toggled_mods+1] = mod.name
+               end
+               if toset then
+                       -- Mark this mod for recursive dependency traversal
+                       enabled_mods[mod.name] = true
+               end
+       else
+               -- Toggle or en/disable every mod in the modpack,
+               -- interleaved unsupported
+               for i = 1, #list do
+                       if list[i].modpack == mod.name then
+                               toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, list[i])
+                       end
+               end
+       end
+end
+
 function pkgmgr.enable_mod(this, toset)
-       local mod = this.data.list:get_list()[this.data.selected_mod]
+       local list = this.data.list:get_list()
+       local mod = list[this.data.selected_mod]
 
-       -- game mods can't be enabled or disabled
+       -- Game mods can't be enabled or disabled
        if mod.is_game_content then
                return
        end
 
-       -- toggle or en/disable the mod
-       if not mod.is_modpack then
-               if toset == nil then
-                       mod.enabled = not mod.enabled
-               else
-                       mod.enabled = toset
-               end
+       local toggled_mods = {}
+       local enabled_mods = {}
+       toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod)
+       toset = mod.enabled -- Update if toggled
+
+       if not toset then
+               -- Mod(s) were disabled, so no dependencies need to be enabled
+               table.sort(toggled_mods)
+               core.log("info", "Following mods were disabled: " ..
+                       table.concat(toggled_mods, ", "))
                return
        end
 
-       -- toggle or en/disable every mod in the modpack, interleaved unsupported
-       local list = this.data.list:get_raw_list()
-       for i = 1, #list do
-               if list[i].modpack == mod.name then
-                       if toset == nil then
-                               toset = not list[i].enabled
+       -- Enable mods' depends after activation
+
+       -- Make a list of mod ids indexed by their names
+       local mod_ids = {}
+       for id, mod2 in pairs(list) do
+               if mod2.type == "mod" and not mod2.is_modpack then
+                       mod_ids[mod2.name] = id
+               end
+       end
+
+       -- to_enable is used as a DFS stack with sp as stack pointer
+       local to_enable = {}
+       local sp = 0
+       for name in pairs(enabled_mods) do
+               local depends = pkgmgr.get_dependencies(list[mod_ids[name]].path)
+               for i = 1, #depends do
+                       local dependency_name = depends[i]
+                       if not enabled_mods[dependency_name] then
+                               sp = sp+1
+                               to_enable[sp] = dependency_name
+                       end
+               end
+       end
+       -- If sp is 0, every dependency is already activated
+       while sp > 0 do
+               local name = to_enable[sp]
+               sp = sp-1
+
+               if not enabled_mods[name] then
+                       enabled_mods[name] = true
+                       local mod_to_enable = list[mod_ids[name]]
+                       if not mod_to_enable then
+                               core.log("warning", "Mod dependency \"" .. name ..
+                                       "\" not found!")
+                       else
+                               if mod_to_enable.enabled == false then
+                                       mod_to_enable.enabled = true
+                                       toggled_mods[#toggled_mods+1] = mod_to_enable.name
+                               end
+                               -- Push the dependencies of the dependency onto the stack
+                               local depends = pkgmgr.get_dependencies(mod_to_enable.path)
+                               for i = 1, #depends do
+                                       if not enabled_mods[name] then
+                                               sp = sp+1
+                                               to_enable[sp] = depends[i]
+                                       end
+                               end
                        end
-                       list[i].enabled = toset
                end
        end
+
+       -- Log the list of enabled mods
+       table.sort(toggled_mods)
+       core.log("info", "Following mods were enabled: " ..
+               table.concat(toggled_mods, ", "))
 end
 
 --------------------------------------------------------------------------------
@@ -366,7 +517,10 @@ function pkgmgr.get_worldconfig(worldpath)
                if key == "gameid" then
                        worldconfig.id = value
                elseif key:sub(0, 9) == "load_mod_" then
-                       worldconfig.global_mods[key] = core.is_yes(value)
+                       -- Compatibility: Check against "nil" which was erroneously used
+                       -- as value for fresh configured worlds
+                       worldconfig.global_mods[key] = value ~= "false" and value ~= "nil"
+                               and value
                else
                        worldconfig[key] = value
                end
@@ -393,11 +547,10 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
                local from = basefolder and basefolder.path or path
                if targetpath then
                        core.delete_dir(targetpath)
-                       core.create_dir(targetpath)
                else
                        targetpath = core.get_texturepath() .. DIR_DELIM .. basename
                end
-               if not core.copy_dir(from, targetpath) then
+               if not core.copy_dir(from, targetpath, false) then
                        return nil,
                                fgettext("Failed to install $1 to $2", basename, targetpath)
                end
@@ -418,11 +571,10 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
                -- Get destination name for modpack
                if targetpath then
                        core.delete_dir(targetpath)
-                       core.create_dir(targetpath)
                else
                        local clean_path = nil
                        if basename ~= nil then
-                               clean_path = "mp_" .. basename
+                               clean_path = basename
                        end
                        if not clean_path then
                                clean_path = get_last_folder(cleanup_path(basefolder.path))
@@ -431,8 +583,8 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
                                targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
                        else
                                return nil,
-                                       fgettext("Install Mod: unable to find suitable foldername for modpack $1",
-                                       modfilename)
+                                       fgettext("Install Mod: Unable to find suitable folder name for modpack $1",
+                                       path)
                        end
                end
        elseif basefolder.type == "mod" then
@@ -442,7 +594,6 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
 
                if targetpath then
                        core.delete_dir(targetpath)
-                       core.create_dir(targetpath)
                else
                        local targetfolder = basename
                        if targetfolder == nil then
@@ -457,7 +608,7 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
                        if targetfolder ~= nil and pkgmgr.isValidModname(targetfolder) then
                                targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
                        else
-                               return nil, fgettext("Install Mod: unable to find real modname for: $1", modfilename)
+                               return nil, fgettext("Install Mod: Unable to find real mod name for: $1", path)
                        end
                end
 
@@ -468,38 +619,24 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
 
                if targetpath then
                        core.delete_dir(targetpath)
-                       core.create_dir(targetpath)
                else
                        targetpath = core.get_gamepath() .. DIR_DELIM .. basename
                end
        end
 
        -- Copy it
-       if not core.copy_dir(basefolder.path, targetpath) then
+       if not core.copy_dir(basefolder.path, targetpath, false) then
                return nil,
                        fgettext("Failed to install $1 to $2", basename, targetpath)
        end
 
-       pkgmgr.refresh_globals()
-
-       return targetpath, nil
-end
-
---------------------------------------------------------------------------------
-function pkgmgr.install(type, modfilename, basename, dest)
-       local archive_info = pkgmgr.identify_filetype(modfilename)
-       local path = pkgmgr.extract(archive_info)
-
-       if path == nil then
-               return nil,
-                       fgettext("Install: file: \"$1\"", archive_info.name) .. "\n" ..
-                       fgettext("Install: unsupported filetype \"$1\" or broken archive",
-                               archive_info.type)
+       if basefolder.type == "game" then
+               pkgmgr.update_gamelist()
+       else
+               pkgmgr.refresh_globals()
        end
 
-       local targetpath, msg = pkgmgr.install_dir(type, path, basename, dest)
-       core.delete_dir(path)
-       return targetpath, msg
+       return targetpath, nil
 end
 
 --------------------------------------------------------------------------------
@@ -510,11 +647,9 @@ function pkgmgr.preparemodlist(data)
        local game_mods = {}
 
        --read global mods
-       local modpath = core.get_modpath()
-
-       if modpath ~= nil and
-               modpath ~= "" then
-               get_mods(modpath,global_mods)
+       local modpaths = core.get_modpaths()
+       for _, modpath in ipairs(modpaths) do
+               get_mods(modpath, global_mods)
        end
 
        for i=1,#global_mods,1 do
@@ -532,7 +667,8 @@ function pkgmgr.preparemodlist(data)
                retval[#retval + 1] = {
                        type = "game",
                        is_game_content = true,
-                       name = fgettext("Subgame Mods")
+                       name = fgettext("$1 mods", gamespec.name),
+                       path = gamespec.path
                }
        end
 
@@ -565,7 +701,7 @@ function pkgmgr.preparemodlist(data)
                                end
                        end
                        if element ~= nil then
-                               element.enabled = core.is_yes(value)
+                               element.enabled = value ~= "false" and value ~= "nil" and value
                        else
                                core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
                        end
@@ -646,45 +782,6 @@ function pkgmgr.refresh_globals()
        pkgmgr.global_mods:set_sortmode("alphabetic")
 end
 
---------------------------------------------------------------------------------
-function pkgmgr.identify_filetype(name)
-
-       if name:sub(-3):lower() == "zip" then
-               return {
-                               name = name,
-                               type = "zip"
-                               }
-       end
-
-       if name:sub(-6):lower() == "tar.gz" or
-               name:sub(-3):lower() == "tgz"then
-               return {
-                               name = name,
-                               type = "tgz"
-                               }
-       end
-
-       if name:sub(-6):lower() == "tar.bz2" then
-               return {
-                               name = name,
-                               type = "tbz"
-                               }
-       end
-
-       if name:sub(-2):lower() == "7z" then
-               return {
-                               name = name,
-                               type = "7z"
-                               }
-       end
-
-       return {
-               name = name,
-               type = "ukn"
-       }
-end
-
-
 --------------------------------------------------------------------------------
 function pkgmgr.find_by_gameid(gameid)
        for i=1,#pkgmgr.games,1 do