Add online content repository
authorrubenwardy <rw@rubenwardy.com>
Tue, 17 Apr 2018 13:54:50 +0000 (14:54 +0100)
committerrubenwardy <rw@rubenwardy.com>
Thu, 19 Apr 2018 19:14:53 +0000 (20:14 +0100)
Replaces mods and texture pack tabs with a single content tab

52 files changed:
build/android/jni/Android.mk
builtin/common/filterlist.lua
builtin/common/misc_helpers.lua
builtin/mainmenu/common.lua
builtin/mainmenu/dlg_config_world.lua
builtin/mainmenu/dlg_contentstore.lua [new file with mode: 0644]
builtin/mainmenu/dlg_create_world.lua
builtin/mainmenu/dlg_delete_content.lua [new file with mode: 0644]
builtin/mainmenu/dlg_delete_mod.lua [deleted file]
builtin/mainmenu/dlg_rename_modpack.lua
builtin/mainmenu/dlg_settings_advanced.lua
builtin/mainmenu/gamemgr.lua [deleted file]
builtin/mainmenu/init.lua
builtin/mainmenu/modmgr.lua [deleted file]
builtin/mainmenu/pkgmgr.lua [new file with mode: 0644]
builtin/mainmenu/tab_content.lua [new file with mode: 0644]
builtin/mainmenu/tab_local.lua
builtin/mainmenu/tab_mods.lua [deleted file]
builtin/mainmenu/tab_texturepacks.lua [deleted file]
doc/lua_api.txt
doc/menu_lua_api.txt
doc/texture_packs.txt
src/CMakeLists.txt
src/client.cpp
src/content/CMakeLists.txt [new file with mode: 0644]
src/content/content.cpp [new file with mode: 0644]
src/content/content.h [new file with mode: 0644]
src/content/mods.cpp [new file with mode: 0644]
src/content/mods.h [new file with mode: 0644]
src/content/packages.cpp [new file with mode: 0644]
src/content/packages.h [new file with mode: 0644]
src/content/subgames.cpp [new file with mode: 0644]
src/content/subgames.h [new file with mode: 0644]
src/convert_json.cpp
src/defaultsettings.cpp
src/httpfetch.cpp
src/mods.cpp [deleted file]
src/mods.h [deleted file]
src/script/cpp_api/s_base.cpp
src/script/lua_api/l_base.cpp
src/script/lua_api/l_mainmenu.cpp
src/script/lua_api/l_mainmenu.h
src/script/lua_api/l_storage.cpp
src/script/scripting_mainmenu.cpp
src/server.cpp
src/server.h
src/server/mods.cpp
src/server/mods.h
src/serverlist.h
src/subgame.cpp [deleted file]
src/subgame.h [deleted file]
src/unittest/test.cpp

index a4e55eaa947263c4b3f8e6f2300f9044183722c0..c19fcb747bf67b1e0944b74402962f9c734a57cc 100644 (file)
@@ -130,6 +130,9 @@ LOCAL_SRC_FILES := \
                jni/src/content_mapnode.cpp               \
                jni/src/content_nodemeta.cpp              \
                jni/src/content_sao.cpp                   \
+               jni/src/content/contentdb.cpp             \
+               jni/src/content/mods.cpp                  \
+               jni/src/content/subgames.cpp              \
                jni/src/convert_json.cpp                  \
                jni/src/craftdef.cpp                      \
                jni/src/database/database-dummy.cpp       \
@@ -198,7 +201,6 @@ LOCAL_SRC_FILES := \
                jni/src/mapgen/mg_ore.cpp                 \
                jni/src/mapgen/mg_schematic.cpp           \
                jni/src/minimap.cpp                       \
-               jni/src/mods.cpp                          \
                jni/src/modchannels.cpp                   \
                jni/src/nameidmapping.cpp                 \
                jni/src/nodedef.cpp                       \
@@ -229,7 +231,6 @@ LOCAL_SRC_FILES := \
                jni/src/shader.cpp                        \
                jni/src/sky.cpp                           \
                jni/src/staticobject.cpp                  \
-               jni/src/subgame.cpp                       \
                jni/src/tileanimation.cpp                 \
                jni/src/translation.cpp                   \
                jni/src/tool.cpp                          \
index 56223119230602f499c92c9a539996ee6a87b52e..1ba1d87417fb35e007373285f904244263847d36 100644 (file)
@@ -47,17 +47,17 @@ function filterlist.create(raw_fct,compare_fct,uid_match_fct,filter_fct,fetch_pa
 
        assert((raw_fct ~= nil) and (type(raw_fct) == "function"))
        assert((compare_fct ~= nil) and (type(compare_fct) == "function"))
-       
+
        local self = {}
-       
+
        self.m_raw_list_fct  = raw_fct
        self.m_compare_fct   = compare_fct
        self.m_filter_fct    = filter_fct
        self.m_uid_match_fct = uid_match_fct
-       
+
        self.m_filtercriteria = nil
        self.m_fetch_param = fetch_param
-       
+
        self.m_sortmode = "none"
        self.m_sort_list = {}
 
@@ -79,7 +79,7 @@ function filterlist.create(raw_fct,compare_fct,uid_match_fct,filter_fct,fetch_pa
        self.refresh            = filterlist.refresh
 
        filterlist.process(self)
-       
+
        return self
 end
 
@@ -128,49 +128,49 @@ function filterlist.get_raw_element(self,idx)
        if type(idx) ~= "number" then
                idx = tonumber(idx)
        end
-       
+
        if idx ~= nil and idx > 0 and idx <= #self.m_raw_list then
                return self.m_raw_list[idx]
        end
-       
+
        return nil
 end
 
 --------------------------------------------------------------------------------
 function filterlist.get_raw_index(self,listindex)
        assert(self.m_processed_list ~= nil)
-       
+
        if listindex ~= nil and listindex > 0 and
                listindex <= #self.m_processed_list then
                local entry = self.m_processed_list[listindex]
-               
+
                for i,v in ipairs(self.m_raw_list) do
-               
+
                        if self.m_compare_fct(v,entry) then
                                return i
                        end
                end
        end
-       
+
        return 0
 end
 
 --------------------------------------------------------------------------------
 function filterlist.get_current_index(self,listindex)
        assert(self.m_processed_list ~= nil)
-       
+
        if listindex ~= nil and listindex > 0 and
                listindex <= #self.m_raw_list then
                local entry = self.m_raw_list[listindex]
-               
+
                for i,v in ipairs(self.m_processed_list) do
-               
+
                        if self.m_compare_fct(v,entry) then
                                return i
                        end
                end
        end
-       
+
        return 0
 end
 
@@ -183,23 +183,23 @@ function filterlist.process(self)
                self.m_processed_list = self.m_raw_list
                return
        end
-       
+
        self.m_processed_list = {}
-       
+
        for k,v in pairs(self.m_raw_list) do
                if self.m_filtercriteria == nil or
                        self.m_filter_fct(v,self.m_filtercriteria) then
                        self.m_processed_list[#self.m_processed_list + 1] = v
                end
        end
-       
+
        if self.m_sortmode == "none" then
                return
        end
-       
+
        if self.m_sort_list[self.m_sortmode] ~= nil and
                type(self.m_sort_list[self.m_sortmode]) == "function" then
-               
+
                self.m_sort_list[self.m_sortmode](self)
        end
 end
@@ -209,7 +209,7 @@ function filterlist.size(self)
        if self.m_processed_list == nil then
                return 0
        end
-       
+
        return #self.m_processed_list
 end
 
@@ -233,8 +233,8 @@ function filterlist.raw_index_by_uid(self, uid)
                        elementidx = i
                end
        end
-       
-       
+
+
        -- If there are more elements than one with same name uid can't decide which
        -- one is meant. self shouldn't be possible but just for sure.
        if elementcount > 1 then
@@ -254,11 +254,11 @@ function compare_worlds(world1,world2)
        if world1.path ~= world2.path then
                return false
        end
-       
+
        if world1.name ~= world2.name then
                return false
        end
-       
+
        if world1.gameid ~= world2.gameid then
                return false
        end
@@ -288,11 +288,11 @@ function sort_mod_list(self)
 
        table.sort(self.m_processed_list, function(a, b)
                -- Show game mods at bottom
-               if a.typ ~= b.typ then
-                       if b.typ == "game" then 
-                               return a.typ ~= "game_mod"
+               if a.type ~= b.type or a.loc ~= b.loc then
+                       if b.type == "game" then
+                               return a.loc ~= "game"
                        end
-                       return b.typ == "game_mod"
+                       return b.loc == "game"
                end
                -- If in same or no modpack, sort by name
                if a.modpack == b.modpack then
@@ -308,7 +308,7 @@ function sort_mod_list(self)
                        elseif b.name == a.modpack then
                                return false
                        end
-                       
+
                        local name_a = a.modpack or a.name
                        local name_b = b.modpack or b.name
                        if name_a:lower() == name_b:lower() then
index aa118b4430157241bc45a15ac7a522a6e2cff4c7..43a6dda48239ecd118b88a07f658180cbeb1c441 100644 (file)
@@ -551,6 +551,16 @@ function table.copy(t, seen)
        end
        return n
 end
+
+
+function table.insert_all(t, other)
+       for i=1, #other do
+               t[#t + 1] = other[i]
+       end
+       return t
+end
+
+
 --------------------------------------------------------------------------------
 -- mainmenu only functions
 --------------------------------------------------------------------------------
index 7eb941775b9b67fc86fe4aeb365ba78402a2f9b1..7c0cbae4d6152804ec53bf93eb7f07fc677dfed5 100644 (file)
@@ -41,7 +41,7 @@ local function render_client_count(n)
 end
 
 local function configure_selected_world_params(idx)
-       local worldconfig = modmgr.get_worldconfig(menudata.worldlist:get_list()[idx].path)
+       local worldconfig = pkgmgr.get_worldconfig(menudata.worldlist:get_list()[idx].path)
        if worldconfig.creative_mode then
                core.settings:set("creative_mode", worldconfig.creative_mode)
        end
index fcedadea886347e1bfc444200faada55ce2463ba..33c5d9bba3e0d8eb862f287358bac57aff2da57d 100644 (file)
@@ -35,7 +35,7 @@ local function get_formspec(data)
                mod = {name=""}
        end
 
-       local hard_deps, soft_deps = modmgr.get_dependencies(mod.path)
+       local hard_deps, soft_deps = pkgmgr.get_dependencies(mod.path)
 
        retval = retval ..
                "label[0,0.7;" .. fgettext("Mod:") .. "]" ..
@@ -88,7 +88,7 @@ local function get_formspec(data)
        retval = retval ..
                "tablecolumns[color;tree;text]" ..
                "table[5.5,0.75;5.75,6;world_config_modlist;"
-       retval = retval .. modmgr.render_modlist(data.list)
+       retval = retval .. pkgmgr.render_packagelist(data.list)
        retval = retval .. ";" .. data.selected_mod .."]"
 
        return retval
@@ -237,7 +237,7 @@ function create_configure_world_dlg(worldidx)
        dlg.data.worldspec = core.get_worlds()[worldidx]
        if dlg.data.worldspec == nil then dlg:delete() return nil end
 
-       dlg.data.worldconfig = modmgr.get_worldconfig(dlg.data.worldspec.path)
+       dlg.data.worldconfig = pkgmgr.get_worldconfig(dlg.data.worldspec.path)
 
        if dlg.data.worldconfig == nil or dlg.data.worldconfig.id == nil or
                        dlg.data.worldconfig.id == "" then
@@ -247,8 +247,8 @@ function create_configure_world_dlg(worldidx)
        end
 
        dlg.data.list = filterlist.create(
-                       modmgr.preparemodlist, --refresh
-                       modmgr.comparemod, --compare
+                       pkgmgr.preparemodlist, --refresh
+                       pkgmgr.comparemod, --compare
                        function(element,uid) --uid match
                                        if element.name == uid then
                                                return true
diff --git a/builtin/mainmenu/dlg_contentstore.lua b/builtin/mainmenu/dlg_contentstore.lua
new file mode 100644 (file)
index 0000000..2e9b90c
--- /dev/null
@@ -0,0 +1,451 @@
+--Minetest
+--Copyright (C) 2018 rubenwardy
+--
+--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 function download_package(param)
+       if core.download_file(param.package.url, param.filename) then
+               return {
+                       package = param.package,
+                       filename = param.filename,
+                       successful = true,
+               }
+       else
+               core.log("error", "downloading " .. dump(param.package.url) .. " failed")
+               return {
+                       package = param.package,
+                       successful = false,
+               }
+       end
+end
+
+local function start_install(calling_dialog, package)
+       local params = {
+               package = package,
+               filename = os.tempfolder() .. "_MODNAME_" .. package.name .. ".zip",
+       }
+
+       local function callback(result)
+               if result.successful then
+                       local path, msg = pkgmgr.install(result.package.type, result.filename, result.package.name)
+                       if not path then
+                               gamedata.errormessage = msg
+                       else
+                               local conf_path
+                               local name_is_title = false
+                               if result.package.type == "mod" then
+                                       conf_path = path .. DIR_DELIM .. "mod.conf"
+                               elseif result.package.type == "game" then
+                                       conf_path = path .. DIR_DELIM .. "game.conf"
+                                       name_is_title = true
+                               elseif result.package.type == "txp" then
+                                       conf_path = path .. DIR_DELIM .. "texture_pack.conf"
+                               end
+
+                               if conf_path then
+                                       local conf = Settings(conf_path)
+                                       local function set_def(key, value)
+                                               if conf:get(key) == nil then
+                                                       conf:set(key, value)
+                                               end
+                                       end
+                                       if name_is_title then
+                                               set_def("name",    result.package.title)
+                                       else
+                                               set_def("title",   result.package.title)
+                                               set_def("name",    result.package.name)
+                                       end
+                                       set_def("description", result.package.short_description)
+                                       set_def("author",      result.package.author)
+                                       conf:write()
+                               end
+                       end
+                       os.remove(result.filename)
+               else
+                       gamedata.errormessage = fgettext("Failed to download $1", package.name)
+               end
+
+               if gamedata.errormessage == nil then
+                       core.button_handler({btn_hidden_close_download=result})
+               else
+                       core.button_handler({btn_hidden_close_download={successful=false}})
+               end
+       end
+
+       if not core.handle_async(download_package, params, callback) then
+               minetest.log("error", "ERROR: async event failed")
+               gamedata.errormessage = fgettext("Failed to download $1", package.name)
+       end
+
+       local new_dlg = dialog_create("store_downloading",
+               function(data)
+                       return "size[7,2]label[0.25,0.75;" ..
+                               fgettext("Downloading and installing $1, please wait...", data.title) .. "]"
+               end,
+               function(this,fields)
+                       if fields["btn_hidden_close_download"] ~= nil then
+                               this:delete()
+                               return true
+                       end
+
+                       return false
+               end,
+               nil)
+
+       new_dlg:set_parent(calling_dialog)
+       new_dlg.data.title = package.title
+       calling_dialog:hide()
+       new_dlg:show()
+end
+
+
+local package_dialog = {}
+
+function package_dialog.get_formspec()
+       local package = package_dialog.package
+
+       local formspec = {
+               "size[8,4;true]",
+               "label[2.5,0.2;", core.formspec_escape(package.title), "]",
+               "label[0,1;", core.formspec_escape(package.short_description), "]",
+               "button[0,0;2,1;back;", fgettext("Back"), "]",
+               "button[6,0;2,1;install;", fgettext("Install"), "]",
+       }
+
+       -- TODO: screenshots
+
+       return table.concat(formspec, "")
+end
+
+function package_dialog.handle_submit(this, fields, tabname, tabdata)
+       if fields.back then
+               this:delete()
+               return true
+       end
+
+       if fields.install then
+               start_install(package_dialog.package)
+               return true
+       end
+
+       return false
+end
+
+function package_dialog.create(package)
+       package_dialog.package = package
+       return dialog_create("package_view",
+               package_dialog.get_formspec,
+               package_dialog.handle_submit,
+               nil)
+end
+
+
+
+
+local store = {}
+local search_string = ""
+local cur_page = 1
+local num_per_page = 5
+local filter_type = 1
+local filter_types_titles = {
+       fgettext("All packages"),
+       fgettext("Games"),
+       fgettext("Mods"),
+       fgettext("Texture packs"),
+}
+
+local filter_types_type = {
+       nil,
+       "game",
+       "mod",
+       "txp",
+}
+
+function store.load()
+       store.packages_full = core.get_package_list()
+       store.packages = store.packages_full
+       store.loaded = true
+end
+
+function store.update_paths()
+       local mod_hash = {}
+       pkgmgr.refresh_globals()
+       for _, mod in pairs(pkgmgr.global_mods:get_list()) do
+               mod_hash[mod.name] = mod
+       end
+
+       local game_hash = {}
+       pkgmgr.update_gamelist()
+       for _, game in pairs(pkgmgr.games) do
+               game_hash[game.id] = game
+       end
+
+       local txp_hash = {}
+       for _, txp in pairs(pkgmgr.get_texture_packs()) do
+               txp_hash[txp.name] = txp
+       end
+
+       for _, package in pairs(store.packages_full) do
+               local content
+               if package.type == "mod" then
+                       content = mod_hash[package.name]
+               elseif package.type == "game" then
+                       content = game_hash[package.name]
+               elseif package.type == "txp" then
+                       content = txp_hash[package.name]
+               end
+
+               if content and content.author == package.author then
+                       package.path = content.path
+               else
+                       package.path = nil
+               end
+       end
+end
+
+function store.filter_packages(query)
+       if query == "" and filter_type == 1 then
+               store.packages = store.packages_full
+               return
+       end
+
+       local keywords = {}
+       for word in query:lower():gmatch("%S+") do
+               table.insert(keywords, word)
+       end
+
+       local function matches_keywords(package, keywords)
+               for k = 1, #keywords do
+                       local keyword = keywords[k]
+
+                       if string.find(package.name:lower(), keyword, 1, true) or
+                                       string.find(package.title:lower(), keyword, 1, true) or
+                                       string.find(package.author:lower(), keyword, 1, true) or
+                                       string.find(package.short_description:lower(), keyword, 1, true) then
+                               return true
+                       end
+               end
+
+               return false
+       end
+
+       store.packages = {}
+       for _, package in pairs(store.packages_full) do
+               if (query == "" or matches_keywords(package, keywords)) and
+                               (filter_type == 1 or package.type == filter_types_type[filter_type]) then
+                       store.packages[#store.packages + 1] = package
+               end
+       end
+
+end
+
+function store.get_formspec()
+       assert(store.loaded)
+
+       store.update_paths()
+
+       local pages = math.ceil(#store.packages / num_per_page)
+       if cur_page > pages then
+               cur_page = 1
+       end
+
+       local formspec = {
+               "size[12,6.5;true]",
+               "field[0.3,0.1;10.2,1;search_string;;", core.formspec_escape(search_string), "]",
+               "field_close_on_enter[search_string;false]",
+               "button[10.2,-0.2;2,1;search;", fgettext("Search"), "]",
+               "dropdown[0,1;2.4;type;",
+               table.concat(filter_types_titles, ","),
+               ";",
+               filter_type,
+               "]",
+               -- "textlist[0,1;2.4,5.6;a;",
+               -- table.concat(taglist, ","),
+               -- "]"
+       }
+
+       local start_idx = (cur_page - 1) * num_per_page + 1
+       for i=start_idx, math.min(#store.packages, start_idx+num_per_page-1) do
+               local package = store.packages[i]
+               formspec[#formspec + 1] = "container[3,"
+               formspec[#formspec + 1] = i - start_idx + 1
+               formspec[#formspec + 1] = "]"
+
+               -- image
+               formspec[#formspec + 1] = "image[-0.4,0;1.5,1;"
+               formspec[#formspec + 1] = defaulttexturedir
+               formspec[#formspec + 1] = "no_screenshot.png"
+               formspec[#formspec + 1] = "]"
+
+               -- title
+               formspec[#formspec + 1] = "label[1,0;"
+               formspec[#formspec + 1] = core.formspec_escape(package.title ..
+                               " by " .. package.author)
+               formspec[#formspec + 1] = "]"
+
+               -- description
+               local short = package.short_description
+               if #short > 60 then
+                       short = short:sub(1, 59) .. "…"
+               end
+               formspec[#formspec + 1] = "label[1,0.3;"
+               formspec[#formspec + 1] = core.formspec_escape(short)
+               formspec[#formspec + 1] = "]"
+
+               -- buttons
+               if package.path then
+                       formspec[#formspec + 1] = "button[6,0;1.5,1;uninstall_"
+                       formspec[#formspec + 1] = tostring(i)
+                       formspec[#formspec + 1] = ";"
+                       formspec[#formspec + 1] = fgettext("Uninstall")
+                       formspec[#formspec + 1] = "]"
+               else
+                       formspec[#formspec + 1] = "button[6,0;1.5,1;install_"
+                       formspec[#formspec + 1] = tostring(i)
+                       formspec[#formspec + 1] = ";"
+                       formspec[#formspec + 1] = fgettext("Install")
+                       formspec[#formspec + 1] = "]"
+               end
+               formspec[#formspec + 1] = "button[7.5,0;1.5,1;view_"
+               formspec[#formspec + 1] = tostring(i)
+               formspec[#formspec + 1] = ";"
+               formspec[#formspec + 1] = fgettext("View")
+               formspec[#formspec + 1] = "]"
+
+               formspec[#formspec + 1] = "container_end[]"
+       end
+
+       formspec[#formspec + 1] = "container[0,"
+       formspec[#formspec + 1] = num_per_page + 1
+       formspec[#formspec + 1] = "]"
+       formspec[#formspec + 1] = "button[2.6,0;3,1;back;"
+       formspec[#formspec + 1] = fgettext("Back to Main Menu")
+       formspec[#formspec + 1] = "]"
+       formspec[#formspec + 1] = "button[7,0;1,1;pstart;<<]"
+       formspec[#formspec + 1] = "button[8,0;1,1;pback;<]"
+       formspec[#formspec + 1] = "label[9.2,0.2;"
+       formspec[#formspec + 1] = tonumber(cur_page)
+       formspec[#formspec + 1] = " / "
+       formspec[#formspec + 1] = tonumber(pages)
+       formspec[#formspec + 1] = "]"
+       formspec[#formspec + 1] = "button[10,0;1,1;pnext;>]"
+       formspec[#formspec + 1] = "button[11,0;1,1;pend;>>]"
+       formspec[#formspec + 1] = "container_end[]"
+
+       formspec[#formspec + 1] = "]"
+       return table.concat(formspec, "")
+end
+
+function store.handle_submit(this, fields, tabname, tabdata)
+       if fields.search or fields.key_enter_field == "search_string" then
+               search_string = fields.search_string:trim()
+               cur_page = 1
+               store.filter_packages(search_string)
+               core.update_formspec(store.get_formspec())
+               return true
+       end
+
+       if fields.back then
+               this:delete()
+               return true
+       end
+
+       if fields.pstart then
+               cur_page = 1
+               core.update_formspec(store.get_formspec())
+               return true
+       end
+
+       if fields.pend then
+               cur_page = math.ceil(#store.packages / num_per_page)
+               core.update_formspec(store.get_formspec())
+               return true
+       end
+
+       if fields.pnext then
+               cur_page = cur_page + 1
+               local pages = math.ceil(#store.packages / num_per_page)
+               if cur_page > pages then
+                       cur_page = 1
+               end
+               core.update_formspec(store.get_formspec())
+               return true
+       end
+
+       if fields.pback then
+               if cur_page == 1 then
+                       local pages = math.ceil(#store.packages / num_per_page)
+                       cur_page = pages
+               else
+                       cur_page = cur_page - 1
+               end
+               core.update_formspec(store.get_formspec())
+               return true
+       end
+
+       if fields.type then
+               local new_type = table.indexof(filter_types_titles, fields.type)
+               if new_type ~= filter_type then
+                       filter_type = new_type
+                       store.filter_packages(search_string)
+                       return true
+               end
+       end
+
+       local start_idx = (cur_page - 1) * num_per_page + 1
+       assert(start_idx ~= nil)
+       for i=start_idx, math.min(#store.packages, start_idx+num_per_page-1) do
+               local package = store.packages[i]
+               assert(package)
+
+               if fields["install_" .. i] then
+                       start_install(this, package)
+                       return true
+               end
+
+               if fields["uninstall_" .. i] then
+                       local dlg_delmod = create_delete_content_dlg(package)
+                       dlg_delmod:set_parent(this)
+                       this:hide()
+                       dlg_delmod:show()
+                       return true
+               end
+
+               if fields["view_" .. i] then
+                       local dlg = package_dialog.create(package)
+                       dlg:set_parent(this)
+                       this:hide()
+                       dlg:show()
+                       return true
+               end
+       end
+
+       return false
+end
+
+function create_store_dlg(type)
+       if not store.loaded then
+               store.load()
+       end
+
+       search_string = ""
+       cur_page = 1
+       store.filter_packages(search_string)
+
+       return dialog_create("store",
+                       store.get_formspec,
+                       store.handle_submit,
+                       nil)
+end
index 134eeebc185456dff67231ecdbe7e884f4378a70..968b1fc7433b784646153712becbbb48120a342e 100644 (file)
@@ -26,7 +26,7 @@ local function create_world_formspec(dialogdata)
 
        local game, gameidx = nil , 0
        if gameid ~= nil then
-               game, gameidx = gamemgr.find_by_gameid(gameid)
+               game, gameidx = pkgmgr.find_by_gameid(gameid)
                
                if gameidx == nil then
                        gameidx = 0
@@ -77,17 +77,17 @@ local function create_world_formspec(dialogdata)
                "dropdown[4.2,2;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" ..
 
                "label[2,3;" .. fgettext("Game") .. "]"..
-               "textlist[4.2,3;5.8,2.3;games;" .. gamemgr.gamelist() ..
+               "textlist[4.2,3;5.8,2.3;games;" .. pkgmgr.gamelist() ..
                ";" .. gameidx .. ";true]" ..
 
                "button[3.25,6;2.5,0.5;world_create_confirm;" .. fgettext("Create") .. "]" ..
                "button[5.75,6;2.5,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
                
-       if #gamemgr.games == 0 then
+       if #pkgmgr.games == 0 then
                retval = retval .. "box[2,4;8,1;#ff8800]label[2.25,4;" ..
                                fgettext("You have no games installed.") .. "]label[2.25,4.4;" ..
                                fgettext("Download one from minetest.net") .. "]"
-       elseif #gamemgr.games == 1 and gamemgr.games[1].id == "minimal" then
+       elseif #pkgmgr.games == 1 and pkgmgr.games[1].id == "minimal" then
                retval = retval .. "box[1.75,4;8.7,1;#ff8800]label[2,4;" ..
                                fgettext("Warning: The minimal development test is meant for developers.") .. "]label[2,4.4;" ..
                                fgettext("Download a game, such as minetest_game, from minetest.net") .. "]"
@@ -125,10 +125,10 @@ local function create_world_buttonhandler(this, fields)
                        if message ~= nil then
                                gamedata.errormessage = message
                        else
-                               core.settings:set("menu_last_game",gamemgr.games[gameindex].id)
+                               core.settings:set("menu_last_game",pkgmgr.games[gameindex].id)
                                if this.data.update_worldlist_filter then
-                                       menudata.worldlist:set_filtercriteria(gamemgr.games[gameindex].id)
-                                       mm_texture.update("singleplayer", gamemgr.games[gameindex].id)
+                                       menudata.worldlist:set_filtercriteria(pkgmgr.games[gameindex].id)
+                                       mm_texture.update("singleplayer", pkgmgr.games[gameindex].id)
                                end
                                menudata.worldlist:refresh()
                                core.settings:set("mainmenu_last_selected_world",
@@ -145,7 +145,7 @@ local function create_world_buttonhandler(this, fields)
 
        if fields["games"] then
                local gameindex = core.get_textlist_index("games")
-               core.settings:set("menu_last_game", gamemgr.games[gameindex].id)
+               core.settings:set("menu_last_game", pkgmgr.games[gameindex].id)
                return true
        end
 
diff --git a/builtin/mainmenu/dlg_delete_content.lua b/builtin/mainmenu/dlg_delete_content.lua
new file mode 100644 (file)
index 0000000..8bc80b1
--- /dev/null
@@ -0,0 +1,69 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--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 function delete_content_formspec(dialogdata)
+       local retval =
+               "size[11.5,4.5,true]" ..
+               "label[2,2;" ..
+               fgettext("Are you sure you want to delete \"$1\"?", dialogdata.content.name) .. "]"..
+               "button[3.25,3.5;2.5,0.5;dlg_delete_content_confirm;" .. fgettext("Delete") .. "]" ..
+               "button[5.75,3.5;2.5,0.5;dlg_delete_content_cancel;" .. fgettext("Cancel") .. "]"
+
+       return retval
+end
+
+--------------------------------------------------------------------------------
+local function delete_content_buttonhandler(this, fields)
+       if fields["dlg_delete_content_confirm"] ~= nil then
+
+               if this.data.content.path ~= nil and
+                               this.data.content.path ~= "" and
+                               this.data.content.path ~= core.get_modpath() and
+                               this.data.content.path ~= core.get_gamepath() and
+                               this.data.content.path ~= core.get_texturepath() then
+                       if not core.delete_dir(this.data.content.path) then
+                               gamedata.errormessage = fgettext("pkgmgr: failed to delete \"$1\"", this.data.content.path)
+                       end
+                       pkgmgr.refresh_globals()
+               else
+                       gamedata.errormessage = fgettext("pkgmgr: invalid path \"$1\"", this.data.content.path)
+               end
+               this:delete()
+               return true
+       end
+
+       if fields["dlg_delete_content_cancel"] then
+               this:delete()
+               return true
+       end
+
+       return false
+end
+
+--------------------------------------------------------------------------------
+function create_delete_content_dlg(content)
+       assert(content.name)
+
+       local retval = dialog_create("dlg_delete_content",
+                                       delete_content_formspec,
+                                       delete_content_buttonhandler,
+                                       nil)
+       retval.data.content = content
+       return retval
+end
diff --git a/builtin/mainmenu/dlg_delete_mod.lua b/builtin/mainmenu/dlg_delete_mod.lua
deleted file mode 100644 (file)
index 2efd704..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
---Minetest
---Copyright (C) 2014 sapier
---
---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 function delete_mod_formspec(dialogdata)
-       
-       dialogdata.mod = modmgr.global_mods:get_list()[dialogdata.selected]
-
-       local retval =
-               "size[11.5,4.5,true]" ..
-               "label[2,2;" ..
-               fgettext("Are you sure you want to delete \"$1\"?", dialogdata.mod.name) .. "]"..
-               "button[3.25,3.5;2.5,0.5;dlg_delete_mod_confirm;" .. fgettext("Delete") .. "]" ..
-               "button[5.75,3.5;2.5,0.5;dlg_delete_mod_cancel;" .. fgettext("Cancel") .. "]"
-       
-       return retval
-end
-
---------------------------------------------------------------------------------
-local function delete_mod_buttonhandler(this, fields)
-       if fields["dlg_delete_mod_confirm"] ~= nil then
-
-               if this.data.mod.path ~= nil and
-                       this.data.mod.path ~= "" and
-                       this.data.mod.path ~= core.get_modpath() then
-                       if not core.delete_dir(this.data.mod.path) then
-                               gamedata.errormessage = fgettext("Modmgr: failed to delete \"$1\"", this.data.mod.path)
-                       end
-                       modmgr.refresh_globals()
-               else
-                       gamedata.errormessage = fgettext("Modmgr: invalid modpath \"$1\"", this.data.mod.path)
-               end
-               this:delete()
-               return true
-       end
-       
-       if fields["dlg_delete_mod_cancel"] then
-               this:delete()
-               return true
-       end
-
-       return false
-end
-
---------------------------------------------------------------------------------
-function create_delete_mod_dlg(selected_index)
-
-       local retval = dialog_create("dlg_delete_mod",
-                                       delete_mod_formspec,
-                                       delete_mod_buttonhandler,
-                                       nil)
-       retval.data.selected = selected_index
-       return retval
-end
index 959c65d9b65237c13b8391a1391918cf1402569b..2b383993fb50359c3df4aeed77daa17aa267086b 100644 (file)
@@ -18,8 +18,8 @@
 --------------------------------------------------------------------------------
 
 local function rename_modpack_formspec(dialogdata)
-       
-       dialogdata.mod = modmgr.global_mods:get_list()[dialogdata.selected]
+
+       dialogdata.mod = pkgmgr.global_mods:get_list()[dialogdata.selected]
 
        local retval =
                "size[11.5,4.5,true]" ..
@@ -29,7 +29,7 @@ local function rename_modpack_formspec(dialogdata)
                                fgettext("Accept") .. "]" ..
                "button[5.75,3.5;2.5,0.5;dlg_rename_modpack_cancel;"..
                                fgettext("Cancel") .. "]"
-       
+
        return retval
 end
 
@@ -39,14 +39,14 @@ local function rename_modpack_buttonhandler(this, fields)
                local oldpath = core.get_modpath() .. DIR_DELIM .. this.data.mod.name
                local targetpath = core.get_modpath() .. DIR_DELIM .. fields["te_modpack_name"]
                core.copy_dir(oldpath,targetpath,false)
-               modmgr.refresh_globals()
-               modmgr.selected_mod = modmgr.global_mods:get_current_index(
-                       modmgr.global_mods:raw_index_by_uid(fields["te_modpack_name"]))
-                       
+               pkgmgr.refresh_globals()
+               pkgmgr.selected_mod = pkgmgr.global_mods:get_current_index(
+                       pkgmgr.global_mods:raw_index_by_uid(fields["te_modpack_name"]))
+
                this:delete()
                return true
        end
-       
+
        if fields["dlg_rename_modpack_cancel"] then
                this:delete()
                return true
index 7d524eb8049a3ee2d465d6596bf81351ddcc7ba4..2e6617aa728f34c02f29b384f085ea2bd5350e05 100644 (file)
@@ -338,7 +338,7 @@ local function parse_config_file(read_all, parse_mods)
                -- Parse games
                local games_category_initialized = false
                local index = 1
-               local game = gamemgr.get_game(index)
+               local game = pkgmgr.get_game(index)
                while game do
                        local path = game.path .. DIR_DELIM .. FILENAME
                        local file = io.open(path, "r")
@@ -365,7 +365,7 @@ local function parse_config_file(read_all, parse_mods)
                        end
 
                        index = index + 1
-                       game = gamemgr.get_game(index)
+                       game = pkgmgr.get_game(index)
                end
 
                -- Parse mods
diff --git a/builtin/mainmenu/gamemgr.lua b/builtin/mainmenu/gamemgr.lua
deleted file mode 100644 (file)
index fd6025f..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
---Minetest
---Copyright (C) 2013 sapier
---
---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.
-
-gamemgr = {}
-
---------------------------------------------------------------------------------
-function gamemgr.find_by_gameid(gameid)
-       for i=1,#gamemgr.games,1 do
-               if gamemgr.games[i].id == gameid then
-                       return gamemgr.games[i], i
-               end
-       end
-       return nil, nil
-end
-
---------------------------------------------------------------------------------
-function gamemgr.get_game_mods(gamespec, retval)
-       if gamespec ~= nil and
-               gamespec.gamemods_path ~= nil and
-               gamespec.gamemods_path ~= "" then
-               get_mods(gamespec.gamemods_path, retval)
-       end
-end
-
---------------------------------------------------------------------------------
-function gamemgr.get_game_modlist(gamespec)
-       local retval = ""
-       local game_mods = {}
-       gamemgr.get_game_mods(gamespec, game_mods)
-       for i=1,#game_mods,1 do
-               if retval ~= "" then
-                       retval = retval..","
-               end
-               retval = retval .. game_mods[i].name
-       end
-       return retval
-end
-
---------------------------------------------------------------------------------
-function gamemgr.get_game(index)
-       if index > 0 and index <= #gamemgr.games then
-               return gamemgr.games[index]
-       end
-
-       return nil
-end
-
---------------------------------------------------------------------------------
-function gamemgr.update_gamelist()
-       gamemgr.games = core.get_games()
-end
-
---------------------------------------------------------------------------------
-function gamemgr.gamelist()
-       local retval = ""
-       if #gamemgr.games > 0 then
-               retval = retval .. core.formspec_escape(gamemgr.games[1].name)
-
-               for i=2,#gamemgr.games,1 do
-                       retval = retval .. "," .. core.formspec_escape(gamemgr.games[i].name)
-               end
-       end
-       return retval
-end
-
---------------------------------------------------------------------------------
--- read initial data
---------------------------------------------------------------------------------
-gamemgr.update_gamelist()
index 1861a835e45e21880e64201d5bee4d2fbbf0a0dd..d959cd902411f0cb0f507b5ffc57322c9f68089e 100644 (file)
@@ -38,15 +38,15 @@ dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua")
 dofile(basepath .. "fstk" .. DIR_DELIM .. "tabview.lua")
 dofile(basepath .. "fstk" .. DIR_DELIM .. "ui.lua")
 dofile(menupath .. DIR_DELIM .. "common.lua")
-dofile(menupath .. DIR_DELIM .. "gamemgr.lua")
-dofile(menupath .. DIR_DELIM .. "modmgr.lua")
+dofile(menupath .. DIR_DELIM .. "pkgmgr.lua")
 dofile(menupath .. DIR_DELIM .. "textures.lua")
 
 dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua")
 dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua")
+dofile(menupath .. DIR_DELIM .. "dlg_contentstore.lua")
 if menustyle ~= "simple" then
        dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua")
-       dofile(menupath .. DIR_DELIM .. "dlg_delete_mod.lua")
+       dofile(menupath .. DIR_DELIM .. "dlg_delete_content.lua")
        dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua")
        dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua")
 end
@@ -54,14 +54,13 @@ end
 local tabs = {}
 
 tabs.settings = dofile(menupath .. DIR_DELIM .. "tab_settings.lua")
-tabs.mods = dofile(menupath .. DIR_DELIM .. "tab_mods.lua")
-tabs.credits = dofile(menupath .. DIR_DELIM .. "tab_credits.lua")
+tabs.content  = dofile(menupath .. DIR_DELIM .. "tab_content.lua")
+tabs.credits  = dofile(menupath .. DIR_DELIM .. "tab_credits.lua")
 if menustyle == "simple" then
        tabs.simple_main = dofile(menupath .. DIR_DELIM .. "tab_simple_main.lua")
 else
        tabs.local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua")
        tabs.play_online = dofile(menupath .. DIR_DELIM .. "tab_online.lua")
-       tabs.texturepacks = dofile(menupath .. DIR_DELIM .. "tab_texturepacks.lua")
 end
 
 --------------------------------------------------------------------------------
@@ -134,16 +133,14 @@ local function init_globals()
 
        if menustyle == "simple" then
                tv_main:add(tabs.simple_main)
-               tv_main:add(tabs.settings)
        else
                tv_main:set_autosave_tab(true)
                tv_main:add(tabs.local_game)
                tv_main:add(tabs.play_online)
-               tv_main:add(tabs.settings)
-               tv_main:add(tabs.texturepacks)
        end
 
-       tv_main:add(tabs.mods)
+       tv_main:add(tabs.content)
+       tv_main:add(tabs.settings)
        tv_main:add(tabs.credits)
 
        tv_main:set_global_event_handler(main_event_handler)
diff --git a/builtin/mainmenu/modmgr.lua b/builtin/mainmenu/modmgr.lua
deleted file mode 100644 (file)
index 185bcd6..0000000
+++ /dev/null
@@ -1,549 +0,0 @@
---Minetest
---Copyright (C) 2013 sapier
---
---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.
-
---------------------------------------------------------------------------------
-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 .. DIR_DELIM
-                       local toadd = {}
-                       retval[#retval + 1] = toadd
-
-                       local mod_conf = Settings(prefix .. "mod.conf"):to_table()
-                       if mod_conf.name then
-                               name = mod_conf.name
-                       end
-
-                       toadd.name = name
-                       toadd.path = prefix
-
-                       if modpack ~= nil and modpack ~= "" then
-                               toadd.modpack = modpack
-                       else
-                               local modpackfile = io.open(prefix .. "modpack.txt")
-                               if modpackfile then
-                                       modpackfile:close()
-                                       toadd.is_modpack = true
-                                       get_mods(prefix, retval, name)
-                               end
-                       end
-               end
-       end
-end
-
---modmanager implementation
-modmgr = {}
-
---------------------------------------------------------------------------------
-function modmgr.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 modmgr.getbasefolder(temppath)
-
-       if temppath == nil then
-               return {
-               type = "invalid",
-               path = ""
-               }
-       end
-
-       local testfile = io.open(temppath .. DIR_DELIM .. "init.lua","r")
-       if testfile ~= nil then
-               testfile:close()
-               return {
-                               type="mod",
-                               path=temppath
-                               }
-       end
-
-       testfile = io.open(temppath .. DIR_DELIM .. "modpack.txt","r")
-       if testfile ~= nil then
-               testfile:close()
-               return {
-                               type="modpack",
-                               path=temppath
-                               }
-       end
-
-       local subdirs = core.get_dir_list(temppath, true)
-
-       --only single mod or modpack allowed
-       if #subdirs ~= 1 then
-               return {
-                       type = "invalid",
-                       path = ""
-                       }
-       end
-
-       testfile =
-       io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."init.lua","r")
-       if testfile ~= nil then
-               testfile:close()
-               return {
-                       type="mod",
-                       path= temppath .. DIR_DELIM .. subdirs[1]
-                       }
-       end
-
-       testfile =
-       io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."modpack.txt","r")
-       if testfile ~= nil then
-               testfile:close()
-               return {
-                       type="modpack",
-                       path=temppath ..  DIR_DELIM .. subdirs[1]
-                       }
-       end
-
-       return {
-               type = "invalid",
-               path = ""
-               }
-end
-
---------------------------------------------------------------------------------
-function modmgr.isValidModname(modpath)
-       if modpath:find("-") ~= nil then
-               return false
-       end
-
-       return true
-end
-
---------------------------------------------------------------------------------
-function modmgr.parse_register_line(line)
-       local pos1 = line:find("\"")
-       local pos2 = nil
-       if pos1 ~= nil then
-               pos2 = line:find("\"",pos1+1)
-       end
-
-       if pos1 ~= nil and pos2 ~= nil then
-               local item = line:sub(pos1+1,pos2-1)
-
-               if item ~= nil and
-                       item ~= "" then
-                       local pos3 = item:find(":")
-
-                       if pos3 ~= nil then
-                               local retval = item:sub(1,pos3-1)
-                               if retval ~= nil and
-                                       retval ~= "" then
-                                       return retval
-                               end
-                       end
-               end
-       end
-       return nil
-end
-
---------------------------------------------------------------------------------
-function modmgr.parse_dofile_line(modpath,line)
-       local pos1 = line:find("\"")
-       local pos2 = nil
-       if pos1 ~= nil then
-               pos2 = line:find("\"",pos1+1)
-       end
-
-       if pos1 ~= nil and pos2 ~= nil then
-               local filename = line:sub(pos1+1,pos2-1)
-
-               if filename ~= nil and
-                       filename ~= "" and
-                       filename:find(".lua") then
-                       return modmgr.identify_modname(modpath,filename)
-               end
-       end
-       return nil
-end
-
---------------------------------------------------------------------------------
-function modmgr.identify_modname(modpath,filename)
-       local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
-       if testfile ~= nil then
-               local line = testfile:read()
-
-               while line~= nil do
-                       local modname = nil
-
-                       if line:find("minetest.register_tool") then
-                               modname = modmgr.parse_register_line(line)
-                       end
-
-                       if line:find("minetest.register_craftitem") then
-                               modname = modmgr.parse_register_line(line)
-                       end
-
-
-                       if line:find("minetest.register_node") then
-                               modname = modmgr.parse_register_line(line)
-                       end
-
-                       if line:find("dofile") then
-                               modname = modmgr.parse_dofile_line(modpath,line)
-                       end
-
-                       if modname ~= nil then
-                               testfile:close()
-                               return modname
-                       end
-
-                       line = testfile:read()
-               end
-               testfile:close()
-       end
-
-       return nil
-end
---------------------------------------------------------------------------------
-function modmgr.render_modlist(render_list)
-       local retval = ""
-
-       if render_list == nil then
-               if modmgr.global_mods == nil then
-                       modmgr.refresh_globals()
-               end
-               render_list = modmgr.global_mods
-       end
-
-       local list = render_list:get_list()
-       local last_modpack = nil
-       local retval = {}
-       for i, v in ipairs(list) do
-               local color = ""
-               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
-                                               rawlist[j].enabled ~= true then
-                                       -- Modpack not entirely enabled so showing as grey
-                                       color = mt_color_grey
-                                       break
-                               end
-                       end
-               elseif v.is_game_content then
-                       color = mt_color_blue
-               elseif v.enabled then
-                       color = mt_color_green
-               end
-
-               retval[#retval + 1] = color
-               if v.modpack ~= nil or v.typ == "game_mod" then
-                       retval[#retval + 1] = "1"
-               else
-                       retval[#retval + 1] = "0"
-               end
-               retval[#retval + 1] = core.formspec_escape(v.name)
-       end
-
-       return table.concat(retval, ",")
-end
-
---------------------------------------------------------------------------------
-function modmgr.get_dependencies(path)
-       if path == nil then
-               return "", ""
-       end
-
-       local info = core.get_mod_info(path)
-       return table.concat(info.depends, ","), table.concat(info.optional_depends, ",")
-end
-
---------------------------------------------------------------------------------
-function modmgr.get_worldconfig(worldpath)
-       local filename = worldpath ..
-                               DIR_DELIM .. "world.mt"
-
-       local worldfile = Settings(filename)
-
-       local worldconfig = {}
-       worldconfig.global_mods = {}
-       worldconfig.game_mods = {}
-
-       for key,value in pairs(worldfile:to_table()) do
-               if key == "gameid" then
-                       worldconfig.id = value
-               elseif key:sub(0, 9) == "load_mod_" then
-                       worldconfig.global_mods[key] = core.is_yes(value)
-               else
-                       worldconfig[key] = value
-               end
-       end
-
-       --read gamemods
-       local gamespec = gamemgr.find_by_gameid(worldconfig.id)
-       gamemgr.get_game_mods(gamespec, worldconfig.game_mods)
-
-       return worldconfig
-end
-
---------------------------------------------------------------------------------
-function modmgr.installmod(modfilename,basename)
-       local modfile = modmgr.identify_filetype(modfilename)
-       local modpath = modmgr.extract(modfile)
-
-       if modpath == nil then
-               gamedata.errormessage = fgettext("Install Mod: file: \"$1\"", modfile.name) ..
-                       fgettext("\nInstall Mod: unsupported filetype \"$1\" or broken archive", modfile.type)
-               return
-       end
-
-       local basefolder = modmgr.getbasefolder(modpath)
-
-       if basefolder.type == "modpack" then
-               local clean_path = nil
-
-               if basename ~= nil then
-                       clean_path = "mp_" .. basename
-               end
-
-               if clean_path == nil then
-                       clean_path = get_last_folder(cleanup_path(basefolder.path))
-               end
-
-               if clean_path ~= nil then
-                       local targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
-                       if not core.copy_dir(basefolder.path,targetpath) then
-                               gamedata.errormessage = fgettext("Failed to install $1 to $2", basename, targetpath)
-                       end
-               else
-                       gamedata.errormessage = fgettext("Install Mod: unable to find suitable foldername for modpack $1", modfilename)
-               end
-       end
-
-       if basefolder.type == "mod" then
-               local targetfolder = basename
-
-               if targetfolder == nil then
-                       targetfolder = modmgr.identify_modname(basefolder.path,"init.lua")
-               end
-
-               --if heuristic failed try to use current foldername
-               if targetfolder == nil then
-                       targetfolder = get_last_folder(basefolder.path)
-               end
-
-               if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then
-                       local targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
-                       core.copy_dir(basefolder.path,targetpath)
-               else
-                       gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename)
-               end
-       end
-
-       core.delete_dir(modpath)
-
-       modmgr.refresh_globals()
-
-end
-
---------------------------------------------------------------------------------
-function modmgr.preparemodlist(data)
-       local retval = {}
-
-       local global_mods = {}
-       local game_mods = {}
-
-       --read global mods
-       local modpath = core.get_modpath()
-
-       if modpath ~= nil and
-               modpath ~= "" then
-               get_mods(modpath,global_mods)
-       end
-
-       for i=1,#global_mods,1 do
-               global_mods[i].typ = "global_mod"
-               retval[#retval + 1] = global_mods[i]
-       end
-
-       --read game mods
-       local gamespec = gamemgr.find_by_gameid(data.gameid)
-       gamemgr.get_game_mods(gamespec, game_mods)
-
-       if #game_mods > 0 then
-               -- Add title
-               retval[#retval + 1] = {
-                       typ = "game",
-                       is_game_content = true,
-                       name = fgettext("Subgame Mods")
-               }
-       end
-
-       for i=1,#game_mods,1 do
-               game_mods[i].typ = "game_mod"
-               game_mods[i].is_game_content = true
-               retval[#retval + 1] = game_mods[i]
-       end
-
-       if data.worldpath == nil then
-               return retval
-       end
-
-       --read world mod configuration
-       local filename = data.worldpath ..
-                               DIR_DELIM .. "world.mt"
-
-       local worldfile = Settings(filename)
-
-       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
-                               if retval[i].name == key and
-                                       not retval[i].is_modpack then
-                                       element = retval[i]
-                                       break
-                               end
-                       end
-                       if element ~= nil then
-                               element.enabled = core.is_yes(value)
-                       else
-                               core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
-                       end
-               end
-       end
-
-       return retval
-end
-
---------------------------------------------------------------------------------
-function modmgr.comparemod(elem1,elem2)
-       if elem1 == nil or elem2 == nil then
-               return false
-       end
-       if elem1.name ~= elem2.name then
-               return false
-       end
-       if elem1.is_modpack ~= elem2.is_modpack then
-               return false
-       end
-       if elem1.typ ~= elem2.typ then
-               return false
-       end
-       if elem1.modpack ~= elem2.modpack then
-               return false
-       end
-
-       if elem1.path ~= elem2.path then
-               return false
-       end
-
-       return true
-end
-
---------------------------------------------------------------------------------
-function modmgr.mod_exists(basename)
-
-       if modmgr.global_mods == nil then
-               modmgr.refresh_globals()
-       end
-
-       if modmgr.global_mods:raw_index_by_uid(basename) > 0 then
-               return true
-       end
-
-       return false
-end
-
---------------------------------------------------------------------------------
-function modmgr.get_global_mod(idx)
-
-       if modmgr.global_mods == nil then
-               return nil
-       end
-
-       if idx == nil or idx < 1 or
-               idx > modmgr.global_mods:size() then
-               return nil
-       end
-
-       return modmgr.global_mods:get_list()[idx]
-end
-
---------------------------------------------------------------------------------
-function modmgr.refresh_globals()
-       modmgr.global_mods = filterlist.create(
-                                       modmgr.preparemodlist, --refresh
-                                       modmgr.comparemod, --compare
-                                       function(element,uid) --uid match
-                                               if element.name == uid then
-                                                       return true
-                                               end
-                                       end,
-                                       nil, --filter
-                                       {}
-                                       )
-       modmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
-       modmgr.global_mods:set_sortmode("alphabetic")
-end
-
---------------------------------------------------------------------------------
-function modmgr.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
diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua
new file mode 100644 (file)
index 0000000..b834888
--- /dev/null
@@ -0,0 +1,684 @@
+--Minetest
+--Copyright (C) 2013 sapier
+--
+--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.
+
+--------------------------------------------------------------------------------
+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 .. DIR_DELIM
+                       local toadd = {}
+                       retval[#retval + 1] = toadd
+
+                       local mod_conf = Settings(prefix .. "mod.conf"):to_table()
+                       if mod_conf.name then
+                               name = mod_conf.name
+                       end
+
+                       toadd.name = name
+                       toadd.author = mod_conf.author
+                       toadd.path = prefix
+                       toadd.type = "mod"
+
+                       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
+               end
+       end
+end
+
+--modmanager implementation
+pkgmgr = {}
+
+function pkgmgr.get_texture_packs()
+       local txtpath = core.get_texturepath()
+       local list = core.get_dir_list(txtpath, true)
+       local retval = {}
+
+       local current_texture_path = core.settings:get("texture_path")
+
+       for _, item in ipairs(list) do
+               if item ~= "base" then
+                       local name = item
+
+                       local path = txtpath .. DIR_DELIM .. item .. DIR_DELIM
+                       if path == current_texture_path then
+                               name = fgettext("$1 (Enabled)", name)
+                       end
+
+                       local conf = Settings(path .. "texture_pack.conf")
+
+                       retval[#retval + 1] = {
+                               name = item,
+                               author = conf:get("author"),
+                               list_name = name,
+                               type = "txp",
+                               path = path,
+                               enabled = path == current_texture_path,
+                       }
+               end
+       end
+
+       table.sort(retval, function(a, b)
+               return a.name > b.name
+       end)
+
+       return retval
+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
+               testfile:close()
+               return { type = "mod", path = path }
+       end
+
+       testfile = io.open(path .. DIR_DELIM .. "modpack.txt","r")
+       if testfile ~= nil then
+               testfile:close()
+               return { type = "modpack", path = path }
+       end
+
+       testfile = io.open(path .. DIR_DELIM .. "game.conf","r")
+       if testfile ~= nil then
+               testfile:close()
+               return { type = "game", path = path }
+       end
+
+       testfile = io.open(path .. DIR_DELIM .. "texture_pack.conf","r")
+       if testfile ~= nil then
+               testfile:close()
+               return { type = "txp", path = path }
+       end
+
+       return nil
+end
+
+-------------------------------------------------------------------------------
+function pkgmgr.get_base_folder(temppath)
+       if temppath == nil then
+               return { type = "invalid", path = "" }
+       end
+
+       local ret = pkgmgr.get_folder_type(temppath)
+       if ret then
+               return ret
+       end
+
+       local subdirs = core.get_dir_list(temppath, true)
+       if #subdirs == 1 then
+               ret = pkgmgr.get_folder_type(temppath .. DIR_DELIM .. subdirs[1])
+               if ret then
+                       return ret
+               else
+                       return { type = "invalid", path = temppath .. DIR_DELIM .. subdirs[1] }
+               end
+       end
+
+       return nil
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.isValidModname(modpath)
+       if modpath:find("-") ~= nil then
+               return false
+       end
+
+       return true
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.parse_register_line(line)
+       local pos1 = line:find("\"")
+       local pos2 = nil
+       if pos1 ~= nil then
+               pos2 = line:find("\"",pos1+1)
+       end
+
+       if pos1 ~= nil and pos2 ~= nil then
+               local item = line:sub(pos1+1,pos2-1)
+
+               if item ~= nil and
+                       item ~= "" then
+                       local pos3 = item:find(":")
+
+                       if pos3 ~= nil then
+                               local retval = item:sub(1,pos3-1)
+                               if retval ~= nil and
+                                       retval ~= "" then
+                                       return retval
+                               end
+                       end
+               end
+       end
+       return nil
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.parse_dofile_line(modpath,line)
+       local pos1 = line:find("\"")
+       local pos2 = nil
+       if pos1 ~= nil then
+               pos2 = line:find("\"",pos1+1)
+       end
+
+       if pos1 ~= nil and pos2 ~= nil then
+               local filename = line:sub(pos1+1,pos2-1)
+
+               if filename ~= nil and
+                       filename ~= "" and
+                       filename:find(".lua") then
+                       return pkgmgr.identify_modname(modpath,filename)
+               end
+       end
+       return nil
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.identify_modname(modpath,filename)
+       local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
+       if testfile ~= nil then
+               local line = testfile:read()
+
+               while line~= nil do
+                       local modname = nil
+
+                       if line:find("minetest.register_tool") then
+                               modname = pkgmgr.parse_register_line(line)
+                       end
+
+                       if line:find("minetest.register_craftitem") then
+                               modname = pkgmgr.parse_register_line(line)
+                       end
+
+
+                       if line:find("minetest.register_node") then
+                               modname = pkgmgr.parse_register_line(line)
+                       end
+
+                       if line:find("dofile") then
+                               modname = pkgmgr.parse_dofile_line(modpath,line)
+                       end
+
+                       if modname ~= nil then
+                               testfile:close()
+                               return modname
+                       end
+
+                       line = testfile:read()
+               end
+               testfile:close()
+       end
+
+       return nil
+end
+--------------------------------------------------------------------------------
+function pkgmgr.render_packagelist(render_list)
+       local retval = ""
+
+       if render_list == nil then
+               if pkgmgr.global_mods == nil 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 = ""
+               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
+                                               rawlist[j].enabled ~= true then
+                                       -- Modpack not entirely enabled so showing as grey
+                                       color = mt_color_grey
+                                       break
+                               end
+                       end
+               elseif v.is_game_content or v.type == "game" then
+                       color = mt_color_blue
+               elseif v.enabled or v.type == "txp" then
+                       color = mt_color_green
+               end
+
+               retval[#retval + 1] = color
+               if v.modpack ~= nil or v.loc == "game" then
+                       retval[#retval + 1] = "1"
+               else
+                       retval[#retval + 1] = "0"
+               end
+               retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
+       end
+
+       return table.concat(retval, ",")
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.get_dependencies(path)
+       if path == nil then
+               return "", ""
+       end
+
+       local info = core.get_content_info(path)
+       return table.concat(info.depends or {}, ","), table.concat(info.optional_depends or {}, ",")
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.get_worldconfig(worldpath)
+       local filename = worldpath ..
+                               DIR_DELIM .. "world.mt"
+
+       local worldfile = Settings(filename)
+
+       local worldconfig = {}
+       worldconfig.global_mods = {}
+       worldconfig.game_mods = {}
+
+       for key,value in pairs(worldfile:to_table()) do
+               if key == "gameid" then
+                       worldconfig.id = value
+               elseif key:sub(0, 9) == "load_mod_" then
+                       worldconfig.global_mods[key] = core.is_yes(value)
+               else
+                       worldconfig[key] = value
+               end
+       end
+
+       --read gamemods
+       local gamespec = pkgmgr.find_by_gameid(worldconfig.id)
+       pkgmgr.get_game_mods(gamespec, worldconfig.game_mods)
+
+       return worldconfig
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.install_dir(type, path, basename)
+       local basefolder = pkgmgr.get_base_folder(path)
+
+       local targetpath
+       if type == "txp" then
+               if basefolder and basefolder.type ~= "invalid" and basefolder.type ~= "txp" then
+                       return nil, fgettext("Unable to install a $1 as a texture pack", basefolder.type)
+               end
+
+               local from = basefolder and basefolder.path or path
+               targetpath = core.get_texturepath() .. DIR_DELIM .. basename
+               core.copy_dir(from, targetpath)
+               return targetpath, nil
+
+       elseif not basefolder then
+               return nil, fgettext("Unable to find a valid mod or modpack")
+       end
+
+       if basefolder.type == "modpack" then
+               if type ~= "mod" then
+                       return nil, fgettext("Unable to install a modpack as a $1", type)
+               end
+               local clean_path = nil
+
+               if basename ~= nil then
+                       clean_path = "mp_" .. basename
+               end
+
+               if clean_path == nil then
+                       clean_path = get_last_folder(cleanup_path(basefolder.path))
+               end
+
+               if clean_path ~= nil then
+                       targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
+                       if not core.copy_dir(basefolder.path,targetpath) then
+                               return nil,
+                                       fgettext("Failed to install $1 to $2", basename, targetpath)
+                       end
+               else
+                       return nil,
+                               fgettext("Install Mod: unable to find suitable foldername for modpack $1",
+                               modfilename)
+               end
+
+               pkgmgr.refresh_globals()
+
+       elseif basefolder.type == "mod" then
+               if type ~= "mod" then
+                       return nil, fgettext("Unable to install a mod as a $1", type)
+               end
+               local targetfolder = basename
+
+               if targetfolder == nil then
+                       targetfolder = pkgmgr.identify_modname(basefolder.path,"init.lua")
+               end
+
+               --if heuristic failed try to use current foldername
+               if targetfolder == nil then
+                       targetfolder = get_last_folder(basefolder.path)
+               end
+
+               if targetfolder ~= nil and pkgmgr.isValidModname(targetfolder) then
+                       targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
+                       core.copy_dir(basefolder.path, targetpath)
+               else
+                       return nil, fgettext("Install Mod: unable to find real modname for: $1", modfilename)
+               end
+
+               pkgmgr.refresh_globals()
+
+       elseif basefolder.type == "game" then
+               if type ~= "game" then
+                       return nil, fgettext("Unable to install a game as a $1", type)
+               end
+
+               targetpath = core.get_gamepath() .. DIR_DELIM .. basename
+               core.copy_dir(basefolder.path, targetpath)
+       end
+
+       return targetpath, nil
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.install(type, modfilename, basename)
+       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)
+       end
+
+       local targetpath, msg = pkgmgr.install_dir(type, path, basename)
+       core.delete_dir(path)
+       return targetpath, msg
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.preparemodlist(data)
+       local retval = {}
+
+       local global_mods = {}
+       local game_mods = {}
+
+       --read global mods
+       local modpath = core.get_modpath()
+
+       if modpath ~= nil and
+               modpath ~= "" then
+               get_mods(modpath,global_mods)
+       end
+
+       for i=1,#global_mods,1 do
+               global_mods[i].type = "mod"
+               global_mods[i].loc = "global"
+               retval[#retval + 1] = global_mods[i]
+       end
+
+       --read game mods
+       local gamespec = pkgmgr.find_by_gameid(data.gameid)
+       pkgmgr.get_game_mods(gamespec, game_mods)
+
+       if #game_mods > 0 then
+               -- Add title
+               retval[#retval + 1] = {
+                       type = "game",
+                       is_game_content = true,
+                       name = fgettext("Subgame Mods")
+               }
+       end
+
+       for i=1,#game_mods,1 do
+               game_mods[i].type = "mod"
+               game_mods[i].loc = "game"
+               game_mods[i].is_game_content = true
+               retval[#retval + 1] = game_mods[i]
+       end
+
+       if data.worldpath == nil then
+               return retval
+       end
+
+       --read world mod configuration
+       local filename = data.worldpath ..
+                               DIR_DELIM .. "world.mt"
+
+       local worldfile = Settings(filename)
+
+       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
+                               if retval[i].name == key and
+                                       not retval[i].is_modpack then
+                                       element = retval[i]
+                                       break
+                               end
+                       end
+                       if element ~= nil then
+                               element.enabled = core.is_yes(value)
+                       else
+                               core.log("info", "Mod: " .. 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
+
+--------------------------------------------------------------------------------
+function pkgmgr.comparemod(elem1,elem2)
+       if elem1 == nil or elem2 == nil then
+               return false
+       end
+       if elem1.name ~= elem2.name then
+               return false
+       end
+       if elem1.is_modpack ~= elem2.is_modpack then
+               return false
+       end
+       if elem1.type ~= elem2.type then
+               return false
+       end
+       if elem1.modpack ~= elem2.modpack then
+               return false
+       end
+
+       if elem1.path ~= elem2.path then
+               return false
+       end
+
+       return true
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.mod_exists(basename)
+
+       if pkgmgr.global_mods == nil then
+               pkgmgr.refresh_globals()
+       end
+
+       if pkgmgr.global_mods:raw_index_by_uid(basename) > 0 then
+               return true
+       end
+
+       return false
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.get_global_mod(idx)
+
+       if pkgmgr.global_mods == nil then
+               return nil
+       end
+
+       if idx == nil or idx < 1 or
+               idx > pkgmgr.global_mods:size() then
+               return nil
+       end
+
+       return pkgmgr.global_mods:get_list()[idx]
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.refresh_globals()
+       local function is_equal(element,uid) --uid match
+               if element.name == uid then
+                       return true
+               end
+       end
+       pkgmgr.global_mods = filterlist.create(pkgmgr.preparemodlist,
+                       pkgmgr.comparemod, is_equal, nil, {})
+       pkgmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
+       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
+               if pkgmgr.games[i].id == gameid then
+                       return pkgmgr.games[i], i
+               end
+       end
+       return nil, nil
+end
+
+--------------------------------------------------------------------------------
+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)
+       end
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.get_game_modlist(gamespec)
+       local retval = ""
+       local game_mods = {}
+       pkgmgr.get_game_mods(gamespec, game_mods)
+       for i=1,#game_mods,1 do
+               if retval ~= "" then
+                       retval = retval..","
+               end
+               retval = retval .. game_mods[i].name
+       end
+       return retval
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.get_game(index)
+       if index > 0 and index <= #pkgmgr.games then
+               return pkgmgr.games[index]
+       end
+
+       return nil
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.update_gamelist()
+       pkgmgr.games = core.get_games()
+end
+
+--------------------------------------------------------------------------------
+function pkgmgr.gamelist()
+       local retval = ""
+       if #pkgmgr.games > 0 then
+               retval = retval .. core.formspec_escape(pkgmgr.games[1].name)
+
+               for i=2,#pkgmgr.games,1 do
+                       retval = retval .. "," .. core.formspec_escape(pkgmgr.games[i].name)
+               end
+       end
+       return retval
+end
+
+--------------------------------------------------------------------------------
+-- read initial data
+--------------------------------------------------------------------------------
+pkgmgr.update_gamelist()
diff --git a/builtin/mainmenu/tab_content.lua b/builtin/mainmenu/tab_content.lua
new file mode 100644 (file)
index 0000000..2699a74
--- /dev/null
@@ -0,0 +1,217 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--Copyright (C) 2018 rubenwardy <rw@rubenwardy.com>
+--
+--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
+local packages
+
+--------------------------------------------------------------------------------
+local function get_formspec(tabview, name, tabdata)
+       if pkgmgr.global_mods == nil then
+               pkgmgr.refresh_globals()
+       end
+       if pkgmgr.games == nil then
+               pkgmgr.update_gamelist()
+       end
+
+       if packages == nil then
+               packages_raw = {}
+               table.insert_all(packages_raw, pkgmgr.games)
+               table.insert_all(packages_raw, pkgmgr.get_texture_packs())
+               table.insert_all(packages_raw, pkgmgr.global_mods: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, {})
+       end
+
+       if tabdata.selected_pkg == nil then
+               tabdata.selected_pkg = 1
+       end
+
+
+       local retval =
+               "label[0.05,-0.25;".. fgettext("Installed Packages:") .. "]" ..
+               "tablecolumns[color;tree;text]" ..
+               "table[0,0.25;5.1,4.3;pkglist;" ..
+               pkgmgr.render_packagelist(packages) ..
+               ";" .. tabdata.selected_pkg .. "]" ..
+               "button[0,4.85;5.25,0.5;btn_contentdb;".. fgettext("Browse online content") .. "]"
+
+
+       local selected_pkg
+       if filterlist.size(packages) >= tabdata.selected_pkg then
+               selected_pkg = packages:get_list()[tabdata.selected_pkg]
+       end
+
+       if selected_pkg ~= nil then
+               --check for screenshot beeing available
+               local screenshotfilename = selected_pkg.path .. DIR_DELIM .. "screenshot.png"
+               local screenshotfile, error = io.open(screenshotfilename, "r")
+
+               local modscreenshot
+               if error == nil then
+                       screenshotfile:close()
+                       modscreenshot = screenshotfilename
+               end
+
+               if modscreenshot == nil then
+                               modscreenshot = defaulttexturedir .. "no_screenshot.png"
+               end
+
+               retval = retval ..
+                               "image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]" ..
+                               "label[8.25,0.6;" .. core.formspec_escape(selected_pkg.name) .. "]" ..
+                               "label[5.5,1.7;".. fgettext("Information:") .. "]" ..
+                               "textlist[5.5,2.2;6.2,2.4;description;"
+
+               local info = core.get_content_info(selected_pkg.path)
+               local desc = info.description or fgettext("No package description available")
+               local descriptionlines = core.wrap_text(desc, 42, true)
+               for i = 1, #descriptionlines do
+                       retval = retval .. core.formspec_escape(descriptionlines[i]) .. ","
+               end
+
+               if selected_pkg.type == "mod" then
+                       if selected_pkg.is_modpack then
+                               retval = retval .. ";0]" ..
+                                       "button[8.9,4.65;3,1;btn_mod_mgr_rename_modpack;" ..
+                                       fgettext("Rename") .. "]"
+                       else
+                               --show dependencies
+                               local toadd_hard = table.concat(info.depends or {}, ",")
+                               local toadd_soft = table.concat(info.optional_depends or {}, ",")
+                               if toadd_hard == "" and toadd_soft == "" then
+                                       retval = retval .. "," .. fgettext("No dependencies.")
+                               else
+                                       if toadd_hard ~= "" then
+                                               retval = retval .. "," .. fgettext("Dependencies:") .. ","
+                                               retval = retval .. toadd_hard
+                                       end
+                                       if toadd_soft ~= "" then
+                                               if toadd_hard ~= "" then
+                                                       retval = retval .. ","
+                                               end
+                                               retval = retval .. "," .. fgettext("Optional dependencies:") .. ","
+                                               retval = retval .. toadd_soft
+                                       end
+                               end
+
+                               retval = retval .. ";0]"
+                       end
+
+               else
+                       retval = retval .. ";0]"
+
+                       if selected_pkg.type == "txp" then
+                               if selected_pkg.enabled then
+                                       retval = retval ..
+                                               "button[8.9,4.65;3,1;btn_mod_mgr_disable_txp;" ..
+                                               fgettext("Disable Texture Pack") .. "]"
+                               else
+                                       retval = retval ..
+                                               "button[8.9,4.65;3,1;btn_mod_mgr_use_txp;" ..
+                                               fgettext("Use Texture Pack") .. "]"
+                               end
+                       end
+               end
+
+               retval = retval .. "button[5.5,4.65;3,1;btn_mod_mgr_delete_mod;"
+                       .. fgettext("Uninstall Package") .. "]"
+       end
+       return retval
+end
+
+--------------------------------------------------------------------------------
+local function handle_buttons(tabview, fields, tabname, tabdata)
+       if fields["pkglist"] ~= nil then
+               local event = core.explode_table_event(fields["pkglist"])
+               tabdata.selected_pkg = event.row
+               return true
+       end
+
+       if fields["btn_mod_mgr_install_local"] ~= nil then
+               core.show_file_open_dialog("mod_mgt_open_dlg", fgettext("Select Package File:"))
+               return true
+       end
+
+       if fields["btn_contentdb"] ~= nil then
+               local dlg = create_store_dlg()
+               dlg:set_parent(tabview)
+               tabview:hide()
+               dlg:show()
+               packages = nil
+               return true
+       end
+
+       if fields["btn_mod_mgr_rename_modpack"] ~= nil then
+               local dlg_renamemp = create_rename_modpack_dlg(tabdata.selected_pkg)
+               dlg_renamemp:set_parent(tabview)
+               tabview:hide()
+               dlg_renamemp:show()
+               return true
+       end
+
+       if fields["btn_mod_mgr_delete_mod"] ~= nil then
+               local mod = packages:get_list()[tabdata.selected_pkg]
+               local dlg_delmod = create_delete_content_dlg(mod)
+               dlg_delmod:set_parent(tabview)
+               tabview:hide()
+               dlg_delmod:show()
+               packages = nil
+               return true
+       end
+
+       if fields.btn_mod_mgr_use_txp then
+               local txp = packages:get_list()[tabdata.selected_pkg]
+               core.settings:set("texture_path", txp.path)
+               packages = nil
+               return true
+       end
+
+
+       if fields.btn_mod_mgr_disable_txp then
+               core.settings:set("texture_path", "")
+               packages = nil
+               return true
+       end
+
+       if fields["mod_mgt_open_dlg_accepted"] and
+                       fields["mod_mgt_open_dlg_accepted"] ~= "" then
+               pkgmgr.install_mod(fields["mod_mgt_open_dlg_accepted"],nil)
+               return true
+       end
+
+       return false
+end
+
+--------------------------------------------------------------------------------
+return {
+       name = "content",
+       caption = fgettext("Content"),
+       cbf_formspec = get_formspec,
+       cbf_button_handler = handle_buttons,
+       on_change = pkgmgr.update_gamelist
+}
index 64ea09043ed91b79d17cb09e5fccb250a9338a0e..ec99c394630bec3419cceab34ef8f0e6f5678bdb 100644 (file)
@@ -17,7 +17,7 @@
 
 local function current_game()
        local last_game_id = core.settings:get("menu_last_game")
-       local game, index = gamemgr.find_by_gameid(last_game_id)
+       local game, index = pkgmgr.find_by_gameid(last_game_id)
 
        return game
 end
@@ -32,12 +32,12 @@ local function singleplayer_refresh_gamebar()
 
        local function game_buttonbar_button_handler(fields)
                for key,value in pairs(fields) do
-                       for j=1,#gamemgr.games,1 do
-                               if ("game_btnbar_" .. gamemgr.games[j].id == key) then
-                                       mm_texture.update("singleplayer", gamemgr.games[j])
-                                       core.set_topleft_text(gamemgr.games[j].name)
-                                       core.settings:set("menu_last_game",gamemgr.games[j].id)
-                                       menudata.worldlist:set_filtercriteria(gamemgr.games[j].id)
+                       for j=1,#pkgmgr.games,1 do
+                               if ("game_btnbar_" .. pkgmgr.games[j].id == key) then
+                                       mm_texture.update("singleplayer", pkgmgr.games[j])
+                                       core.set_topleft_text(pkgmgr.games[j].name)
+                                       core.settings:set("menu_last_game",pkgmgr.games[j].id)
+                                       menudata.worldlist:set_filtercriteria(pkgmgr.games[j].id)
                                        local index = filterlist.get_current_index(menudata.worldlist,
                                                tonumber(core.settings:get("mainmenu_last_selected_world")))
                                        if not index or index < 1 then
@@ -59,21 +59,21 @@ local function singleplayer_refresh_gamebar()
                game_buttonbar_button_handler,
                {x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15})
 
-       for i=1,#gamemgr.games,1 do
-               local btn_name = "game_btnbar_" .. gamemgr.games[i].id
+       for i=1,#pkgmgr.games,1 do
+               local btn_name = "game_btnbar_" .. pkgmgr.games[i].id
 
                local image = nil
                local text = nil
-               local tooltip = core.formspec_escape(gamemgr.games[i].name)
+               local tooltip = core.formspec_escape(pkgmgr.games[i].name)
 
-               if gamemgr.games[i].menuicon_path ~= nil and
-                       gamemgr.games[i].menuicon_path ~= "" then
-                       image = core.formspec_escape(gamemgr.games[i].menuicon_path)
+               if pkgmgr.games[i].menuicon_path ~= nil and
+                       pkgmgr.games[i].menuicon_path ~= "" then
+                       image = core.formspec_escape(pkgmgr.games[i].menuicon_path)
                else
 
-                       local part1 = gamemgr.games[i].id:sub(1,5)
-                       local part2 = gamemgr.games[i].id:sub(6,10)
-                       local part3 = gamemgr.games[i].id:sub(11)
+                       local part1 = pkgmgr.games[i].id:sub(1,5)
+                       local part2 = pkgmgr.games[i].id:sub(6,10)
+                       local part3 = pkgmgr.games[i].id:sub(11)
 
                        text = part1 .. "\n" .. part2
                        if part3 ~= nil and
@@ -213,7 +213,7 @@ local function main_button_handler(this, fields, name, tabdata)
                                --update last game
                                local world = menudata.worldlist:get_raw_element(gamedata.selected_world)
                                if world then
-                                       local game, index = gamemgr.find_by_gameid(world.gameid)
+                                       local game, index = pkgmgr.find_by_gameid(world.gameid)
                                        core.settings:set("menu_last_game", game.id)
                                end
 
diff --git a/builtin/mainmenu/tab_mods.lua b/builtin/mainmenu/tab_mods.lua
deleted file mode 100644 (file)
index 7685bfc..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
---Minetest
---Copyright (C) 2014 sapier
---
---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 function get_formspec(tabview, name, tabdata)
-
-       if modmgr.global_mods == nil then
-               modmgr.refresh_globals()
-       end
-
-       if tabdata.selected_mod == nil then
-               tabdata.selected_mod = 1
-       end
-
-       local retval =
-               "label[0.05,-0.25;".. fgettext("Installed Mods:") .. "]" ..
-               "tablecolumns[color;tree;text]" ..
-               "table[0,0.25;5.1,5;modlist;" ..
-               modmgr.render_modlist(modmgr.global_mods) ..
-               ";" .. tabdata.selected_mod .. "]"
-
-       local selected_mod = nil
-
-       if filterlist.size(modmgr.global_mods) >= tabdata.selected_mod then
-               selected_mod = modmgr.global_mods:get_list()[tabdata.selected_mod]
-       end
-
-       if selected_mod ~= nil then
-               --check for screenshot beeing available
-               local screenshotfilename = selected_mod.path .. DIR_DELIM .. "screenshot.png"
-               local screenshotfile, error = io.open(screenshotfilename,"r")
-
-               local modscreenshot
-               if error == nil then
-                       screenshotfile:close()
-                       modscreenshot = screenshotfilename
-               end
-
-               if modscreenshot == nil then
-                               modscreenshot = defaulttexturedir .. "no_screenshot.png"
-               end
-
-               retval = retval ..
-                               "image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]" ..
-                               "label[8.25,0.6;" .. selected_mod.name .. "]" ..
-                               "label[5.5,1.7;".. fgettext("Mod Information:") .. "]" ..
-                               "textlist[5.5,2.2;6.2,2.4;description;"
-
-
-               local info = core.get_mod_info(selected_mod.path)
-               local desc = info.description or fgettext("No mod description available")
-               local descriptionlines = core.wrap_text(desc, 42, true)
-               for i = 1, #descriptionlines do
-                       retval = retval .. core.formspec_escape(descriptionlines[i]) .. ","
-               end
-
-               if selected_mod.is_modpack then
-                       retval = retval .. ";0]" ..
-                               "button[9.9,4.65;2,1;btn_mod_mgr_rename_modpack;" ..
-                               fgettext("Rename") .. "]"
-                       retval = retval .. "button[5.5,4.65;4.5,1;btn_mod_mgr_delete_mod;"
-                               .. fgettext("Uninstall Selected Modpack") .. "]"
-               else
-                       --show dependencies
-                       local toadd_hard = table.concat(info.depends, ",")
-                       local toadd_soft = table.concat(info.optional_depends, ",")
-                       if toadd_hard == "" and toadd_soft == "" then
-                               retval = retval .. "," .. fgettext("No dependencies.")
-                       else
-                               if toadd_hard ~= "" then
-                                       retval = retval .. "," .. fgettext("Dependencies:") .. ","
-                                       retval = retval .. toadd_hard
-                               end
-                               if toadd_soft ~= "" then
-                                       if toadd_hard ~= "" then
-                                               retval = retval .. ","
-                                       end
-                                       retval = retval .. "," .. fgettext("Optional dependencies:") .. ","
-                                       retval = retval .. toadd_soft
-                               end
-                       end
-
-                       retval = retval .. ";0]"
-
-                       retval = retval .. "button[5.5,4.65;4.5,1;btn_mod_mgr_delete_mod;"
-                               .. fgettext("Uninstall Selected Mod") .. "]"
-               end
-       end
-       return retval
-end
-
---------------------------------------------------------------------------------
-local function handle_buttons(tabview, fields, tabname, tabdata)
-       if fields["modlist"] ~= nil then
-               local event = core.explode_table_event(fields["modlist"])
-               tabdata.selected_mod = event.row
-               return true
-       end
-
-       if fields["btn_mod_mgr_install_local"] ~= nil then
-               core.show_file_open_dialog("mod_mgt_open_dlg",fgettext("Select Mod File:"))
-               return true
-       end
-
-       if fields["btn_mod_mgr_rename_modpack"] ~= nil then
-               local dlg_renamemp = create_rename_modpack_dlg(tabdata.selected_mod)
-               dlg_renamemp:set_parent(tabview)
-               tabview:hide()
-               dlg_renamemp:show()
-               return true
-       end
-
-       if fields["btn_mod_mgr_delete_mod"] ~= nil then
-               local dlg_delmod = create_delete_mod_dlg(tabdata.selected_mod)
-               dlg_delmod:set_parent(tabview)
-               tabview:hide()
-               dlg_delmod:show()
-               return true
-       end
-
-       if fields["mod_mgt_open_dlg_accepted"] ~= nil and
-               fields["mod_mgt_open_dlg_accepted"] ~= "" then
-               modmgr.installmod(fields["mod_mgt_open_dlg_accepted"],nil)
-               return true
-       end
-
-       return false
-end
-
---------------------------------------------------------------------------------
-return {
-       name = "mods",
-       caption = fgettext("Mods"),
-       cbf_formspec = get_formspec,
-       cbf_button_handler = handle_buttons,
-       on_change = gamemgr.update_gamelist
-}
diff --git a/builtin/mainmenu/tab_texturepacks.lua b/builtin/mainmenu/tab_texturepacks.lua
deleted file mode 100644 (file)
index 6be5559..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
---Minetest
---Copyright (C) 2014 sapier
---
---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 function filter_texture_pack_list(list)
-       local retval = {}
-
-       for _, item in ipairs(list) do
-               if item ~= "base" then
-                       retval[#retval + 1] = item
-               end
-       end
-
-       table.sort(retval)
-       table.insert(retval, 1, fgettext("None"))
-
-       return retval
-end
-
---------------------------------------------------------------------------------
-local function render_texture_pack_list(list)
-       local retval = ""
-
-       for i, v in ipairs(list) do
-               if v:sub(1, 1) ~= "." then
-                       if retval ~= "" then
-                               retval = retval .. ","
-                       end
-
-                       retval = retval .. core.formspec_escape(v)
-               end
-       end
-
-       return retval
-end
-
---------------------------------------------------------------------------------
-local function get_formspec(tabview, name, tabdata)
-
-       local retval = "label[4,-0.25;" .. fgettext("Select Texture Pack:") .. "]" ..
-                       "textlist[4,0.25;7.5,5.0;TPs;"
-
-       local current_texture_path = core.settings:get("texture_path")
-       local list = filter_texture_pack_list(core.get_dir_list(core.get_texturepath(), true))
-       local index = tonumber(core.settings:get("mainmenu_last_selected_TP"))
-
-       if not index then index = 1 end
-
-       if current_texture_path == "" then
-               retval = retval ..
-                       render_texture_pack_list(list) ..
-                       ";" .. index .. "]" ..
-                       "textarea[0.6,2.85;3.7,1.5;;" ..
-                       fgettext("Default textures will be used.") ..
-                       ";]"
-               return retval
-       end
-
-       local infofile = current_texture_path .. DIR_DELIM .. "description.txt"
-       -- This adds backwards compatibility for old texture pack description files named
-       -- "info.txt", and should be removed once all such texture packs have been updated
-       if not file_exists(infofile) then
-               infofile = current_texture_path .. DIR_DELIM .. "info.txt"
-               if file_exists(infofile) then
-                       core.log("deprecated", "info.txt is deprecated. description.txt should be used instead.")
-               end
-       end
-
-       local infotext = ""
-       local f = io.open(infofile, "r")
-       if not f then
-               infotext = fgettext("No information available")
-       else
-               infotext = f:read("*all")
-               f:close()
-       end
-
-       local screenfile = current_texture_path .. DIR_DELIM .. "screenshot.png"
-       local no_screenshot
-       if not file_exists(screenfile) then
-               screenfile = nil
-               no_screenshot = defaulttexturedir .. "no_screenshot.png"
-       end
-
-       return  retval ..
-                       render_texture_pack_list(list) ..
-                       ";" .. index .. "]" ..
-                       "image[0.25,0.25;4.05,2.7;" .. core.formspec_escape(screenfile or no_screenshot) .. "]" ..
-                       "textarea[0.6,2.85;3.7,1.5;;" .. core.formspec_escape(infotext or "") .. ";]"
-end
-
---------------------------------------------------------------------------------
-local function main_button_handler(tabview, fields, name, tabdata)
-       if fields["TPs"] then
-               local event = core.explode_textlist_event(fields["TPs"])
-               if event.type == "CHG" or event.type == "DCL" then
-                       local index = core.get_textlist_index("TPs")
-                       core.settings:set("mainmenu_last_selected_TP", index)
-                       local list = filter_texture_pack_list(core.get_dir_list(core.get_texturepath(), true))
-                       local current_index = core.get_textlist_index("TPs")
-                       if current_index and #list >= current_index then
-                               local new_path = core.get_texturepath() .. DIR_DELIM .. list[current_index]
-                               if list[current_index] == fgettext("None") then
-                                       new_path = ""
-                               end
-                               core.settings:set("texture_path", new_path)
-                       end
-               end
-               return true
-       end
-       return false
-end
-
---------------------------------------------------------------------------------
-return {
-       name = "texturepacks",
-       caption = fgettext("Texture Packs"),
-       cbf_formspec = get_formspec,
-       cbf_button_handler = main_button_handler,
-       on_change = nil
-}
index 0c7d44adf15c1df21591379e304dbe722b176574..70eb48c128febb76cdf9fc4feac62de1c0f6ebd4 100644 (file)
@@ -55,10 +55,10 @@ Where `gameid` is unique to each game.
 
 The game directory can contain the following files:
 
-* `game.conf`, which contains:
-    * `name = <Human-readable full name of the game>` e.g. `name = Minetest`
-    * Optionally, game.conf can also contain
-      `disallowed_mapgens = <comma-separated mapgens>`
+* `game.conf`, with the following keys:
+    * `name` - required, human readable name  e.g. `name = Minetest`
+    * `description` - Short description to be shown in the content tab
+    * `disallowed_mapgens = <comma-separated mapgens>`
       e.g. `disallowed_mapgens = v5,v6,flat`
       These mapgens are removed from the list of mapgens for the game.
 * `minetest.conf`:
@@ -2544,6 +2544,9 @@ Helper functions
     * returns time with microsecond precision. May not return wall time.
 * `table.copy(table)`: returns a table
     * returns a deep copy of `table`
+* `table.insert_all(table, other_table)`:
+    * Appends all values in `other_table` to `table` - uses `#table + 1` to
+      find new indices.
 * `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a
   position.
     * returns the exact position on the surface of a pointed node
index 49c6fbef0a8d6012fab5c7d78f5c51c05212fd55..6b81e6bf518d28f8a2558518025c64dc5e847772 100644 (file)
@@ -33,14 +33,6 @@ core.close()
 Filesystem:
 core.get_builtin_path()
 ^ returns path to builtin root
-core.get_modpath() (possible in async calls)
-^ returns path to global modpath
-core.get_clientmodpath() (possible in async calls)
-^ returns path to global client-side modpath
-core.get_gamepath() (possible in async calls)
-^ returns path to global gamepath
-core.get_texturepath() (possible in async calls)
-^ returns path to default textures
 core.create_dir(absolute_path) (possible in async calls)
 ^ absolute_path to directory to create (needs to be absolute)
 ^ returns true/false
@@ -71,6 +63,8 @@ core.get_video_drivers()
 ^ returns list of available video drivers' settings name and 'friendly' display name
 ^ e.g. { {name="opengl", friendly_name="OpenGL"}, {name="software", friendly_name="Software Renderer"} }
 ^ first element of returned list is guaranteed to be the NULL driver
+core.get_mapgen_names([include_hidden=false]) -> table of map generator algorithms
+    registered in the core (possible in async calls)
 
 Formspec:
 core.update_formspec(formspec)
@@ -111,29 +105,58 @@ core.get_screen_info()
        window_height   = <current window height>
        }
 
-Packages:
-core.get_game(index)
-^ returns {
-       id               = <id>,
-       path             = <full path to game>,
-       gamemods_path    = <path>,
-       name             = <name of game>,
-       menuicon_path    = <full path to menuicon>,
-       DEPRECATED:
-       addon_mods_paths = {[1] = <path>,},
-}
-core.get_games() -> table of all games in upper format (possible in async calls)
-core.get_mapgen_names([include_hidden=false]) -> table of map generator algorithms
-    registered in the core (possible in async calls)
-core.get_mod_info(path)
-^ returns  {
-       name             = "name of mod",
-       type             = "mod" or "modpack",
-       description      = "description",
-       path             = "path/to/mod",
-       depends          = {"mod", "names"},
-       optional_depends = {"mod", "names"},
-}
+### Content and Packages
+
+Content - an installed mod, modpack, game, or texture pack (txt)
+Package - content which is downloadable from the content db, may or may not be installed.
+
+* core.get_modpath() (possible in async calls)
+       * returns path to global modpath
+* core.get_clientmodpath() (possible in async calls)
+       * returns path to global client-side modpath
+* core.get_gamepath() (possible in async calls)
+       * returns path to global gamepath
+* core.get_texturepath() (possible in async calls)
+       * returns path to default textures
+* core.get_game(index)
+       * returns:
+
+               {
+                       id               = <id>,
+                       path             = <full path to game>,
+                       gamemods_path    = <path>,
+                       name             = <name of game>,
+                       menuicon_path    = <full path to menuicon>,
+                       author           = "author",
+                       DEPRECATED:
+                       addon_mods_paths = {[1] = <path>,},
+               }
+
+* core.get_games() -> table of all games in upper format (possible in async calls)
+* core.get_content_info(path)
+       * returns
+
+               {
+                       name             = "name of content",
+                       type             = "mod" or "modpack" or "game" or "txp",
+                       description      = "description",
+                       author           = "author",
+                       path             = "path/to/content",
+                       depends          = {"mod", "names"}, -- mods only
+                       optional_depends = {"mod", "names"}, -- mods only
+               }
+
+* core.get_package_list() -> downloads package list from content db
+       * returns a list of:
+
+               {
+                       name = "basename",
+                       title = "human readable title",
+                       author = "username",
+                       type = "", -- mod, game, txp
+                       short_description = "description",
+                       url = "",
+               }
 
 Favorites:
 core.get_favorites(location) -> list of favorites (possible in async calls)
index 1813c29e42a75dcb33778f3ebbd67d126faa43ec..89fdc08260cfee99ba858b060ec50518c97e77e1 100644 (file)
@@ -9,6 +9,7 @@ Texture pack directory structure
 
     textures
     |-- Texture Pack
+    |   |-- texture_pack.conf
     |   |-- screenshot.png
     |   |-- description.txt
     |   |-- override.txt
@@ -21,9 +22,17 @@ This is a directory containing the entire contents of a single texture pack.
 It can be chosen more or less freely and will also become the name of the
 texture pack. The name must not be “base”.
 
+### `texture_pack.conf`
+A key-value config file with the following keys:
+
+* `title` - human readable title
+* `description` - short description, shown in the content tab
+
 ### `description.txt`
+**Deprecated**, you should use texture_pack.conf instead.
+
 A file containing a short description of the texture pack to be shown in the
-texture packs tab.
+content tab.
 
 ### `screenshot.png`
 A preview image showing an in-game screenshot of this texture pack; it will be
index 1b3d10ef5bd485eab168b02dbc01bf506ed9aba9..0b2aab80f715fc59ef5510b77d0593b9aed261ab 100644 (file)
@@ -358,6 +358,7 @@ add_custom_target(GenerateVersion
 
 
 add_subdirectory(threading)
+add_subdirectory(content)
 add_subdirectory(database)
 add_subdirectory(gui)
 add_subdirectory(mapgen)
@@ -372,6 +373,7 @@ set(common_SRCS
        ${database_SRCS}
        ${mapgen_SRCS}
        ${server_SRCS}
+       ${content_SRCS}
        ban.cpp
        chat.cpp
        clientiface.cpp
@@ -403,7 +405,6 @@ set(common_SRCS
        mapsector.cpp
        metadata.cpp
        modchannels.cpp
-       mods.cpp
        nameidmapping.cpp
        nodedef.cpp
        nodemetadata.cpp
@@ -428,7 +429,6 @@ set(common_SRCS
        serverobject.cpp
        settings.cpp
        staticobject.cpp
-       subgame.cpp
        terminal_chat_console.cpp
        tileanimation.cpp
        tool.cpp
index 87e5e12bca1941f4ef7654ba0a3292f24ba31799..d2f585de7a11b60699c19466980f190bfb81959e 100644 (file)
@@ -43,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "mapblock.h"
 #include "minimap.h"
 #include "modchannels.h"
-#include "mods.h"
+#include "content/mods.h"
 #include "profiler.h"
 #include "shader.h"
 #include "gettext.h"
diff --git a/src/content/CMakeLists.txt b/src/content/CMakeLists.txt
new file mode 100644 (file)
index 0000000..5adcf6b
--- /dev/null
@@ -0,0 +1,7 @@
+set(content_SRCS
+       ${CMAKE_CURRENT_SOURCE_DIR}/content.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/packages.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/subgames.cpp
+       PARENT_SCOPE
+)
diff --git a/src/content/content.cpp b/src/content/content.cpp
new file mode 100644 (file)
index 0000000..d45c5fe
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+Minetest
+Copyright (C) 2018 rubenwardy <rw@rubenwardy.com>
+
+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.
+*/
+
+#include <fstream>
+#include "content/content.h"
+#include "content/subgames.h"
+#include "content/mods.h"
+#include "filesys.h"
+#include "settings.h"
+
+enum ContentType
+{
+       ECT_UNKNOWN,
+       ECT_MOD,
+       ECT_MODPACK,
+       ECT_GAME,
+       ECT_TXP
+};
+
+ContentType getContentType(const ContentSpec &spec)
+{
+       std::ifstream modpack_is((spec.path + DIR_DELIM + "modpack.txt").c_str());
+       if (modpack_is.good()) {
+               modpack_is.close();
+               return ECT_MODPACK;
+       }
+
+       std::ifstream init_is((spec.path + DIR_DELIM + "init.lua").c_str());
+       if (init_is.good()) {
+               init_is.close();
+               return ECT_MOD;
+       }
+
+       std::ifstream game_is((spec.path + DIR_DELIM + "game.conf").c_str());
+       if (game_is.good()) {
+               game_is.close();
+               return ECT_GAME;
+       }
+
+       std::ifstream txp_is((spec.path + DIR_DELIM + "texture_pack.conf").c_str());
+       if (txp_is.good()) {
+               txp_is.close();
+               return ECT_TXP;
+       }
+
+       return ECT_UNKNOWN;
+}
+
+void parseContentInfo(ContentSpec &spec)
+{
+       std::string conf_path;
+
+       switch (getContentType(spec)) {
+       case ECT_MOD:
+               spec.type = "mod";
+               conf_path = spec.path + DIR_DELIM + "mod.conf";
+               break;
+       case ECT_MODPACK:
+               spec.type = "modpack";
+               conf_path = spec.path + DIR_DELIM + "mod.conf";
+               break;
+       case ECT_GAME:
+               spec.type = "game";
+               conf_path = spec.path + DIR_DELIM + "game.conf";
+               break;
+       case ECT_TXP:
+               spec.type = "txp";
+               conf_path = spec.path + DIR_DELIM + "texture_pack.conf";
+               break;
+       default:
+               spec.type = "unknown";
+               break;
+       }
+
+       Settings conf;
+       if (!conf_path.empty() && conf.readConfigFile(conf_path.c_str())) {
+               if (conf.exists("name"))
+                       spec.name = conf.get("name");
+
+               if (conf.exists("description"))
+                       spec.desc = conf.get("description");
+
+               if (conf.exists("author"))
+                       spec.author = conf.get("author");
+       }
+
+       if (spec.desc.empty()) {
+               std::ifstream is((spec.path + DIR_DELIM + "description.txt").c_str());
+               spec.desc = std::string((std::istreambuf_iterator<char>(is)),
+                               std::istreambuf_iterator<char>());
+       }
+}
diff --git a/src/content/content.h b/src/content/content.h
new file mode 100644 (file)
index 0000000..782a4fd
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+Minetest
+Copyright (C) 2018 rubenwardy <rw@rubenwardy.com>
+
+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.
+*/
+
+#pragma once
+#include "config.h"
+#include "convert_json.h"
+
+struct ContentSpec
+{
+       std::string type;
+       std::string author;
+       std::string name;
+       std::string desc;
+       std::string path;
+};
+
+void parseContentInfo(ContentSpec &spec);
diff --git a/src/content/mods.cpp b/src/content/mods.cpp
new file mode 100644 (file)
index 0000000..694bbcc
--- /dev/null
@@ -0,0 +1,469 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#include <cctype>
+#include <fstream>
+#include <json/json.h>
+#include <algorithm>
+#include "content/mods.h"
+#include "filesys.h"
+#include "log.h"
+#include "content/subgames.h"
+#include "settings.h"
+#include "porting.h"
+#include "convert_json.h"
+
+bool parseDependsString(std::string &dep, std::unordered_set<char> &symbols)
+{
+       dep = trim(dep);
+       symbols.clear();
+       size_t pos = dep.size();
+       while (pos > 0 &&
+                       !string_allowed(dep.substr(pos - 1, 1), MODNAME_ALLOWED_CHARS)) {
+               // last character is a symbol, not part of the modname
+               symbols.insert(dep[pos - 1]);
+               --pos;
+       }
+       dep = trim(dep.substr(0, pos));
+       return !dep.empty();
+}
+
+void parseModContents(ModSpec &spec)
+{
+       // NOTE: this function works in mutual recursion with getModsInPath
+       Settings info;
+       info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str());
+
+       if (info.exists("name"))
+               spec.name = info.get("name");
+
+       if (info.exists("author"))
+               spec.author = info.get("author");
+
+       spec.depends.clear();
+       spec.optdepends.clear();
+       spec.is_modpack = false;
+       spec.modpack_content.clear();
+
+       // Handle modpacks (defined by containing modpack.txt)
+       std::ifstream modpack_is((spec.path + DIR_DELIM + "modpack.txt").c_str());
+       if (modpack_is.good()) {    // a modpack, recursively get the mods in it
+               modpack_is.close(); // We don't actually need the file
+               spec.is_modpack = true;
+               spec.modpack_content = getModsInPath(spec.path, true);
+               // modpacks have no dependencies; they are defined and
+               // tracked separately for each mod in the modpack
+
+       } else {
+               // Attempt to load dependencies from mod.conf
+               bool mod_conf_has_depends = false;
+               if (info.exists("depends")) {
+                       mod_conf_has_depends = true;
+                       std::string dep = info.get("depends");
+                       // clang-format off
+                       dep.erase(std::remove_if(dep.begin(), dep.end(),
+                                       static_cast<int (*)(int)>(&std::isspace)), dep.end());
+                       // clang-format on
+                       for (const auto &dependency : str_split(dep, ',')) {
+                               spec.depends.insert(dependency);
+                       }
+               }
+
+               if (info.exists("optional_depends")) {
+                       mod_conf_has_depends = true;
+                       std::string dep = info.get("optional_depends");
+                       // clang-format off
+                       dep.erase(std::remove_if(dep.begin(), dep.end(),
+                                       static_cast<int (*)(int)>(&std::isspace)), dep.end());
+                       // clang-format on
+                       for (const auto &dependency : str_split(dep, ',')) {
+                               spec.optdepends.insert(dependency);
+                       }
+               }
+
+               // Fallback to depends.txt
+               if (!mod_conf_has_depends) {
+                       std::vector<std::string> dependencies;
+
+                       std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str());
+                       while (is.good()) {
+                               std::string dep;
+                               std::getline(is, dep);
+                               dependencies.push_back(dep);
+                       }
+
+                       for (auto &dependency : dependencies) {
+                               std::unordered_set<char> symbols;
+                               if (parseDependsString(dependency, symbols)) {
+                                       if (symbols.count('?') != 0) {
+                                               spec.optdepends.insert(dependency);
+                                       } else {
+                                               spec.depends.insert(dependency);
+                                       }
+                               }
+                       }
+               }
+
+               if (info.exists("description")) {
+                       spec.desc = info.get("description");
+               } else {
+                       std::ifstream is((spec.path + DIR_DELIM + "description.txt")
+                                                        .c_str());
+                       spec.desc = std::string((std::istreambuf_iterator<char>(is)),
+                                       std::istreambuf_iterator<char>());
+               }
+       }
+}
+
+std::map<std::string, ModSpec> getModsInPath(
+               const std::string &path, bool part_of_modpack)
+{
+       // NOTE: this function works in mutual recursion with parseModContents
+
+       std::map<std::string, ModSpec> result;
+       std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path);
+       std::string modpath;
+
+       for (const fs::DirListNode &dln : dirlist) {
+               if (!dln.dir)
+                       continue;
+
+               const std::string &modname = dln.name;
+               // Ignore all directories beginning with a ".", especially
+               // VCS directories like ".git" or ".svn"
+               if (modname[0] == '.')
+                       continue;
+
+               modpath.clear();
+               modpath.append(path).append(DIR_DELIM).append(modname);
+
+               ModSpec spec(modname, modpath, part_of_modpack);
+               parseModContents(spec);
+               result.insert(std::make_pair(modname, spec));
+       }
+       return result;
+}
+
+std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
+{
+       std::vector<ModSpec> result;
+       for (const auto &it : mods) {
+               const ModSpec &mod = it.second;
+               if (mod.is_modpack) {
+                       std::vector<ModSpec> content = flattenMods(mod.modpack_content);
+                       result.reserve(result.size() + content.size());
+                       result.insert(result.end(), content.begin(), content.end());
+
+               } else // not a modpack
+               {
+                       result.push_back(mod);
+               }
+       }
+       return result;
+}
+
+ModConfiguration::ModConfiguration(const std::string &worldpath)
+{
+}
+
+void ModConfiguration::printUnsatisfiedModsError() const
+{
+       for (const ModSpec &mod : m_unsatisfied_mods) {
+               errorstream << "mod \"" << mod.name
+                           << "\" has unsatisfied dependencies: ";
+               for (const std::string &unsatisfied_depend : mod.unsatisfied_depends)
+                       errorstream << " \"" << unsatisfied_depend << "\"";
+               errorstream << std::endl;
+       }
+}
+
+void ModConfiguration::addModsInPath(const std::string &path)
+{
+       addMods(flattenMods(getModsInPath(path)));
+}
+
+void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods)
+{
+       // Maintain a map of all existing m_unsatisfied_mods.
+       // Keys are mod names and values are indices into m_unsatisfied_mods.
+       std::map<std::string, u32> existing_mods;
+       for (u32 i = 0; i < m_unsatisfied_mods.size(); ++i) {
+               existing_mods[m_unsatisfied_mods[i].name] = i;
+       }
+
+       // Add new mods
+       for (int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack) {
+               // First iteration:
+               // Add all the mods that come from modpacks
+               // Second iteration:
+               // Add all the mods that didn't come from modpacks
+
+               std::set<std::string> seen_this_iteration;
+
+               for (const ModSpec &mod : new_mods) {
+                       if (mod.part_of_modpack != (bool)want_from_modpack)
+                               continue;
+
+                       if (existing_mods.count(mod.name) == 0) {
+                               // GOOD CASE: completely new mod.
+                               m_unsatisfied_mods.push_back(mod);
+                               existing_mods[mod.name] = m_unsatisfied_mods.size() - 1;
+                       } else if (seen_this_iteration.count(mod.name) == 0) {
+                               // BAD CASE: name conflict in different levels.
+                               u32 oldindex = existing_mods[mod.name];
+                               const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
+                               warningstream << "Mod name conflict detected: \""
+                                             << mod.name << "\"" << std::endl
+                                             << "Will not load: " << oldmod.path
+                                             << std::endl
+                                             << "Overridden by: " << mod.path
+                                             << std::endl;
+                               m_unsatisfied_mods[oldindex] = mod;
+
+                               // If there was a "VERY BAD CASE" name conflict
+                               // in an earlier level, ignore it.
+                               m_name_conflicts.erase(mod.name);
+                       } else {
+                               // VERY BAD CASE: name conflict in the same level.
+                               u32 oldindex = existing_mods[mod.name];
+                               const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
+                               warningstream << "Mod name conflict detected: \""
+                                             << mod.name << "\"" << std::endl
+                                             << "Will not load: " << oldmod.path
+                                             << std::endl
+                                             << "Will not load: " << mod.path
+                                             << std::endl;
+                               m_unsatisfied_mods[oldindex] = mod;
+                               m_name_conflicts.insert(mod.name);
+                       }
+
+                       seen_this_iteration.insert(mod.name);
+               }
+       }
+}
+
+void ModConfiguration::addModsFromConfig(
+               const std::string &settings_path, const std::set<std::string> &mods)
+{
+       Settings conf;
+       std::set<std::string> load_mod_names;
+
+       conf.readConfigFile(settings_path.c_str());
+       std::vector<std::string> names = conf.getNames();
+       for (const std::string &name : names) {
+               if (name.compare(0, 9, "load_mod_") == 0 && conf.getBool(name))
+                       load_mod_names.insert(name.substr(9));
+       }
+
+       std::vector<ModSpec> addon_mods;
+       for (const std::string &i : mods) {
+               std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(i));
+               for (std::vector<ModSpec>::const_iterator it = addon_mods_in_path.begin();
+                               it != addon_mods_in_path.end(); ++it) {
+                       const ModSpec &mod = *it;
+                       if (load_mod_names.count(mod.name) != 0)
+                               addon_mods.push_back(mod);
+                       else
+                               conf.setBool("load_mod_" + mod.name, false);
+               }
+       }
+       conf.updateConfigFile(settings_path.c_str());
+
+       addMods(addon_mods);
+       checkConflictsAndDeps();
+
+       // complain about mods declared to be loaded, but not found
+       for (const ModSpec &addon_mod : addon_mods)
+               load_mod_names.erase(addon_mod.name);
+
+       std::vector<ModSpec> unsatisfiedMods = getUnsatisfiedMods();
+
+       for (const ModSpec &unsatisfiedMod : unsatisfiedMods)
+               load_mod_names.erase(unsatisfiedMod.name);
+
+       if (!load_mod_names.empty()) {
+               errorstream << "The following mods could not be found:";
+               for (const std::string &mod : load_mod_names)
+                       errorstream << " \"" << mod << "\"";
+               errorstream << std::endl;
+       }
+}
+
+void ModConfiguration::checkConflictsAndDeps()
+{
+       // report on name conflicts
+       if (!m_name_conflicts.empty()) {
+               std::string s = "Unresolved name conflicts for mods ";
+               for (std::unordered_set<std::string>::const_iterator it =
+                                               m_name_conflicts.begin();
+                               it != m_name_conflicts.end(); ++it) {
+                       if (it != m_name_conflicts.begin())
+                               s += ", ";
+                       s += std::string("\"") + (*it) + "\"";
+               }
+               s += ".";
+               throw ModError(s);
+       }
+
+       // get the mods in order
+       resolveDependencies();
+}
+
+void ModConfiguration::resolveDependencies()
+{
+       // Step 1: Compile a list of the mod names we're working with
+       std::set<std::string> modnames;
+       for (const ModSpec &mod : m_unsatisfied_mods) {
+               modnames.insert(mod.name);
+       }
+
+       // Step 2: get dependencies (including optional dependencies)
+       // of each mod, split mods into satisfied and unsatisfied
+       std::list<ModSpec> satisfied;
+       std::list<ModSpec> unsatisfied;
+       for (ModSpec mod : m_unsatisfied_mods) {
+               mod.unsatisfied_depends = mod.depends;
+               // check which optional dependencies actually exist
+               for (const std::string &optdep : mod.optdepends) {
+                       if (modnames.count(optdep) != 0)
+                               mod.unsatisfied_depends.insert(optdep);
+               }
+               // if a mod has no depends it is initially satisfied
+               if (mod.unsatisfied_depends.empty())
+                       satisfied.push_back(mod);
+               else
+                       unsatisfied.push_back(mod);
+       }
+
+       // Step 3: mods without unmet dependencies can be appended to
+       // the sorted list.
+       while (!satisfied.empty()) {
+               ModSpec mod = satisfied.back();
+               m_sorted_mods.push_back(mod);
+               satisfied.pop_back();
+               for (auto it = unsatisfied.begin(); it != unsatisfied.end();) {
+                       ModSpec &mod2 = *it;
+                       mod2.unsatisfied_depends.erase(mod.name);
+                       if (mod2.unsatisfied_depends.empty()) {
+                               satisfied.push_back(mod2);
+                               it = unsatisfied.erase(it);
+                       } else {
+                               ++it;
+                       }
+               }
+       }
+
+       // Step 4: write back list of unsatisfied mods
+       m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end());
+}
+
+#ifndef SERVER
+ClientModConfiguration::ClientModConfiguration(const std::string &path) :
+               ModConfiguration(path)
+{
+       std::set<std::string> paths;
+       std::string path_user = porting::path_user + DIR_DELIM + "clientmods";
+       paths.insert(path);
+       paths.insert(path_user);
+
+       std::string settings_path = path_user + DIR_DELIM + "mods.conf";
+       addModsFromConfig(settings_path, paths);
+}
+#endif
+
+ModMetadata::ModMetadata(const std::string &mod_name) : m_mod_name(mod_name)
+{
+}
+
+void ModMetadata::clear()
+{
+       Metadata::clear();
+       m_modified = true;
+}
+
+bool ModMetadata::save(const std::string &root_path)
+{
+       Json::Value json;
+       for (StringMap::const_iterator it = m_stringvars.begin();
+                       it != m_stringvars.end(); ++it) {
+               json[it->first] = it->second;
+       }
+
+       if (!fs::PathExists(root_path)) {
+               if (!fs::CreateAllDirs(root_path)) {
+                       errorstream << "ModMetadata[" << m_mod_name
+                                   << "]: Unable to save. '" << root_path
+                                   << "' tree cannot be created." << std::endl;
+                       return false;
+               }
+       } else if (!fs::IsDir(root_path)) {
+               errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '"
+                           << root_path << "' is not a directory." << std::endl;
+               return false;
+       }
+
+       bool w_ok = fs::safeWriteToFile(
+                       root_path + DIR_DELIM + m_mod_name, fastWriteJson(json));
+
+       if (w_ok) {
+               m_modified = false;
+       } else {
+               errorstream << "ModMetadata[" << m_mod_name << "]: failed write file."
+                           << std::endl;
+       }
+       return w_ok;
+}
+
+bool ModMetadata::load(const std::string &root_path)
+{
+       m_stringvars.clear();
+
+       std::ifstream is((root_path + DIR_DELIM + m_mod_name).c_str(),
+                       std::ios_base::binary);
+       if (!is.good()) {
+               return false;
+       }
+
+       Json::Value root;
+       Json::CharReaderBuilder builder;
+       builder.settings_["collectComments"] = false;
+       std::string errs;
+
+       if (!Json::parseFromStream(builder, is, &root, &errs)) {
+               errorstream << "ModMetadata[" << m_mod_name
+                           << "]: failed read data "
+                              "(Json decoding failure). Message: "
+                           << errs << std::endl;
+               return false;
+       }
+
+       const Json::Value::Members attr_list = root.getMemberNames();
+       for (const auto &it : attr_list) {
+               Json::Value attr_value = root[it];
+               m_stringvars[it] = attr_value.asString();
+       }
+
+       return true;
+}
+
+bool ModMetadata::setString(const std::string &name, const std::string &var)
+{
+       m_modified = Metadata::setString(name, var);
+       return m_modified;
+}
diff --git a/src/content/mods.h b/src/content/mods.h
new file mode 100644 (file)
index 0000000..a7cad07
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes.h"
+#include <list>
+#include <set>
+#include <vector>
+#include <string>
+#include <map>
+#include <json/json.h>
+#include <unordered_set>
+#include "util/basic_macros.h"
+#include "config.h"
+#include "metadata.h"
+
+#define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_"
+
+struct ModSpec
+{
+       std::string name;
+       std::string author;
+       std::string path;
+       std::string desc;
+
+       // if normal mod:
+       std::unordered_set<std::string> depends;
+       std::unordered_set<std::string> optdepends;
+       std::unordered_set<std::string> unsatisfied_depends;
+
+       bool part_of_modpack = false;
+       bool is_modpack = false;
+
+       // if modpack:
+       std::map<std::string, ModSpec> modpack_content;
+       ModSpec(const std::string &name = "", const std::string &path = "") :
+                       name(name), path(path)
+       {
+       }
+       ModSpec(const std::string &name, const std::string &path, bool part_of_modpack) :
+                       name(name), path(path), part_of_modpack(part_of_modpack)
+       {
+       }
+};
+
+// Retrieves depends, optdepends, is_modpack and modpack_content
+void parseModContents(ModSpec &mod);
+
+std::map<std::string, ModSpec> getModsInPath(
+               const std::string &path, bool part_of_modpack = false);
+
+// replaces modpack Modspecs with their content
+std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods);
+
+// a ModConfiguration is a subset of installed mods, expected to have
+// all dependencies fullfilled, so it can be used as a list of mods to
+// load when the game starts.
+class ModConfiguration
+{
+public:
+       // checks if all dependencies are fullfilled.
+       bool isConsistent() const { return m_unsatisfied_mods.empty(); }
+
+       const std::vector<ModSpec> &getMods() const { return m_sorted_mods; }
+
+       const std::vector<ModSpec> &getUnsatisfiedMods() const
+       {
+               return m_unsatisfied_mods;
+       }
+
+       void printUnsatisfiedModsError() const;
+
+protected:
+       ModConfiguration(const std::string &worldpath);
+       // adds all mods in the given path. used for games, modpacks
+       // and world-specific mods (worldmods-folders)
+       void addModsInPath(const std::string &path);
+
+       // adds all mods in the set.
+       void addMods(const std::vector<ModSpec> &new_mods);
+
+       void addModsFromConfig(const std::string &settings_path,
+                       const std::set<std::string> &mods);
+
+       void checkConflictsAndDeps();
+
+protected:
+       // list of mods sorted such that they can be loaded in the
+       // given order with all dependencies being fullfilled. I.e.,
+       // every mod in this list has only dependencies on mods which
+       // appear earlier in the vector.
+       std::vector<ModSpec> m_sorted_mods;
+
+private:
+       // move mods from m_unsatisfied_mods to m_sorted_mods
+       // in an order that satisfies dependencies
+       void resolveDependencies();
+
+       // mods with unmet dependencies. Before dependencies are resolved,
+       // this is where all mods are stored. Afterwards this contains
+       // only the ones with really unsatisfied dependencies.
+       std::vector<ModSpec> m_unsatisfied_mods;
+
+       // set of mod names for which an unresolved name conflict
+       // exists. A name conflict happens when two or more mods
+       // at the same level have the same name but different paths.
+       // Levels (mods in higher levels override mods in lower levels):
+       // 1. game mod in modpack; 2. game mod;
+       // 3. world mod in modpack; 4. world mod;
+       // 5. addon mod in modpack; 6. addon mod.
+       std::unordered_set<std::string> m_name_conflicts;
+
+       // Deleted default constructor
+       ModConfiguration() = default;
+};
+
+#ifndef SERVER
+class ClientModConfiguration : public ModConfiguration
+{
+public:
+       ClientModConfiguration(const std::string &path);
+};
+#endif
+
+class ModMetadata : public Metadata
+{
+public:
+       ModMetadata() = delete;
+       ModMetadata(const std::string &mod_name);
+       ~ModMetadata() = default;
+
+       virtual void clear();
+
+       bool save(const std::string &root_path);
+       bool load(const std::string &root_path);
+
+       bool isModified() const { return m_modified; }
+       const std::string &getModName() const { return m_mod_name; }
+
+       virtual bool setString(const std::string &name, const std::string &var);
+
+private:
+       std::string m_mod_name;
+       bool m_modified = false;
+};
diff --git a/src/content/packages.cpp b/src/content/packages.cpp
new file mode 100644 (file)
index 0000000..a769c31
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+Minetest
+Copyright (C) 2018 rubenwardy <rw@rubenwardy.com>
+
+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.
+*/
+
+#include "content/packages.h"
+#include "log.h"
+#include "filesys.h"
+#include "porting.h"
+#include "settings.h"
+#include "content/mods.h"
+#include "content/subgames.h"
+
+#if USE_CURL
+std::vector<Package> getPackagesFromURL(const std::string &url)
+{
+       std::vector<std::string> extra_headers;
+       extra_headers.emplace_back("Accept: application/json");
+
+       Json::Value json = fetchJsonValue(url, &extra_headers);
+       if (!json.isArray()) {
+               errorstream << "Invalid JSON download " << std::endl;
+               return std::vector<Package>();
+       }
+
+       std::vector<Package> packages;
+
+       // Note: `unsigned int` is required to index JSON
+       for (unsigned int i = 0; i < json.size(); ++i) {
+               Package package;
+
+               package.name = json[i]["name"].asString();
+               package.title = json[i]["title"].asString();
+               package.author = json[i]["author"].asString();
+               package.type = json[i]["type"].asString();
+               package.shortDesc = json[i]["shortDesc"].asString();
+               package.url = json[i]["url"].asString();
+
+               Json::Value jScreenshots = json[i]["screenshots"];
+               for (unsigned int j = 0; j < jScreenshots.size(); ++j) {
+                       package.screenshots.push_back(jScreenshots[j].asString());
+               }
+
+               if (package.valid()) {
+                       packages.push_back(package);
+               } else {
+                       errorstream << "Invalid package at " << i << std::endl;
+               }
+       }
+
+       return packages;
+}
+
+#endif
diff --git a/src/content/packages.h b/src/content/packages.h
new file mode 100644 (file)
index 0000000..6774678
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+Minetest
+Copyright (C) 2018 rubenwardy <rw@rubenwardy.com>
+
+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.
+*/
+
+#pragma once
+#include "config.h"
+#include "convert_json.h"
+
+struct Package
+{
+       std::string name; // Technical name
+       std::string title;
+       std::string author;
+       std::string type; // One of "mod", "game", or "txp"
+
+       std::string shortDesc;
+       std::string url; // download URL
+       std::vector<std::string> screenshots;
+
+       bool valid()
+       {
+               return !(name.empty() || title.empty() || author.empty() ||
+                               type.empty() || url.empty());
+       }
+};
+
+#if USE_CURL
+std::vector<Package> getPackagesFromURL(const std::string &url);
+#else
+inline std::vector<Package> getPackagesFromURL(const std::string &url)
+{
+       return std::vector<Package>();
+}
+#endif
diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
new file mode 100644 (file)
index 0000000..fd6231a
--- /dev/null
@@ -0,0 +1,332 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#include "content/subgames.h"
+#include "porting.h"
+#include "filesys.h"
+#include "settings.h"
+#include "log.h"
+#include "util/strfnd.h"
+#include "defaultsettings.h" // for override_default_settings
+#include "mapgen/mapgen.h"   // for MapgenParams
+#include "util/string.h"
+
+#ifndef SERVER
+#include "client/tile.h" // getImagePath
+#endif
+
+bool getGameMinetestConfig(const std::string &game_path, Settings &conf)
+{
+       std::string conf_path = game_path + DIR_DELIM + "minetest.conf";
+       return conf.readConfigFile(conf_path.c_str());
+}
+
+struct GameFindPath
+{
+       std::string path;
+       bool user_specific;
+       GameFindPath(const std::string &path, bool user_specific) :
+                       path(path), user_specific(user_specific)
+       {
+       }
+};
+
+std::string getSubgamePathEnv()
+{
+       char *subgame_path = getenv("MINETEST_SUBGAME_PATH");
+       return subgame_path ? std::string(subgame_path) : "";
+}
+
+SubgameSpec findSubgame(const std::string &id)
+{
+       if (id.empty())
+               return SubgameSpec();
+       std::string share = porting::path_share;
+       std::string user = porting::path_user;
+
+       // Get games install locations
+       Strfnd search_paths(getSubgamePathEnv());
+
+       // Get all possible paths fo game
+       std::vector<GameFindPath> find_paths;
+       while (!search_paths.at_end()) {
+               std::string path = search_paths.next(PATH_DELIM);
+               find_paths.emplace_back(path + DIR_DELIM + id, false);
+               find_paths.emplace_back(path + DIR_DELIM + id + "_game", false);
+       }
+       find_paths.emplace_back(
+                       user + DIR_DELIM + "games" + DIR_DELIM + id + "_game", true);
+       find_paths.emplace_back(user + DIR_DELIM + "games" + DIR_DELIM + id, true);
+       find_paths.emplace_back(
+                       share + DIR_DELIM + "games" + DIR_DELIM + id + "_game", false);
+       find_paths.emplace_back(share + DIR_DELIM + "games" + DIR_DELIM + id, false);
+
+       // Find game directory
+       std::string game_path;
+       bool user_game = true; // Game is in user's directory
+       for (const GameFindPath &find_path : find_paths) {
+               const std::string &try_path = find_path.path;
+               if (fs::PathExists(try_path)) {
+                       game_path = try_path;
+                       user_game = find_path.user_specific;
+                       break;
+               }
+       }
+
+       if (game_path.empty())
+               return SubgameSpec();
+
+       std::string gamemod_path = game_path + DIR_DELIM + "mods";
+
+       // Find mod directories
+       std::set<std::string> mods_paths;
+       if (!user_game)
+               mods_paths.insert(share + DIR_DELIM + "mods");
+       if (user != share || user_game)
+               mods_paths.insert(user + DIR_DELIM + "mods");
+
+       // Get meta
+       std::string conf_path = game_path + DIR_DELIM + "game.conf";
+       Settings conf;
+       conf.readConfigFile(conf_path.c_str());
+
+       std::string game_name;
+       if (conf.exists("name"))
+               game_name = conf.get("name");
+       else
+               game_name = id;
+
+       std::string game_author;
+       if (conf.exists("author"))
+               game_author = conf.get("author");
+
+       std::string menuicon_path;
+#ifndef SERVER
+       menuicon_path = getImagePath(
+                       game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png");
+#endif
+       return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name,
+                       menuicon_path, game_author);
+}
+
+SubgameSpec findWorldSubgame(const std::string &world_path)
+{
+       std::string world_gameid = getWorldGameId(world_path, true);
+       // See if world contains an embedded game; if so, use it.
+       std::string world_gamepath = world_path + DIR_DELIM + "game";
+       if (fs::PathExists(world_gamepath)) {
+               SubgameSpec gamespec;
+               gamespec.id = world_gameid;
+               gamespec.path = world_gamepath;
+               gamespec.gamemods_path = world_gamepath + DIR_DELIM + "mods";
+
+               Settings conf;
+               std::string conf_path = world_gamepath + DIR_DELIM + "game.conf";
+               conf.readConfigFile(conf_path.c_str());
+
+               if (conf.exists("name"))
+                       gamespec.name = conf.get("name");
+               else
+                       gamespec.name = world_gameid;
+
+               return gamespec;
+       }
+       return findSubgame(world_gameid);
+}
+
+std::set<std::string> getAvailableGameIds()
+{
+       std::set<std::string> gameids;
+       std::set<std::string> gamespaths;
+       gamespaths.insert(porting::path_share + DIR_DELIM + "games");
+       gamespaths.insert(porting::path_user + DIR_DELIM + "games");
+
+       Strfnd search_paths(getSubgamePathEnv());
+
+       while (!search_paths.at_end())
+               gamespaths.insert(search_paths.next(PATH_DELIM));
+
+       for (const std::string &gamespath : gamespaths) {
+               std::vector<fs::DirListNode> dirlist = fs::GetDirListing(gamespath);
+               for (const fs::DirListNode &dln : dirlist) {
+                       if (!dln.dir)
+                               continue;
+
+                       // If configuration file is not found or broken, ignore game
+                       Settings conf;
+                       std::string conf_path = gamespath + DIR_DELIM + dln.name +
+                                               DIR_DELIM + "game.conf";
+                       if (!conf.readConfigFile(conf_path.c_str()))
+                               continue;
+
+                       // Add it to result
+                       const char *ends[] = {"_game", NULL};
+                       std::string shorter = removeStringEnd(dln.name, ends);
+                       if (!shorter.empty())
+                               gameids.insert(shorter);
+                       else
+                               gameids.insert(dln.name);
+               }
+       }
+       return gameids;
+}
+
+std::vector<SubgameSpec> getAvailableGames()
+{
+       std::vector<SubgameSpec> specs;
+       std::set<std::string> gameids = getAvailableGameIds();
+       for (const auto &gameid : gameids)
+               specs.push_back(findSubgame(gameid));
+       return specs;
+}
+
+#define LEGACY_GAMEID "minetest"
+
+bool getWorldExists(const std::string &world_path)
+{
+       return (fs::PathExists(world_path + DIR_DELIM + "map_meta.txt") ||
+                       fs::PathExists(world_path + DIR_DELIM + "world.mt"));
+}
+
+std::string getWorldGameId(const std::string &world_path, bool can_be_legacy)
+{
+       std::string conf_path = world_path + DIR_DELIM + "world.mt";
+       Settings conf;
+       bool succeeded = conf.readConfigFile(conf_path.c_str());
+       if (!succeeded) {
+               if (can_be_legacy) {
+                       // If map_meta.txt exists, it is probably an old minetest world
+                       if (fs::PathExists(world_path + DIR_DELIM + "map_meta.txt"))
+                               return LEGACY_GAMEID;
+               }
+               return "";
+       }
+       if (!conf.exists("gameid"))
+               return "";
+       // The "mesetint" gameid has been discarded
+       if (conf.get("gameid") == "mesetint")
+               return "minetest";
+       return conf.get("gameid");
+}
+
+std::string getWorldPathEnv()
+{
+       char *world_path = getenv("MINETEST_WORLD_PATH");
+       return world_path ? std::string(world_path) : "";
+}
+
+std::vector<WorldSpec> getAvailableWorlds()
+{
+       std::vector<WorldSpec> worlds;
+       std::set<std::string> worldspaths;
+
+       Strfnd search_paths(getWorldPathEnv());
+
+       while (!search_paths.at_end())
+               worldspaths.insert(search_paths.next(PATH_DELIM));
+
+       worldspaths.insert(porting::path_user + DIR_DELIM + "worlds");
+       infostream << "Searching worlds..." << std::endl;
+       for (const std::string &worldspath : worldspaths) {
+               infostream << "  In " << worldspath << ": " << std::endl;
+               std::vector<fs::DirListNode> dirvector = fs::GetDirListing(worldspath);
+               for (const fs::DirListNode &dln : dirvector) {
+                       if (!dln.dir)
+                               continue;
+                       std::string fullpath = worldspath + DIR_DELIM + dln.name;
+                       std::string name = dln.name;
+                       // Just allow filling in the gameid always for now
+                       bool can_be_legacy = true;
+                       std::string gameid = getWorldGameId(fullpath, can_be_legacy);
+                       WorldSpec spec(fullpath, name, gameid);
+                       if (!spec.isValid()) {
+                               infostream << "(invalid: " << name << ") ";
+                       } else {
+                               infostream << name << " ";
+                               worlds.push_back(spec);
+                       }
+               }
+               infostream << std::endl;
+       }
+       // Check old world location
+       do {
+               std::string fullpath = porting::path_user + DIR_DELIM + "world";
+               if (!fs::PathExists(fullpath))
+                       break;
+               std::string name = "Old World";
+               std::string gameid = getWorldGameId(fullpath, true);
+               WorldSpec spec(fullpath, name, gameid);
+               infostream << "Old world found." << std::endl;
+               worlds.push_back(spec);
+       } while (false);
+       infostream << worlds.size() << " found." << std::endl;
+       return worlds;
+}
+
+bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamespec)
+{
+       // Override defaults with those provided by the game.
+       // We clear and reload the defaults because the defaults
+       // might have been overridden by other subgame config
+       // files that were loaded before.
+       g_settings->clearDefaults();
+       set_default_settings(g_settings);
+       Settings game_defaults;
+       getGameMinetestConfig(gamespec.path, game_defaults);
+       override_default_settings(g_settings, &game_defaults);
+
+       infostream << "Initializing world at " << path << std::endl;
+
+       fs::CreateAllDirs(path);
+
+       // Create world.mt if does not already exist
+       std::string worldmt_path = path + DIR_DELIM "world.mt";
+       if (!fs::PathExists(worldmt_path)) {
+               Settings conf;
+
+               conf.set("gameid", gamespec.id);
+               conf.set("backend", "sqlite3");
+               conf.set("player_backend", "sqlite3");
+               conf.setBool("creative_mode", g_settings->getBool("creative_mode"));
+               conf.setBool("enable_damage", g_settings->getBool("enable_damage"));
+
+               if (!conf.updateConfigFile(worldmt_path.c_str()))
+                       return false;
+       }
+
+       // Create map_meta.txt if does not already exist
+       std::string map_meta_path = path + DIR_DELIM + "map_meta.txt";
+       if (!fs::PathExists(map_meta_path)) {
+               verbosestream << "Creating map_meta.txt (" << map_meta_path << ")"
+                             << std::endl;
+               fs::CreateAllDirs(path);
+               std::ostringstream oss(std::ios_base::binary);
+
+               Settings conf;
+               MapgenParams params;
+
+               params.readParams(g_settings);
+               params.writeParams(&conf);
+               conf.writeLines(oss);
+               oss << "[end_of_params]\n";
+
+               fs::safeWriteToFile(map_meta_path, oss.str());
+       }
+       return true;
+}
diff --git a/src/content/subgames.h b/src/content/subgames.h
new file mode 100644 (file)
index 0000000..70a9d27
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include <string>
+#include <set>
+#include <vector>
+
+class Settings;
+
+struct SubgameSpec
+{
+       std::string id;
+       std::string name;
+       std::string author;
+       std::string path;
+       std::string gamemods_path;
+       std::set<std::string> addon_mods_paths;
+       std::string menuicon_path;
+
+       SubgameSpec(const std::string &id = "", const std::string &path = "",
+                       const std::string &gamemods_path = "",
+                       const std::set<std::string> &addon_mods_paths =
+                                       std::set<std::string>(),
+                       const std::string &name = "",
+                       const std::string &menuicon_path = "",
+                       const std::string &author = "") :
+                       id(id),
+                       name(name), author(author), path(path),
+                       gamemods_path(gamemods_path), addon_mods_paths(addon_mods_paths),
+                       menuicon_path(menuicon_path)
+       {
+       }
+
+       bool isValid() const { return (!id.empty() && !path.empty()); }
+};
+
+// minetest.conf
+bool getGameMinetestConfig(const std::string &game_path, Settings &conf);
+
+SubgameSpec findSubgame(const std::string &id);
+SubgameSpec findWorldSubgame(const std::string &world_path);
+
+std::set<std::string> getAvailableGameIds();
+std::vector<SubgameSpec> getAvailableGames();
+
+bool getWorldExists(const std::string &world_path);
+std::string getWorldGameId(const std::string &world_path, bool can_be_legacy = false);
+
+struct WorldSpec
+{
+       std::string path;
+       std::string name;
+       std::string gameid;
+
+       WorldSpec(const std::string &path = "", const std::string &name = "",
+                       const std::string &gameid = "") :
+                       path(path),
+                       name(name), gameid(gameid)
+       {
+       }
+
+       bool isValid() const
+       {
+               return (!name.empty() && !path.empty() && !gameid.empty());
+       }
+};
+
+std::vector<WorldSpec> getAvailableWorlds();
+
+// loads the subgame's config and creates world directory
+// and world.mt if they don't exist
+bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamespec);
index bfd7f39c4b5c66319bf8cb4b2c7ee9926492f2ac..c774aa002b4355fa7184909490ae3a8ffbed09a0 100644 (file)
@@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <sstream>
 
 #include "convert_json.h"
-#include "mods.h"
+#include "content/mods.h"
 #include "config.h"
 #include "log.h"
 #include "settings.h"
index 0c13e052d3a4ea2cf80e61adb20f7c7dac32303d..8a39d73631c09f8a56c514314d8229cd7df1363c 100644 (file)
@@ -284,6 +284,7 @@ void set_default_settings(Settings *settings)
 #endif
        settings->setDefault("font_size", font_size_str);
        settings->setDefault("mono_font_size", font_size_str);
+       settings->setDefault("contentdb_url", "https://contentdb.rubenwardy.com");
 
 
        // Server
index d8504ad64750b1d90f81eaf85b548c458c7dcdfa..6b67e0e1323ee62c540f2b0131eb33f6d99faa18 100644 (file)
@@ -245,7 +245,7 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_,
        curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
        curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
-       curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1);
+       curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3);
        curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");
 
        std::string bind_address = g_settings->get("bind_address");
diff --git a/src/mods.cpp b/src/mods.cpp
deleted file mode 100644 (file)
index 6fa578f..0000000
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#include <cctype>
-#include <fstream>
-#include <json/json.h>
-#include <algorithm>
-#include "mods.h"
-#include "filesys.h"
-#include "log.h"
-#include "subgame.h"
-#include "settings.h"
-#include "porting.h"
-#include "convert_json.h"
-
-bool parseDependsString(std::string &dep,
-               std::unordered_set<char> &symbols)
-{
-       dep = trim(dep);
-       symbols.clear();
-       size_t pos = dep.size();
-       while (pos > 0 && !string_allowed(dep.substr(pos-1, 1), MODNAME_ALLOWED_CHARS)) {
-               // last character is a symbol, not part of the modname
-               symbols.insert(dep[pos-1]);
-               --pos;
-       }
-       dep = trim(dep.substr(0, pos));
-       return !dep.empty();
-}
-
-void parseModContents(ModSpec &spec)
-{
-       // NOTE: this function works in mutual recursion with getModsInPath
-       Settings info;
-       info.readConfigFile((spec.path+DIR_DELIM+"mod.conf").c_str());
-
-       if (info.exists("name"))
-               spec.name = info.get("name");
-
-       spec.depends.clear();
-       spec.optdepends.clear();
-       spec.is_modpack = false;
-       spec.modpack_content.clear();
-
-       // Handle modpacks (defined by containing modpack.txt)
-       std::ifstream modpack_is((spec.path+DIR_DELIM+"modpack.txt").c_str());
-       if (modpack_is.good()) { // a modpack, recursively get the mods in it
-               modpack_is.close(); // We don't actually need the file
-               spec.is_modpack = true;
-               spec.modpack_content = getModsInPath(spec.path, true);
-               // modpacks have no dependencies; they are defined and
-               // tracked separately for each mod in the modpack
-
-       } else {
-               // Attempt to load dependencies from mod.conf
-               bool mod_conf_has_depends = false;
-               if (info.exists("depends")) {
-                       mod_conf_has_depends = true;
-                       std::string dep = info.get("depends");
-                       dep.erase(std::remove_if(dep.begin(), dep.end(),
-                                       static_cast<int(*)(int)>(&std::isspace)), dep.end());
-                       for (const auto &dependency : str_split(dep, ',')) {
-                               spec.depends.insert(dependency);
-                       }
-               }
-
-               if (info.exists("optional_depends")) {
-                       mod_conf_has_depends = true;
-                       std::string dep = info.get("optional_depends");
-                       dep.erase(std::remove_if(dep.begin(), dep.end(),
-                                       static_cast<int(*)(int)>(&std::isspace)), dep.end());
-                       for (const auto &dependency : str_split(dep, ',')) {
-                               spec.optdepends.insert(dependency);
-                       }
-               }
-
-               // Fallback to depends.txt
-               if (!mod_conf_has_depends) {
-                       std::vector<std::string> dependencies;
-
-                       std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str());
-                       while (is.good()) {
-                               std::string dep;
-                               std::getline(is, dep);
-                               dependencies.push_back(dep);
-                       }
-
-                       for (auto &dependency : dependencies) {
-                               std::unordered_set<char> symbols;
-                               if (parseDependsString(dependency, symbols)) {
-                                       if (symbols.count('?') != 0) {
-                                               spec.optdepends.insert(dependency);
-                                       } else {
-                                               spec.depends.insert(dependency);
-                                       }
-                               }
-                       }
-               }
-
-               if (info.exists("description")) {
-                       spec.desc = info.get("description");
-               } else {
-                       std::ifstream is((spec.path + DIR_DELIM + "description.txt").c_str());
-                       spec.desc = std::string((std::istreambuf_iterator<char>(is)),
-                                       std::istreambuf_iterator<char>());
-               }
-       }
-}
-
-std::map<std::string, ModSpec> getModsInPath(const std::string &path,
-       bool part_of_modpack)
-{
-       // NOTE: this function works in mutual recursion with parseModContents
-
-       std::map<std::string, ModSpec> result;
-       std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path);
-       std::string modpath;
-
-       for (const fs::DirListNode &dln : dirlist) {
-               if (!dln.dir)
-                       continue;
-
-               const std::string &modname = dln.name;
-               // Ignore all directories beginning with a ".", especially
-               // VCS directories like ".git" or ".svn"
-               if (modname[0] == '.')
-                       continue;
-
-               modpath.clear();
-               modpath.append(path)
-                       .append(DIR_DELIM)
-                       .append(modname);
-
-               ModSpec spec(modname, modpath, part_of_modpack);
-               parseModContents(spec);
-               result.insert(std::make_pair(modname, spec));
-       }
-       return result;
-}
-
-std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
-{
-       std::vector<ModSpec> result;
-       for (const auto &it : mods) {
-               const ModSpec &mod = it.second;
-               if (mod.is_modpack) {
-                       std::vector<ModSpec> content = flattenMods(mod.modpack_content);
-                       result.reserve(result.size() + content.size());
-                       result.insert(result.end(),content.begin(),content.end());
-
-               }
-               else //not a modpack
-               {
-                       result.push_back(mod);
-               }
-       }
-       return result;
-}
-
-ModConfiguration::ModConfiguration(const std::string &worldpath)
-{
-}
-
-void ModConfiguration::printUnsatisfiedModsError() const
-{
-       for (const ModSpec &mod : m_unsatisfied_mods) {
-               errorstream << "mod \"" << mod.name << "\" has unsatisfied dependencies: ";
-               for (const std::string &unsatisfied_depend : mod.unsatisfied_depends)
-                       errorstream << " \"" << unsatisfied_depend << "\"";
-               errorstream << std::endl;
-       }
-}
-
-void ModConfiguration::addModsInPath(const std::string &path)
-{
-       addMods(flattenMods(getModsInPath(path)));
-}
-
-void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods)
-{
-       // Maintain a map of all existing m_unsatisfied_mods.
-       // Keys are mod names and values are indices into m_unsatisfied_mods.
-       std::map<std::string, u32> existing_mods;
-       for(u32 i = 0; i < m_unsatisfied_mods.size(); ++i){
-               existing_mods[m_unsatisfied_mods[i].name] = i;
-       }
-
-       // Add new mods
-       for(int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack){
-               // First iteration:
-               // Add all the mods that come from modpacks
-               // Second iteration:
-               // Add all the mods that didn't come from modpacks
-
-               std::set<std::string> seen_this_iteration;
-
-               for (const ModSpec &mod : new_mods) {
-                       if (mod.part_of_modpack != (bool)want_from_modpack)
-                               continue;
-
-                       if (existing_mods.count(mod.name) == 0) {
-                               // GOOD CASE: completely new mod.
-                               m_unsatisfied_mods.push_back(mod);
-                               existing_mods[mod.name] = m_unsatisfied_mods.size() - 1;
-                       } else if(seen_this_iteration.count(mod.name) == 0) {
-                               // BAD CASE: name conflict in different levels.
-                               u32 oldindex = existing_mods[mod.name];
-                               const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
-                               warningstream<<"Mod name conflict detected: \""
-                                       <<mod.name<<"\""<<std::endl
-                                       <<"Will not load: "<<oldmod.path<<std::endl
-                                       <<"Overridden by: "<<mod.path<<std::endl;
-                               m_unsatisfied_mods[oldindex] = mod;
-
-                               // If there was a "VERY BAD CASE" name conflict
-                               // in an earlier level, ignore it.
-                               m_name_conflicts.erase(mod.name);
-                       } else {
-                               // VERY BAD CASE: name conflict in the same level.
-                               u32 oldindex = existing_mods[mod.name];
-                               const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
-                               warningstream<<"Mod name conflict detected: \""
-                                       <<mod.name<<"\""<<std::endl
-                                       <<"Will not load: "<<oldmod.path<<std::endl
-                                       <<"Will not load: "<<mod.path<<std::endl;
-                               m_unsatisfied_mods[oldindex] = mod;
-                               m_name_conflicts.insert(mod.name);
-                       }
-
-                       seen_this_iteration.insert(mod.name);
-               }
-       }
-}
-
-void ModConfiguration::addModsFromConfig(const std::string &settings_path, const std::set<std::string> &mods)
-{
-       Settings conf;
-       std::set<std::string> load_mod_names;
-
-       conf.readConfigFile(settings_path.c_str());
-       std::vector<std::string> names = conf.getNames();
-       for (const std::string &name : names) {
-               if (name.compare(0,9,"load_mod_")==0 && conf.getBool(name))
-                       load_mod_names.insert(name.substr(9));
-       }
-
-       std::vector<ModSpec> addon_mods;
-       for (const std::string &i : mods) {
-               std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(i));
-               for (std::vector<ModSpec>::const_iterator it = addon_mods_in_path.begin();
-                               it != addon_mods_in_path.end(); ++it) {
-                       const ModSpec& mod = *it;
-                       if (load_mod_names.count(mod.name) != 0)
-                               addon_mods.push_back(mod);
-                       else
-                               conf.setBool("load_mod_" + mod.name, false);
-               }
-       }
-       conf.updateConfigFile(settings_path.c_str());
-
-       addMods(addon_mods);
-       checkConflictsAndDeps();
-
-       // complain about mods declared to be loaded, but not found
-       for (const ModSpec &addon_mod : addon_mods)
-               load_mod_names.erase(addon_mod.name);
-
-       std::vector<ModSpec> unsatisfiedMods = getUnsatisfiedMods();
-
-       for (const ModSpec &unsatisfiedMod : unsatisfiedMods)
-               load_mod_names.erase(unsatisfiedMod.name);
-
-       if (!load_mod_names.empty()) {
-               errorstream << "The following mods could not be found:";
-               for (const std::string &mod : load_mod_names)
-                       errorstream << " \"" << mod << "\"";
-               errorstream << std::endl;
-       }
-}
-
-void ModConfiguration::checkConflictsAndDeps()
-{
-       // report on name conflicts
-       if (!m_name_conflicts.empty()) {
-               std::string s = "Unresolved name conflicts for mods ";
-               for (std::unordered_set<std::string>::const_iterator it =
-                       m_name_conflicts.begin(); it != m_name_conflicts.end(); ++it) {
-                       if (it != m_name_conflicts.begin()) s += ", ";
-                       s += std::string("\"") + (*it) + "\"";
-               }
-               s += ".";
-               throw ModError(s);
-       }
-
-       // get the mods in order
-       resolveDependencies();
-}
-
-void ModConfiguration::resolveDependencies()
-{
-       // Step 1: Compile a list of the mod names we're working with
-       std::set<std::string> modnames;
-       for (const ModSpec &mod : m_unsatisfied_mods) {
-               modnames.insert(mod.name);
-       }
-
-       // Step 2: get dependencies (including optional dependencies)
-       // of each mod, split mods into satisfied and unsatisfied
-       std::list<ModSpec> satisfied;
-       std::list<ModSpec> unsatisfied;
-       for (ModSpec mod : m_unsatisfied_mods) {
-               mod.unsatisfied_depends = mod.depends;
-               // check which optional dependencies actually exist
-               for (const std::string &optdep : mod.optdepends) {
-                       if (modnames.count(optdep) != 0)
-                               mod.unsatisfied_depends.insert(optdep);
-               }
-               // if a mod has no depends it is initially satisfied
-               if (mod.unsatisfied_depends.empty())
-                       satisfied.push_back(mod);
-               else
-                       unsatisfied.push_back(mod);
-       }
-
-       // Step 3: mods without unmet dependencies can be appended to
-       // the sorted list.
-       while(!satisfied.empty()){
-               ModSpec mod = satisfied.back();
-               m_sorted_mods.push_back(mod);
-               satisfied.pop_back();
-               for (auto it = unsatisfied.begin(); it != unsatisfied.end(); ) {
-                       ModSpec& mod2 = *it;
-                       mod2.unsatisfied_depends.erase(mod.name);
-                       if (mod2.unsatisfied_depends.empty()) {
-                               satisfied.push_back(mod2);
-                               it = unsatisfied.erase(it);
-                       } else {
-                               ++it;
-                       }
-               }
-       }
-
-       // Step 4: write back list of unsatisfied mods
-       m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end());
-}
-
-#ifndef SERVER
-ClientModConfiguration::ClientModConfiguration(const std::string &path):
-       ModConfiguration(path)
-{
-       std::set<std::string> paths;
-       std::string path_user = porting::path_user + DIR_DELIM + "clientmods";
-       paths.insert(path);
-       paths.insert(path_user);
-
-       std::string settings_path = path_user + DIR_DELIM + "mods.conf";
-       addModsFromConfig(settings_path, paths);
-}
-#endif
-
-ModMetadata::ModMetadata(const std::string &mod_name):
-       m_mod_name(mod_name)
-{
-}
-
-void ModMetadata::clear()
-{
-       Metadata::clear();
-       m_modified = true;
-}
-
-bool ModMetadata::save(const std::string &root_path)
-{
-       Json::Value json;
-       for (StringMap::const_iterator it = m_stringvars.begin();
-                       it != m_stringvars.end(); ++it) {
-               json[it->first] = it->second;
-       }
-
-       if (!fs::PathExists(root_path)) {
-               if (!fs::CreateAllDirs(root_path)) {
-                       errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '"
-                               << root_path << "' tree cannot be created." << std::endl;
-                       return false;
-               }
-       } else if (!fs::IsDir(root_path)) {
-               errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '"
-                       << root_path << "' is not a directory." << std::endl;
-               return false;
-       }
-
-       bool w_ok = fs::safeWriteToFile(root_path + DIR_DELIM + m_mod_name,
-                       fastWriteJson(json));
-
-       if (w_ok) {
-               m_modified = false;
-       } else {
-               errorstream << "ModMetadata[" << m_mod_name << "]: failed write file." << std::endl;
-       }
-       return w_ok;
-}
-
-bool ModMetadata::load(const std::string &root_path)
-{
-       m_stringvars.clear();
-
-       std::ifstream is((root_path + DIR_DELIM + m_mod_name).c_str(), std::ios_base::binary);
-       if (!is.good()) {
-               return false;
-       }
-
-       Json::Value root;
-       Json::CharReaderBuilder builder;
-       builder.settings_["collectComments"] = false;
-       std::string errs;
-
-       if (!Json::parseFromStream(builder, is, &root, &errs)) {
-               errorstream << "ModMetadata[" << m_mod_name << "]: failed read data "
-                       "(Json decoding failure). Message: " << errs << std::endl;
-               return false;
-       }
-
-       const Json::Value::Members attr_list = root.getMemberNames();
-       for (const auto &it : attr_list) {
-               Json::Value attr_value = root[it];
-               m_stringvars[it] = attr_value.asString();
-       }
-
-       return true;
-}
-
-bool ModMetadata::setString(const std::string &name, const std::string &var)
-{
-       m_modified = Metadata::setString(name, var);
-       return m_modified;
-}
diff --git a/src/mods.h b/src/mods.h
deleted file mode 100644 (file)
index 3063eda..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes.h"
-#include <list>
-#include <set>
-#include <vector>
-#include <string>
-#include <map>
-#include <json/json.h>
-#include <unordered_set>
-#include "util/basic_macros.h"
-#include "config.h"
-#include "metadata.h"
-
-#define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_"
-
-struct ModSpec
-{
-       std::string name;
-       std::string path;
-       std::string desc;
-
-       //if normal mod:
-       std::unordered_set<std::string> depends;
-       std::unordered_set<std::string> optdepends;
-       std::unordered_set<std::string> unsatisfied_depends;
-
-       bool part_of_modpack = false;
-       bool is_modpack = false;
-
-       // if modpack:
-       std::map<std::string,ModSpec> modpack_content;
-       ModSpec(const std::string &name_ = "", const std::string &path_ = ""):
-               name(name_),
-               path(path_)
-       {}
-       ModSpec(const std::string &name_, const std::string &path_, bool part_of_modpack_):
-               name(name_),
-               path(path_),
-               part_of_modpack(part_of_modpack_)
-       {}
-};
-
-// Retrieves depends, optdepends, is_modpack and modpack_content
-void parseModContents(ModSpec &mod);
-
-std::map<std::string,ModSpec> getModsInPath(const std::string &path,
-       bool part_of_modpack = false);
-
-// replaces modpack Modspecs with their content
-std::vector<ModSpec> flattenMods(std::map<std::string,ModSpec> mods);
-
-// a ModConfiguration is a subset of installed mods, expected to have
-// all dependencies fullfilled, so it can be used as a list of mods to
-// load when the game starts.
-class ModConfiguration
-{
-public:
-       // checks if all dependencies are fullfilled.
-       bool isConsistent() const
-       {
-               return m_unsatisfied_mods.empty();
-       }
-
-       const std::vector<ModSpec> &getMods() const
-       {
-               return m_sorted_mods;
-       }
-
-       const std::vector<ModSpec> &getUnsatisfiedMods() const
-       {
-               return m_unsatisfied_mods;
-       }
-
-       void printUnsatisfiedModsError() const;
-
-protected:
-       ModConfiguration(const std::string &worldpath);
-       // adds all mods in the given path. used for games, modpacks
-       // and world-specific mods (worldmods-folders)
-       void addModsInPath(const std::string &path);
-
-       // adds all mods in the set.
-       void addMods(const std::vector<ModSpec> &new_mods);
-
-       void addModsFromConfig(const std::string &settings_path, const std::set<std::string> &mods);
-
-       void checkConflictsAndDeps();
-protected:
-       // list of mods sorted such that they can be loaded in the
-       // given order with all dependencies being fullfilled. I.e.,
-       // every mod in this list has only dependencies on mods which
-       // appear earlier in the vector.
-       std::vector<ModSpec> m_sorted_mods;
-
-private:
-       // move mods from m_unsatisfied_mods to m_sorted_mods
-       // in an order that satisfies dependencies
-       void resolveDependencies();
-
-       // mods with unmet dependencies. Before dependencies are resolved,
-       // this is where all mods are stored. Afterwards this contains
-       // only the ones with really unsatisfied dependencies.
-       std::vector<ModSpec> m_unsatisfied_mods;
-
-       // set of mod names for which an unresolved name conflict
-       // exists. A name conflict happens when two or more mods
-       // at the same level have the same name but different paths.
-       // Levels (mods in higher levels override mods in lower levels):
-       // 1. game mod in modpack; 2. game mod;
-       // 3. world mod in modpack; 4. world mod;
-       // 5. addon mod in modpack; 6. addon mod.
-       std::unordered_set<std::string> m_name_conflicts;
-
-       // Deleted default constructor
-       ModConfiguration() = default;
-
-};
-
-#ifndef SERVER
-class ClientModConfiguration: public ModConfiguration
-{
-public:
-       ClientModConfiguration(const std::string &path);
-};
-#endif
-
-class ModMetadata: public Metadata
-{
-public:
-       ModMetadata() = delete;
-       ModMetadata(const std::string &mod_name);
-       ~ModMetadata() = default;
-
-       virtual void clear();
-
-       bool save(const std::string &root_path);
-       bool load(const std::string &root_path);
-
-       bool isModified() const { return m_modified; }
-       const std::string &getModName() const { return m_mod_name; }
-
-       virtual bool setString(const std::string &name, const std::string &var);
-private:
-       std::string m_mod_name;
-       bool m_modified = false;
-};
index 571bac611b06f945857b37e6066c72685ad72094..54ff8c49574f7c31578548b5cae172a8b06c9aaf 100644 (file)
@@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "common/c_converter.h"
 #include "serverobject.h"
 #include "filesys.h"
-#include "mods.h"
+#include "content/mods.h"
 #include "porting.h"
 #include "util/string.h"
 #include "server.h"
index 5d7ba9640329c94ef36f6f1fa433a20492c5b02c..8ab0a20b6d9f01c908d5e9dc2da0647370b697a0 100644 (file)
@@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "lua_api/l_base.h"
 #include "lua_api/l_internal.h"
 #include "cpp_api/s_base.h"
-#include <mods.h>
+#include "content/mods.h"
 #include <server.h>
 
 ScriptApiBase *ModApiBase::getScriptApiBase(lua_State *L)
index 027b7b0f8afde5ef14fcfddf3812a073c5771838..241427709bef3eef2efa2a9b4996cd84498f0637 100644 (file)
@@ -25,11 +25,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "gui/guiMainMenu.h"
 #include "gui/guiKeyChangeMenu.h"
 #include "gui/guiPathSelectMenu.h"
-#include "subgame.h"
 #include "version.h"
 #include "porting.h"
 #include "filesys.h"
 #include "convert_json.h"
+#include "content/packages.h"
+#include "content/content.h"
+#include "content/subgames.h"
 #include "serverlist.h"
 #include "mapgen/mapgen.h"
 #include "settings.h"
@@ -449,6 +451,10 @@ int ModApiMainMenu::l_get_games(lua_State *L)
                lua_pushstring(L, game.path.c_str());
                lua_settable(L,   top_lvl2);
 
+               lua_pushstring(L, "type");
+               lua_pushstring(L, "game");
+               lua_settable(L,   top_lvl2);
+
                lua_pushstring(L, "gamemods_path");
                lua_pushstring(L, game.gamemods_path.c_str());
                lua_settable(L,   top_lvl2);
@@ -457,6 +463,10 @@ int ModApiMainMenu::l_get_games(lua_State *L)
                lua_pushstring(L, game.name.c_str());
                lua_settable(L,   top_lvl2);
 
+               lua_pushstring(L, "author");
+               lua_pushstring(L, game.author.c_str());
+               lua_settable(L,   top_lvl2);
+
                lua_pushstring(L, "menuicon_path");
                lua_pushstring(L, game.menuicon_path.c_str());
                lua_settable(L,   top_lvl2);
@@ -479,47 +489,56 @@ int ModApiMainMenu::l_get_games(lua_State *L)
 }
 
 /******************************************************************************/
-int ModApiMainMenu::l_get_mod_info(lua_State *L)
+int ModApiMainMenu::l_get_content_info(lua_State *L)
 {
        std::string path = luaL_checkstring(L, 1);
 
-       ModSpec spec;
+       ContentSpec spec;
        spec.path = path;
-       parseModContents(spec);
+       parseContentInfo(spec);
 
        lua_newtable(L);
 
        lua_pushstring(L, spec.name.c_str());
        lua_setfield(L, -2, "name");
 
-       lua_pushstring(L, spec.is_modpack ? "modpack" : "mod");
+       lua_pushstring(L, spec.type.c_str());
        lua_setfield(L, -2, "type");
 
+       lua_pushstring(L, spec.author.c_str());
+       lua_setfield(L, -2, "author");
+
        lua_pushstring(L, spec.desc.c_str());
        lua_setfield(L, -2, "description");
 
        lua_pushstring(L, spec.path.c_str());
        lua_setfield(L, -2, "path");
 
-       // Dependencies
-       lua_newtable(L);
-       int i = 1;
-       for (const auto &dep : spec.depends) {
-               lua_pushstring(L, dep.c_str());
-               lua_rawseti(L, -2, i);
-               i++;
-       }
-       lua_setfield(L, -2, "depends");
+       if (spec.type == "mod") {
+               ModSpec spec;
+               spec.path = path;
+               parseModContents(spec);
 
-       // Optional Dependencies
-       lua_newtable(L);
-       i = 1;
-       for (const auto &dep : spec.optdepends) {
-               lua_pushstring(L, dep.c_str());
-               lua_rawseti(L, -2, i);
-               i++;
+               // Dependencies
+               lua_newtable(L);
+               int i = 1;
+               for (const auto &dep : spec.depends) {
+                       lua_pushstring(L, dep.c_str());
+                       lua_rawseti(L, -2, i);
+                       i++;
+               }
+               lua_setfield(L, -2, "depends");
+
+               // Optional Dependencies
+               lua_newtable(L);
+               i = 1;
+               for (const auto &dep : spec.optdepends) {
+                       lua_pushstring(L, dep.c_str());
+                       lua_rawseti(L, -2, i);
+                       i++;
+               }
+               lua_setfield(L, -2, "optional_depends");
        }
-       lua_setfield(L, -2, "optional_depends");
 
        return 1;
 }
@@ -838,6 +857,10 @@ bool ModApiMainMenu::isMinetestPath(std::string path)
        if (fs::PathStartsWith(path,fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM + "mods")))
                return true;
 
+       /* mods */
+       if (fs::PathStartsWith(path,fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM + "textures")))
+               return true;
+
        /* worlds */
        if (fs::PathStartsWith(path,fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM + "worlds")))
                return true;
@@ -972,6 +995,54 @@ int ModApiMainMenu::l_get_screen_info(lua_State *L)
        return 1;
 }
 
+int ModApiMainMenu::l_get_package_list(lua_State *L)
+{
+       std::string url = g_settings->get("contentdb_url");
+       std::vector<Package> packages = getPackagesFromURL(url + "/packages/");
+
+       // Make table
+       lua_newtable(L);
+       int top = lua_gettop(L);
+       unsigned int index = 1;
+
+       // Fill table
+       for (const auto &package : packages) {
+               lua_pushnumber(L, index);
+               lua_newtable(L);
+
+               int top_lvl2 = lua_gettop(L);
+
+               lua_pushstring(L, "name");
+               lua_pushstring(L, package.name.c_str());
+               lua_settable  (L, top_lvl2);
+
+               lua_pushstring(L, "title");
+               lua_pushstring(L, package.title.c_str());
+               lua_settable  (L, top_lvl2);
+
+               lua_pushstring(L, "author");
+               lua_pushstring(L, package.author.c_str());
+               lua_settable  (L, top_lvl2);
+
+               lua_pushstring(L, "type");
+               lua_pushstring(L, package.type.c_str());
+               lua_settable  (L, top_lvl2);
+
+               lua_pushstring(L, "short_description");
+               lua_pushstring(L, package.shortDesc.c_str());
+               lua_settable  (L, top_lvl2);
+
+               lua_pushstring(L, "url");
+               lua_pushstring(L, package.url.c_str());
+               lua_settable  (L, top_lvl2);
+
+               lua_settable(L, top);
+               index++;
+       }
+
+       return 1;
+}
+
 /******************************************************************************/
 int ModApiMainMenu::l_get_min_supp_proto(lua_State *L)
 {
@@ -1015,7 +1086,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
        API_FCT(get_table_index);
        API_FCT(get_worlds);
        API_FCT(get_games);
-       API_FCT(get_mod_info);
+       API_FCT(get_content_info);
        API_FCT(start);
        API_FCT(close);
        API_FCT(get_favorites);
@@ -1042,6 +1113,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
        API_FCT(get_video_drivers);
        API_FCT(get_video_modes);
        API_FCT(get_screen_info);
+       API_FCT(get_package_list);
        API_FCT(get_min_supp_proto);
        API_FCT(get_max_supp_proto);
        API_FCT(do_async_callback);
@@ -1050,7 +1122,6 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
 /******************************************************************************/
 void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
 {
-
        API_FCT(get_worlds);
        API_FCT(get_games);
        API_FCT(get_favorites);
@@ -1066,4 +1137,5 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
        //API_FCT(extract_zip); //TODO remove dependency to GuiEngine
        API_FCT(download_file);
        //API_FCT(gettext); (gettext lib isn't threadsafe)
+       API_FCT(get_package_list);
 }
index 2faeaf63eb538a1004b7851a4544b12acfc0538f..b08a5bc017b1bc89ad4da67a7293680444bb475c 100644 (file)
@@ -83,7 +83,7 @@ private:
 
        static int l_get_games(lua_State *L);
 
-       static int l_get_mod_info(lua_State *L);
+       static int l_get_content_info(lua_State *L);
 
        //gui
 
@@ -133,6 +133,9 @@ private:
 
        static int l_get_video_modes(lua_State *L);
 
+       //content store
+       static int l_get_package_list(lua_State *L);
+
        //version compatibility
        static int l_get_min_supp_proto(lua_State *L);
 
index 4c6b2a1827df56ea5a3cbeab1b218b008c9cd9df..01810ca8b1d79376a601510b9f5fb1a045eaaca7 100644 (file)
@@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "lua_api/l_storage.h"
 #include "l_internal.h"
-#include "mods.h"
+#include "content/mods.h"
 #include "server.h"
 
 int ModApiStorage::l_get_mod_storage(lua_State *L)
index 0ff60951bd04b18bc31c9c85db285478ddda5c88..b6068439a15449af596626a35f9e70b82665319c 100644 (file)
@@ -18,7 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "scripting_mainmenu.h"
-#include "mods.h"
+#include "content/mods.h"
 #include "cpp_api/s_internal.h"
 #include "lua_api/l_base.h"
 #include "lua_api/l_mainmenu.h"
index 5bdf22c7e0519418bfb9d5dc27d3e3e90bb1b9e0..1eddec700c125695125e30e03aca1701859dcfb9 100644 (file)
@@ -49,7 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "content_mapnode.h"
 #include "content_nodemeta.h"
 #include "content_sao.h"
-#include "mods.h"
+#include "content/mods.h"
 #include "modchannels.h"
 #include "serverlist.h"
 #include "util/string.h"
index 2442140c8a3b1f656f7dc2ca82b9855d23ece658..39cf560274076e9f147d89c505e31958f2f19b5f 100644 (file)
@@ -24,9 +24,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "hud.h"
 #include "gamedef.h"
 #include "serialization.h" // For SER_FMT_VER_INVALID
-#include "mods.h"
+#include "content/mods.h"
 #include "inventorymanager.h"
-#include "subgame.h"
+#include "content/subgames.h"
 #include "tileanimation.h" // struct TileAnimationParams
 #include "network/peerhandler.h"
 #include "network/address.h"
index 34ac760e498d9939bbf351975a66cd5334b58a8f..c246e6446b5b869ea64d1ae9926b3dd989405ae8 100644 (file)
@@ -21,7 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "filesys.h"
 #include "log.h"
 #include "scripting_server.h"
-#include "subgame.h"
+#include "content/subgames.h"
 
 /**
  * Manage server mods
index 9e4b23f3072a9dd028e672b6d604dbc5dadc7890..2bc1aa22ff7f21eeab63ebd1fd87851a9603bf96 100644 (file)
@@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #pragma once
 
-#include "../mods.h"
+#include "content/mods.h"
 
 class ServerScripting;
 
index 796b23b98a60b6bc0f987653ce819fe536fd9f0c..2b82b7431833404bc47df3fce078c0e60ff824f8 100644 (file)
@@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include <iostream>
 #include "config.h"
-#include "mods.h"
+#include "content/mods.h"
 #include <json/json.h>
 
 #pragma once
diff --git a/src/subgame.cpp b/src/subgame.cpp
deleted file mode 100644 (file)
index f5ff870..0000000
+++ /dev/null
@@ -1,317 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#include "subgame.h"
-#include "porting.h"
-#include "filesys.h"
-#include "settings.h"
-#include "log.h"
-#include "util/strfnd.h"
-#include "defaultsettings.h"  // for override_default_settings
-#include "mapgen/mapgen.h"  // for MapgenParams
-#include "util/string.h"
-
-#ifndef SERVER
-       #include "client/tile.h" // getImagePath
-#endif
-
-bool getGameMinetestConfig(const std::string &game_path, Settings &conf)
-{
-       std::string conf_path = game_path + DIR_DELIM + "minetest.conf";
-       return conf.readConfigFile(conf_path.c_str());
-}
-
-bool getGameConfig(const std::string &game_path, Settings &conf)
-{
-       std::string conf_path = game_path + DIR_DELIM + "game.conf";
-       return conf.readConfigFile(conf_path.c_str());
-}
-
-std::string getGameName(const std::string &game_path)
-{
-       Settings conf;
-       if(!getGameConfig(game_path, conf))
-               return "";
-       if(!conf.exists("name"))
-               return "";
-       return conf.get("name");
-}
-
-struct GameFindPath
-{
-       std::string path;
-       bool user_specific;
-       GameFindPath(const std::string &path, bool user_specific):
-               path(path),
-               user_specific(user_specific)
-       {}
-};
-
-std::string getSubgamePathEnv()
-{
-       char *subgame_path = getenv("MINETEST_SUBGAME_PATH");
-       return subgame_path ? std::string(subgame_path) : "";
-}
-
-SubgameSpec findSubgame(const std::string &id)
-{
-       if (id.empty())
-               return SubgameSpec();
-       std::string share = porting::path_share;
-       std::string user = porting::path_user;
-       std::vector<GameFindPath> find_paths;
-
-       Strfnd search_paths(getSubgamePathEnv());
-
-       while (!search_paths.at_end()) {
-               std::string path = search_paths.next(PATH_DELIM);
-               find_paths.emplace_back(path + DIR_DELIM + id, false);
-               find_paths.emplace_back(path + DIR_DELIM + id + "_game", false);
-       }
-
-       find_paths.emplace_back(user + DIR_DELIM + "games" + DIR_DELIM + id + "_game", true);
-       find_paths.emplace_back(user + DIR_DELIM + "games" + DIR_DELIM + id, true);
-       find_paths.emplace_back(share + DIR_DELIM + "games" + DIR_DELIM + id + "_game",
-               false);
-       find_paths.emplace_back(share + DIR_DELIM + "games" + DIR_DELIM + id, false);
-       // Find game directory
-       std::string game_path;
-       bool user_game = true; // Game is in user's directory
-       for (const GameFindPath &find_path : find_paths) {
-               const std::string &try_path = find_path.path;
-               if (fs::PathExists(try_path)) {
-                       game_path = try_path;
-                       user_game = find_path.user_specific;
-                       break;
-               }
-       }
-       if (game_path.empty())
-               return SubgameSpec();
-       std::string gamemod_path = game_path + DIR_DELIM + "mods";
-       // Find mod directories
-       std::set<std::string> mods_paths;
-       if(!user_game)
-               mods_paths.insert(share + DIR_DELIM + "mods");
-       if(user != share || user_game)
-               mods_paths.insert(user + DIR_DELIM + "mods");
-       std::string game_name = getGameName(game_path);
-       if (game_name.empty())
-               game_name = id;
-       std::string menuicon_path;
-#ifndef SERVER
-       menuicon_path = getImagePath(game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png");
-#endif
-       return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name,
-                       menuicon_path);
-}
-
-SubgameSpec findWorldSubgame(const std::string &world_path)
-{
-       std::string world_gameid = getWorldGameId(world_path, true);
-       // See if world contains an embedded game; if so, use it.
-       std::string world_gamepath = world_path + DIR_DELIM + "game";
-       if(fs::PathExists(world_gamepath)){
-               SubgameSpec gamespec;
-               gamespec.id = world_gameid;
-               gamespec.path = world_gamepath;
-               gamespec.gamemods_path= world_gamepath + DIR_DELIM + "mods";
-               gamespec.name = getGameName(world_gamepath);
-               if (gamespec.name.empty())
-                       gamespec.name = "unknown";
-               return gamespec;
-       }
-       return findSubgame(world_gameid);
-}
-
-std::set<std::string> getAvailableGameIds()
-{
-       std::set<std::string> gameids;
-       std::set<std::string> gamespaths;
-       gamespaths.insert(porting::path_share + DIR_DELIM + "games");
-       gamespaths.insert(porting::path_user + DIR_DELIM + "games");
-
-       Strfnd search_paths(getSubgamePathEnv());
-
-       while (!search_paths.at_end())
-               gamespaths.insert(search_paths.next(PATH_DELIM));
-
-       for (const std::string &gamespath : gamespaths) {
-               std::vector<fs::DirListNode> dirlist = fs::GetDirListing(gamespath);
-               for (const fs::DirListNode &dln : dirlist) {
-                       if(!dln.dir)
-                               continue;
-                       // If configuration file is not found or broken, ignore game
-                       Settings conf;
-                       if(!getGameConfig(gamespath + DIR_DELIM + dln.name, conf))
-                               continue;
-                       // Add it to result
-                       const char *ends[] = {"_game", NULL};
-                       std::string shorter = removeStringEnd(dln.name, ends);
-                       if (!shorter.empty())
-                               gameids.insert(shorter);
-                       else
-                               gameids.insert(dln.name);
-               }
-       }
-       return gameids;
-}
-
-std::vector<SubgameSpec> getAvailableGames()
-{
-       std::vector<SubgameSpec> specs;
-       std::set<std::string> gameids = getAvailableGameIds();
-       for (const auto &gameid : gameids)
-               specs.push_back(findSubgame(gameid));
-       return specs;
-}
-
-#define LEGACY_GAMEID "minetest"
-
-bool getWorldExists(const std::string &world_path)
-{
-       return (fs::PathExists(world_path + DIR_DELIM + "map_meta.txt") ||
-                       fs::PathExists(world_path + DIR_DELIM + "world.mt"));
-}
-
-std::string getWorldGameId(const std::string &world_path, bool can_be_legacy)
-{
-       std::string conf_path = world_path + DIR_DELIM + "world.mt";
-       Settings conf;
-       bool succeeded = conf.readConfigFile(conf_path.c_str());
-       if(!succeeded){
-               if(can_be_legacy){
-                       // If map_meta.txt exists, it is probably an old minetest world
-                       if(fs::PathExists(world_path + DIR_DELIM + "map_meta.txt"))
-                               return LEGACY_GAMEID;
-               }
-               return "";
-       }
-       if(!conf.exists("gameid"))
-               return "";
-       // The "mesetint" gameid has been discarded
-       if(conf.get("gameid") == "mesetint")
-               return "minetest";
-       return conf.get("gameid");
-}
-
-std::string getWorldPathEnv()
-{
-       char *world_path = getenv("MINETEST_WORLD_PATH");
-       return world_path ? std::string(world_path) : "";
-}
-
-std::vector<WorldSpec> getAvailableWorlds()
-{
-       std::vector<WorldSpec> worlds;
-       std::set<std::string> worldspaths;
-
-       Strfnd search_paths(getWorldPathEnv());
-
-       while (!search_paths.at_end())
-               worldspaths.insert(search_paths.next(PATH_DELIM));
-
-       worldspaths.insert(porting::path_user + DIR_DELIM + "worlds");
-       infostream << "Searching worlds..." << std::endl;
-       for (const std::string &worldspath : worldspaths) {
-               infostream << "  In " << worldspath << ": " <<std::endl;
-               std::vector<fs::DirListNode> dirvector = fs::GetDirListing(worldspath);
-               for (const fs::DirListNode &dln : dirvector) {
-                       if(!dln.dir)
-                               continue;
-                       std::string fullpath = worldspath + DIR_DELIM + dln.name;
-                       std::string name = dln.name;
-                       // Just allow filling in the gameid always for now
-                       bool can_be_legacy = true;
-                       std::string gameid = getWorldGameId(fullpath, can_be_legacy);
-                       WorldSpec spec(fullpath, name, gameid);
-                       if(!spec.isValid()){
-                               infostream<<"(invalid: "<<name<<") ";
-                       } else {
-                               infostream<<name<<" ";
-                               worlds.push_back(spec);
-                       }
-               }
-               infostream<<std::endl;
-       }
-       // Check old world location
-       do{
-               std::string fullpath = porting::path_user + DIR_DELIM + "world";
-               if(!fs::PathExists(fullpath))
-                       break;
-               std::string name = "Old World";
-               std::string gameid = getWorldGameId(fullpath, true);
-               WorldSpec spec(fullpath, name, gameid);
-               infostream<<"Old world found."<<std::endl;
-               worlds.push_back(spec);
-       }while(false);
-       infostream<<worlds.size()<<" found."<<std::endl;
-       return worlds;
-}
-
-bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamespec)
-{
-       // Override defaults with those provided by the game.
-       // We clear and reload the defaults because the defaults
-       // might have been overridden by other subgame config
-       // files that were loaded before.
-       g_settings->clearDefaults();
-       set_default_settings(g_settings);
-       Settings game_defaults;
-       getGameMinetestConfig(gamespec.path, game_defaults);
-       override_default_settings(g_settings, &game_defaults);
-
-       infostream << "Initializing world at " << path << std::endl;
-
-       fs::CreateAllDirs(path);
-
-       // Create world.mt if does not already exist
-       std::string worldmt_path = path + DIR_DELIM "world.mt";
-       if (!fs::PathExists(worldmt_path)) {
-               Settings conf;
-
-               conf.set("gameid", gamespec.id);
-               conf.set("backend", "sqlite3");
-               conf.set("player_backend", "sqlite3");
-               conf.setBool("creative_mode", g_settings->getBool("creative_mode"));
-               conf.setBool("enable_damage", g_settings->getBool("enable_damage"));
-
-               if (!conf.updateConfigFile(worldmt_path.c_str()))
-                       return false;
-       }
-
-       // Create map_meta.txt if does not already exist
-       std::string map_meta_path = path + DIR_DELIM + "map_meta.txt";
-       if (!fs::PathExists(map_meta_path)){
-               verbosestream << "Creating map_meta.txt (" << map_meta_path << ")" << std::endl;
-               fs::CreateAllDirs(path);
-               std::ostringstream oss(std::ios_base::binary);
-
-               Settings conf;
-               MapgenParams params;
-
-               params.readParams(g_settings);
-               params.writeParams(&conf);
-               conf.writeLines(oss);
-               oss << "[end_of_params]\n";
-
-               fs::safeWriteToFile(map_meta_path, oss.str());
-       }
-       return true;
-}
-
diff --git a/src/subgame.h b/src/subgame.h
deleted file mode 100644 (file)
index 6e78639..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include <string>
-#include <set>
-#include <vector>
-
-class Settings;
-
-struct SubgameSpec
-{
-       std::string id; // "" = game does not exist
-       std::string path; // path to game
-       std::string gamemods_path; //path to mods of the game
-       std::set<std::string> addon_mods_paths; //paths to addon mods for this game
-       std::string name;
-       std::string menuicon_path;
-
-       SubgameSpec(const std::string &id_ = "",
-                       const std::string &path_ = "",
-                       const std::string &gamemods_path_ = "",
-                       const std::set<std::string> &addon_mods_paths_ = std::set<std::string>(),
-                       const std::string &name_ = "",
-                       const std::string &menuicon_path_ = ""):
-               id(id_),
-               path(path_),
-               gamemods_path(gamemods_path_),
-               addon_mods_paths(addon_mods_paths_),
-               name(name_),
-               menuicon_path(menuicon_path_)
-       {}
-
-       bool isValid() const
-       {
-               return (!id.empty() && !path.empty());
-       }
-};
-
-// minetest.conf
-bool getGameMinetestConfig(const std::string &game_path, Settings &conf);
-// game.conf
-bool getGameConfig(const std::string &game_path, Settings &conf);
-
-std::string getGameName(const std::string &game_path);
-
-SubgameSpec findSubgame(const std::string &id);
-SubgameSpec findWorldSubgame(const std::string &world_path);
-
-std::set<std::string> getAvailableGameIds();
-std::vector<SubgameSpec> getAvailableGames();
-
-bool getWorldExists(const std::string &world_path);
-std::string getWorldGameId(const std::string &world_path,
-               bool can_be_legacy=false);
-
-struct WorldSpec
-{
-       std::string path;
-       std::string name;
-       std::string gameid;
-
-       WorldSpec(
-               const std::string &path_="",
-               const std::string &name_="",
-               const std::string &gameid_=""
-       ):
-               path(path_),
-               name(name_),
-               gameid(gameid_)
-       {}
-
-       bool isValid() const
-       {
-               return (!name.empty() && !path.empty() && !gameid.empty());
-       }
-};
-
-std::vector<WorldSpec> getAvailableWorlds();
-
-// loads the subgame's config and creates world directory
-// and world.mt if they don't exist
-bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamespec);
index 7a0ef16b10cca605cfc60ed6ef211dcdcb84185b..547c3fd07ea9f0fd030d1e5ed1e526444885a8f5 100644 (file)
@@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "itemdef.h"
 #include "gamedef.h"
 #include "modchannels.h"
-#include "mods.h"
+#include "content/mods.h"
 #include "util/numeric.h"
 
 content_t t_CONTENT_STONE;