Add online content repository

Replaces mods and texture pack tabs with a single content tab
master
rubenwardy 2018-04-17 14:54:50 +01:00
parent 36eb823b1c
commit 87ad4d8e7f
No known key found for this signature in database
GPG Key ID: A1E29D52FF81513C
46 changed files with 1696 additions and 860 deletions

View File

@ -130,6 +130,9 @@ LOCAL_SRC_FILES := \
jni/src/content_mapnode.cpp \ jni/src/content_mapnode.cpp \
jni/src/content_nodemeta.cpp \ jni/src/content_nodemeta.cpp \
jni/src/content_sao.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/convert_json.cpp \
jni/src/craftdef.cpp \ jni/src/craftdef.cpp \
jni/src/database/database-dummy.cpp \ jni/src/database/database-dummy.cpp \
@ -198,7 +201,6 @@ LOCAL_SRC_FILES := \
jni/src/mapgen/mg_ore.cpp \ jni/src/mapgen/mg_ore.cpp \
jni/src/mapgen/mg_schematic.cpp \ jni/src/mapgen/mg_schematic.cpp \
jni/src/minimap.cpp \ jni/src/minimap.cpp \
jni/src/mods.cpp \
jni/src/modchannels.cpp \ jni/src/modchannels.cpp \
jni/src/nameidmapping.cpp \ jni/src/nameidmapping.cpp \
jni/src/nodedef.cpp \ jni/src/nodedef.cpp \
@ -229,7 +231,6 @@ LOCAL_SRC_FILES := \
jni/src/shader.cpp \ jni/src/shader.cpp \
jni/src/sky.cpp \ jni/src/sky.cpp \
jni/src/staticobject.cpp \ jni/src/staticobject.cpp \
jni/src/subgame.cpp \
jni/src/tileanimation.cpp \ jni/src/tileanimation.cpp \
jni/src/translation.cpp \ jni/src/translation.cpp \
jni/src/tool.cpp \ jni/src/tool.cpp \

View 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((raw_fct ~= nil) and (type(raw_fct) == "function"))
assert((compare_fct ~= nil) and (type(compare_fct) == "function")) assert((compare_fct ~= nil) and (type(compare_fct) == "function"))
local self = {} local self = {}
self.m_raw_list_fct = raw_fct self.m_raw_list_fct = raw_fct
self.m_compare_fct = compare_fct self.m_compare_fct = compare_fct
self.m_filter_fct = filter_fct self.m_filter_fct = filter_fct
self.m_uid_match_fct = uid_match_fct self.m_uid_match_fct = uid_match_fct
self.m_filtercriteria = nil self.m_filtercriteria = nil
self.m_fetch_param = fetch_param self.m_fetch_param = fetch_param
self.m_sortmode = "none" self.m_sortmode = "none"
self.m_sort_list = {} 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 self.refresh = filterlist.refresh
filterlist.process(self) filterlist.process(self)
return self return self
end end
@ -128,49 +128,49 @@ function filterlist.get_raw_element(self,idx)
if type(idx) ~= "number" then if type(idx) ~= "number" then
idx = tonumber(idx) idx = tonumber(idx)
end end
if idx ~= nil and idx > 0 and idx <= #self.m_raw_list then if idx ~= nil and idx > 0 and idx <= #self.m_raw_list then
return self.m_raw_list[idx] return self.m_raw_list[idx]
end end
return nil return nil
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function filterlist.get_raw_index(self,listindex) function filterlist.get_raw_index(self,listindex)
assert(self.m_processed_list ~= nil) assert(self.m_processed_list ~= nil)
if listindex ~= nil and listindex > 0 and if listindex ~= nil and listindex > 0 and
listindex <= #self.m_processed_list then listindex <= #self.m_processed_list then
local entry = self.m_processed_list[listindex] local entry = self.m_processed_list[listindex]
for i,v in ipairs(self.m_raw_list) do for i,v in ipairs(self.m_raw_list) do
if self.m_compare_fct(v,entry) then if self.m_compare_fct(v,entry) then
return i return i
end end
end end
end end
return 0 return 0
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function filterlist.get_current_index(self,listindex) function filterlist.get_current_index(self,listindex)
assert(self.m_processed_list ~= nil) assert(self.m_processed_list ~= nil)
if listindex ~= nil and listindex > 0 and if listindex ~= nil and listindex > 0 and
listindex <= #self.m_raw_list then listindex <= #self.m_raw_list then
local entry = self.m_raw_list[listindex] local entry = self.m_raw_list[listindex]
for i,v in ipairs(self.m_processed_list) do for i,v in ipairs(self.m_processed_list) do
if self.m_compare_fct(v,entry) then if self.m_compare_fct(v,entry) then
return i return i
end end
end end
end end
return 0 return 0
end end
@ -183,23 +183,23 @@ function filterlist.process(self)
self.m_processed_list = self.m_raw_list self.m_processed_list = self.m_raw_list
return return
end end
self.m_processed_list = {} self.m_processed_list = {}
for k,v in pairs(self.m_raw_list) do for k,v in pairs(self.m_raw_list) do
if self.m_filtercriteria == nil or if self.m_filtercriteria == nil or
self.m_filter_fct(v,self.m_filtercriteria) then self.m_filter_fct(v,self.m_filtercriteria) then
self.m_processed_list[#self.m_processed_list + 1] = v self.m_processed_list[#self.m_processed_list + 1] = v
end end
end end
if self.m_sortmode == "none" then if self.m_sortmode == "none" then
return return
end end
if self.m_sort_list[self.m_sortmode] ~= nil and if self.m_sort_list[self.m_sortmode] ~= nil and
type(self.m_sort_list[self.m_sortmode]) == "function" then type(self.m_sort_list[self.m_sortmode]) == "function" then
self.m_sort_list[self.m_sortmode](self) self.m_sort_list[self.m_sortmode](self)
end end
end end
@ -209,7 +209,7 @@ function filterlist.size(self)
if self.m_processed_list == nil then if self.m_processed_list == nil then
return 0 return 0
end end
return #self.m_processed_list return #self.m_processed_list
end end
@ -233,8 +233,8 @@ function filterlist.raw_index_by_uid(self, uid)
elementidx = i elementidx = i
end end
end end
-- If there are more elements than one with same name uid can't decide which -- 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. -- one is meant. self shouldn't be possible but just for sure.
if elementcount > 1 then if elementcount > 1 then
@ -254,11 +254,11 @@ function compare_worlds(world1,world2)
if world1.path ~= world2.path then if world1.path ~= world2.path then
return false return false
end end
if world1.name ~= world2.name then if world1.name ~= world2.name then
return false return false
end end
if world1.gameid ~= world2.gameid then if world1.gameid ~= world2.gameid then
return false return false
end end
@ -288,11 +288,11 @@ function sort_mod_list(self)
table.sort(self.m_processed_list, function(a, b) table.sort(self.m_processed_list, function(a, b)
-- Show game mods at bottom -- Show game mods at bottom
if a.typ ~= b.typ then if a.type ~= b.type or a.loc ~= b.loc then
if b.typ == "game" then if b.type == "game" then
return a.typ ~= "game_mod" return a.loc ~= "game"
end end
return b.typ == "game_mod" return b.loc == "game"
end end
-- If in same or no modpack, sort by name -- If in same or no modpack, sort by name
if a.modpack == b.modpack then if a.modpack == b.modpack then
@ -308,7 +308,7 @@ function sort_mod_list(self)
elseif b.name == a.modpack then elseif b.name == a.modpack then
return false return false
end end
local name_a = a.modpack or a.name local name_a = a.modpack or a.name
local name_b = b.modpack or b.name local name_b = b.modpack or b.name
if name_a:lower() == name_b:lower() then if name_a:lower() == name_b:lower() then

View File

@ -551,6 +551,16 @@ function table.copy(t, seen)
end end
return n return n
end end
function table.insert_all(t, other)
for i=1, #other do
t[#t + 1] = other[i]
end
return t
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- mainmenu only functions -- mainmenu only functions
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@ -41,7 +41,7 @@ local function render_client_count(n)
end end
local function configure_selected_world_params(idx) 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 if worldconfig.creative_mode then
core.settings:set("creative_mode", worldconfig.creative_mode) core.settings:set("creative_mode", worldconfig.creative_mode)
end end

View File

@ -35,7 +35,7 @@ local function get_formspec(data)
mod = {name=""} mod = {name=""}
end end
local hard_deps, soft_deps = modmgr.get_dependencies(mod.path) local hard_deps, soft_deps = pkgmgr.get_dependencies(mod.path)
retval = retval .. retval = retval ..
"label[0,0.7;" .. fgettext("Mod:") .. "]" .. "label[0,0.7;" .. fgettext("Mod:") .. "]" ..
@ -88,7 +88,7 @@ local function get_formspec(data)
retval = retval .. retval = retval ..
"tablecolumns[color;tree;text]" .. "tablecolumns[color;tree;text]" ..
"table[5.5,0.75;5.75,6;world_config_modlist;" "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 .."]" retval = retval .. ";" .. data.selected_mod .."]"
return retval return retval
@ -237,7 +237,7 @@ function create_configure_world_dlg(worldidx)
dlg.data.worldspec = core.get_worlds()[worldidx] dlg.data.worldspec = core.get_worlds()[worldidx]
if dlg.data.worldspec == nil then dlg:delete() return nil end 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 if dlg.data.worldconfig == nil or dlg.data.worldconfig.id == nil or
dlg.data.worldconfig.id == "" then dlg.data.worldconfig.id == "" then
@ -247,8 +247,8 @@ function create_configure_world_dlg(worldidx)
end end
dlg.data.list = filterlist.create( dlg.data.list = filterlist.create(
modmgr.preparemodlist, --refresh pkgmgr.preparemodlist, --refresh
modmgr.comparemod, --compare pkgmgr.comparemod, --compare
function(element,uid) --uid match function(element,uid) --uid match
if element.name == uid then if element.name == uid then
return true return true

View File

@ -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

View File

@ -26,7 +26,7 @@ local function create_world_formspec(dialogdata)
local game, gameidx = nil , 0 local game, gameidx = nil , 0
if gameid ~= nil then if gameid ~= nil then
game, gameidx = gamemgr.find_by_gameid(gameid) game, gameidx = pkgmgr.find_by_gameid(gameid)
if gameidx == nil then if gameidx == nil then
gameidx = 0 gameidx = 0
@ -77,17 +77,17 @@ local function create_world_formspec(dialogdata)
"dropdown[4.2,2;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" .. "dropdown[4.2,2;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" ..
"label[2,3;" .. fgettext("Game") .. "]".. "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]" .. ";" .. gameidx .. ";true]" ..
"button[3.25,6;2.5,0.5;world_create_confirm;" .. fgettext("Create") .. "]" .. "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") .. "]" "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;" .. retval = retval .. "box[2,4;8,1;#ff8800]label[2.25,4;" ..
fgettext("You have no games installed.") .. "]label[2.25,4.4;" .. fgettext("You have no games installed.") .. "]label[2.25,4.4;" ..
fgettext("Download one from minetest.net") .. "]" 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;" .. 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("Warning: The minimal development test is meant for developers.") .. "]label[2,4.4;" ..
fgettext("Download a game, such as minetest_game, from minetest.net") .. "]" 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 if message ~= nil then
gamedata.errormessage = message gamedata.errormessage = message
else 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 if this.data.update_worldlist_filter then
menudata.worldlist:set_filtercriteria(gamemgr.games[gameindex].id) menudata.worldlist:set_filtercriteria(pkgmgr.games[gameindex].id)
mm_texture.update("singleplayer", gamemgr.games[gameindex].id) mm_texture.update("singleplayer", pkgmgr.games[gameindex].id)
end end
menudata.worldlist:refresh() menudata.worldlist:refresh()
core.settings:set("mainmenu_last_selected_world", core.settings:set("mainmenu_last_selected_world",
@ -145,7 +145,7 @@ local function create_world_buttonhandler(this, fields)
if fields["games"] then if fields["games"] then
local gameindex = core.get_textlist_index("games") 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 return true
end end

View File

@ -17,39 +17,38 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local function delete_mod_formspec(dialogdata) local function delete_content_formspec(dialogdata)
dialogdata.mod = modmgr.global_mods:get_list()[dialogdata.selected]
local retval = local retval =
"size[11.5,4.5,true]" .. "size[11.5,4.5,true]" ..
"label[2,2;" .. "label[2,2;" ..
fgettext("Are you sure you want to delete \"$1\"?", dialogdata.mod.name) .. "]".. fgettext("Are you sure you want to delete \"$1\"?", dialogdata.content.name) .. "]"..
"button[3.25,3.5;2.5,0.5;dlg_delete_mod_confirm;" .. fgettext("Delete") .. "]" .. "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_mod_cancel;" .. fgettext("Cancel") .. "]" "button[5.75,3.5;2.5,0.5;dlg_delete_content_cancel;" .. fgettext("Cancel") .. "]"
return retval return retval
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local function delete_mod_buttonhandler(this, fields) local function delete_content_buttonhandler(this, fields)
if fields["dlg_delete_mod_confirm"] ~= nil then if fields["dlg_delete_content_confirm"] ~= nil then
if this.data.mod.path ~= nil and if this.data.content.path ~= nil and
this.data.mod.path ~= "" and this.data.content.path ~= "" and
this.data.mod.path ~= core.get_modpath() then this.data.content.path ~= core.get_modpath() and
if not core.delete_dir(this.data.mod.path) then this.data.content.path ~= core.get_gamepath() and
gamedata.errormessage = fgettext("Modmgr: failed to delete \"$1\"", this.data.mod.path) 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 end
modmgr.refresh_globals() pkgmgr.refresh_globals()
else else
gamedata.errormessage = fgettext("Modmgr: invalid modpath \"$1\"", this.data.mod.path) gamedata.errormessage = fgettext("pkgmgr: invalid path \"$1\"", this.data.content.path)
end end
this:delete() this:delete()
return true return true
end end
if fields["dlg_delete_mod_cancel"] then if fields["dlg_delete_content_cancel"] then
this:delete() this:delete()
return true return true
end end
@ -58,12 +57,13 @@ local function delete_mod_buttonhandler(this, fields)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function create_delete_mod_dlg(selected_index) function create_delete_content_dlg(content)
assert(content.name)
local retval = dialog_create("dlg_delete_mod", local retval = dialog_create("dlg_delete_content",
delete_mod_formspec, delete_content_formspec,
delete_mod_buttonhandler, delete_content_buttonhandler,
nil) nil)
retval.data.selected = selected_index retval.data.content = content
return retval return retval
end end

View File

@ -18,8 +18,8 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local function rename_modpack_formspec(dialogdata) 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 = local retval =
"size[11.5,4.5,true]" .. "size[11.5,4.5,true]" ..
@ -29,7 +29,7 @@ local function rename_modpack_formspec(dialogdata)
fgettext("Accept") .. "]" .. fgettext("Accept") .. "]" ..
"button[5.75,3.5;2.5,0.5;dlg_rename_modpack_cancel;".. "button[5.75,3.5;2.5,0.5;dlg_rename_modpack_cancel;"..
fgettext("Cancel") .. "]" fgettext("Cancel") .. "]"
return retval return retval
end end
@ -39,14 +39,14 @@ local function rename_modpack_buttonhandler(this, fields)
local oldpath = core.get_modpath() .. DIR_DELIM .. this.data.mod.name local oldpath = core.get_modpath() .. DIR_DELIM .. this.data.mod.name
local targetpath = core.get_modpath() .. DIR_DELIM .. fields["te_modpack_name"] local targetpath = core.get_modpath() .. DIR_DELIM .. fields["te_modpack_name"]
core.copy_dir(oldpath,targetpath,false) core.copy_dir(oldpath,targetpath,false)
modmgr.refresh_globals() pkgmgr.refresh_globals()
modmgr.selected_mod = modmgr.global_mods:get_current_index( pkgmgr.selected_mod = pkgmgr.global_mods:get_current_index(
modmgr.global_mods:raw_index_by_uid(fields["te_modpack_name"])) pkgmgr.global_mods:raw_index_by_uid(fields["te_modpack_name"]))
this:delete() this:delete()
return true return true
end end
if fields["dlg_rename_modpack_cancel"] then if fields["dlg_rename_modpack_cancel"] then
this:delete() this:delete()
return true return true

View File

@ -338,7 +338,7 @@ local function parse_config_file(read_all, parse_mods)
-- Parse games -- Parse games
local games_category_initialized = false local games_category_initialized = false
local index = 1 local index = 1
local game = gamemgr.get_game(index) local game = pkgmgr.get_game(index)
while game do while game do
local path = game.path .. DIR_DELIM .. FILENAME local path = game.path .. DIR_DELIM .. FILENAME
local file = io.open(path, "r") local file = io.open(path, "r")
@ -365,7 +365,7 @@ local function parse_config_file(read_all, parse_mods)
end end
index = index + 1 index = index + 1
game = gamemgr.get_game(index) game = pkgmgr.get_game(index)
end end
-- Parse mods -- Parse mods

View File

@ -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()

View File

@ -38,15 +38,15 @@ dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua")
dofile(basepath .. "fstk" .. DIR_DELIM .. "tabview.lua") dofile(basepath .. "fstk" .. DIR_DELIM .. "tabview.lua")
dofile(basepath .. "fstk" .. DIR_DELIM .. "ui.lua") dofile(basepath .. "fstk" .. DIR_DELIM .. "ui.lua")
dofile(menupath .. DIR_DELIM .. "common.lua") dofile(menupath .. DIR_DELIM .. "common.lua")
dofile(menupath .. DIR_DELIM .. "gamemgr.lua") dofile(menupath .. DIR_DELIM .. "pkgmgr.lua")
dofile(menupath .. DIR_DELIM .. "modmgr.lua")
dofile(menupath .. DIR_DELIM .. "textures.lua") dofile(menupath .. DIR_DELIM .. "textures.lua")
dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua") dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua")
dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua") dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua")
dofile(menupath .. DIR_DELIM .. "dlg_contentstore.lua")
if menustyle ~= "simple" then if menustyle ~= "simple" then
dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua") 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_delete_world.lua")
dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua") dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua")
end end
@ -54,14 +54,13 @@ end
local tabs = {} local tabs = {}
tabs.settings = dofile(menupath .. DIR_DELIM .. "tab_settings.lua") tabs.settings = dofile(menupath .. DIR_DELIM .. "tab_settings.lua")
tabs.mods = dofile(menupath .. DIR_DELIM .. "tab_mods.lua") tabs.content = dofile(menupath .. DIR_DELIM .. "tab_content.lua")
tabs.credits = dofile(menupath .. DIR_DELIM .. "tab_credits.lua") tabs.credits = dofile(menupath .. DIR_DELIM .. "tab_credits.lua")
if menustyle == "simple" then if menustyle == "simple" then
tabs.simple_main = dofile(menupath .. DIR_DELIM .. "tab_simple_main.lua") tabs.simple_main = dofile(menupath .. DIR_DELIM .. "tab_simple_main.lua")
else else
tabs.local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua") tabs.local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua")
tabs.play_online = dofile(menupath .. DIR_DELIM .. "tab_online.lua") tabs.play_online = dofile(menupath .. DIR_DELIM .. "tab_online.lua")
tabs.texturepacks = dofile(menupath .. DIR_DELIM .. "tab_texturepacks.lua")
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -134,16 +133,14 @@ local function init_globals()
if menustyle == "simple" then if menustyle == "simple" then
tv_main:add(tabs.simple_main) tv_main:add(tabs.simple_main)
tv_main:add(tabs.settings)
else else
tv_main:set_autosave_tab(true) tv_main:set_autosave_tab(true)
tv_main:add(tabs.local_game) tv_main:add(tabs.local_game)
tv_main:add(tabs.play_online) tv_main:add(tabs.play_online)
tv_main:add(tabs.settings)
tv_main:add(tabs.texturepacks)
end end
tv_main:add(tabs.mods) tv_main:add(tabs.content)
tv_main:add(tabs.settings)
tv_main:add(tabs.credits) tv_main:add(tabs.credits)
tv_main:set_global_event_handler(main_event_handler) tv_main:set_global_event_handler(main_event_handler)

View File

@ -31,7 +31,9 @@ function get_mods(path,retval,modpack)
end end
toadd.name = name toadd.name = name
toadd.author = mod_conf.author
toadd.path = prefix toadd.path = prefix
toadd.type = "mod"
if modpack ~= nil and modpack ~= "" then if modpack ~= nil and modpack ~= "" then
toadd.modpack = modpack toadd.modpack = modpack
@ -39,6 +41,7 @@ function get_mods(path,retval,modpack)
local modpackfile = io.open(prefix .. "modpack.txt") local modpackfile = io.open(prefix .. "modpack.txt")
if modpackfile then if modpackfile then
modpackfile:close() modpackfile:close()
toadd.type = "modpack"
toadd.is_modpack = true toadd.is_modpack = true
get_mods(prefix, retval, name) get_mods(prefix, retval, name)
end end
@ -48,10 +51,46 @@ function get_mods(path,retval,modpack)
end end
--modmanager implementation --modmanager implementation
modmgr = {} 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 modmgr.extract(modfile) function pkgmgr.extract(modfile)
if modfile.type == "zip" then if modfile.type == "zip" then
local tempfolder = os.tempfolder() local tempfolder = os.tempfolder()
@ -66,72 +105,60 @@ function modmgr.extract(modfile)
return nil return nil
end 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 modmgr.getbasefolder(temppath) function pkgmgr.get_base_folder(temppath)
if temppath == nil then if temppath == nil then
return { return { type = "invalid", path = "" }
type = "invalid",
path = ""
}
end end
local testfile = io.open(temppath .. DIR_DELIM .. "init.lua","r") local ret = pkgmgr.get_folder_type(temppath)
if testfile ~= nil then if ret then
testfile:close() return ret
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 end
local subdirs = core.get_dir_list(temppath, true) local subdirs = core.get_dir_list(temppath, true)
if #subdirs == 1 then
--only single mod or modpack allowed ret = pkgmgr.get_folder_type(temppath .. DIR_DELIM .. subdirs[1])
if #subdirs ~= 1 then if ret then
return { return ret
type = "invalid", else
path = "" return { type = "invalid", path = temppath .. DIR_DELIM .. subdirs[1] }
} end
end end
testfile = return nil
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 end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.isValidModname(modpath) function pkgmgr.isValidModname(modpath)
if modpath:find("-") ~= nil then if modpath:find("-") ~= nil then
return false return false
end end
@ -140,7 +167,7 @@ function modmgr.isValidModname(modpath)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.parse_register_line(line) function pkgmgr.parse_register_line(line)
local pos1 = line:find("\"") local pos1 = line:find("\"")
local pos2 = nil local pos2 = nil
if pos1 ~= nil then if pos1 ~= nil then
@ -167,7 +194,7 @@ function modmgr.parse_register_line(line)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.parse_dofile_line(modpath,line) function pkgmgr.parse_dofile_line(modpath,line)
local pos1 = line:find("\"") local pos1 = line:find("\"")
local pos2 = nil local pos2 = nil
if pos1 ~= nil then if pos1 ~= nil then
@ -180,14 +207,14 @@ function modmgr.parse_dofile_line(modpath,line)
if filename ~= nil and if filename ~= nil and
filename ~= "" and filename ~= "" and
filename:find(".lua") then filename:find(".lua") then
return modmgr.identify_modname(modpath,filename) return pkgmgr.identify_modname(modpath,filename)
end end
end end
return nil return nil
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.identify_modname(modpath,filename) function pkgmgr.identify_modname(modpath,filename)
local testfile = io.open(modpath .. DIR_DELIM .. filename,"r") local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
if testfile ~= nil then if testfile ~= nil then
local line = testfile:read() local line = testfile:read()
@ -196,20 +223,20 @@ function modmgr.identify_modname(modpath,filename)
local modname = nil local modname = nil
if line:find("minetest.register_tool") then if line:find("minetest.register_tool") then
modname = modmgr.parse_register_line(line) modname = pkgmgr.parse_register_line(line)
end end
if line:find("minetest.register_craftitem") then if line:find("minetest.register_craftitem") then
modname = modmgr.parse_register_line(line) modname = pkgmgr.parse_register_line(line)
end end
if line:find("minetest.register_node") then if line:find("minetest.register_node") then
modname = modmgr.parse_register_line(line) modname = pkgmgr.parse_register_line(line)
end end
if line:find("dofile") then if line:find("dofile") then
modname = modmgr.parse_dofile_line(modpath,line) modname = pkgmgr.parse_dofile_line(modpath,line)
end end
if modname ~= nil then if modname ~= nil then
@ -225,14 +252,14 @@ function modmgr.identify_modname(modpath,filename)
return nil return nil
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.render_modlist(render_list) function pkgmgr.render_packagelist(render_list)
local retval = "" local retval = ""
if render_list == nil then if render_list == nil then
if modmgr.global_mods == nil then if pkgmgr.global_mods == nil then
modmgr.refresh_globals() pkgmgr.refresh_globals()
end end
render_list = modmgr.global_mods render_list = pkgmgr.global_mods
end end
local list = render_list:get_list() local list = render_list:get_list()
@ -252,36 +279,36 @@ function modmgr.render_modlist(render_list)
break break
end end
end end
elseif v.is_game_content then elseif v.is_game_content or v.type == "game" then
color = mt_color_blue color = mt_color_blue
elseif v.enabled then elseif v.enabled or v.type == "txp" then
color = mt_color_green color = mt_color_green
end end
retval[#retval + 1] = color retval[#retval + 1] = color
if v.modpack ~= nil or v.typ == "game_mod" then if v.modpack ~= nil or v.loc == "game" then
retval[#retval + 1] = "1" retval[#retval + 1] = "1"
else else
retval[#retval + 1] = "0" retval[#retval + 1] = "0"
end end
retval[#retval + 1] = core.formspec_escape(v.name) retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
end end
return table.concat(retval, ",") return table.concat(retval, ",")
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.get_dependencies(path) function pkgmgr.get_dependencies(path)
if path == nil then if path == nil then
return "", "" return "", ""
end end
local info = core.get_mod_info(path) local info = core.get_content_info(path)
return table.concat(info.depends, ","), table.concat(info.optional_depends, ",") return table.concat(info.depends or {}, ","), table.concat(info.optional_depends or {}, ",")
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.get_worldconfig(worldpath) function pkgmgr.get_worldconfig(worldpath)
local filename = worldpath .. local filename = worldpath ..
DIR_DELIM .. "world.mt" DIR_DELIM .. "world.mt"
@ -302,26 +329,35 @@ function modmgr.get_worldconfig(worldpath)
end end
--read gamemods --read gamemods
local gamespec = gamemgr.find_by_gameid(worldconfig.id) local gamespec = pkgmgr.find_by_gameid(worldconfig.id)
gamemgr.get_game_mods(gamespec, worldconfig.game_mods) pkgmgr.get_game_mods(gamespec, worldconfig.game_mods)
return worldconfig return worldconfig
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.installmod(modfilename,basename) function pkgmgr.install_dir(type, path, basename)
local modfile = modmgr.identify_filetype(modfilename) local basefolder = pkgmgr.get_base_folder(path)
local modpath = modmgr.extract(modfile)
if modpath == nil then local targetpath
gamedata.errormessage = fgettext("Install Mod: file: \"$1\"", modfile.name) .. if type == "txp" then
fgettext("\nInstall Mod: unsupported filetype \"$1\" or broken archive", modfile.type) if basefolder and basefolder.type ~= "invalid" and basefolder.type ~= "txp" then
return 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 end
local basefolder = modmgr.getbasefolder(modpath)
if basefolder.type == "modpack" then 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 local clean_path = nil
if basename ~= nil then if basename ~= nil then
@ -333,20 +369,27 @@ function modmgr.installmod(modfilename,basename)
end end
if clean_path ~= nil then if clean_path ~= nil then
local targetpath = core.get_modpath() .. DIR_DELIM .. clean_path targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
if not core.copy_dir(basefolder.path,targetpath) then if not core.copy_dir(basefolder.path,targetpath) then
gamedata.errormessage = fgettext("Failed to install $1 to $2", basename, targetpath) return nil,
fgettext("Failed to install $1 to $2", basename, targetpath)
end end
else else
gamedata.errormessage = fgettext("Install Mod: unable to find suitable foldername for modpack $1", modfilename) return nil,
fgettext("Install Mod: unable to find suitable foldername for modpack $1",
modfilename)
end end
end
if basefolder.type == "mod" then 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 local targetfolder = basename
if targetfolder == nil then if targetfolder == nil then
targetfolder = modmgr.identify_modname(basefolder.path,"init.lua") targetfolder = pkgmgr.identify_modname(basefolder.path,"init.lua")
end end
--if heuristic failed try to use current foldername --if heuristic failed try to use current foldername
@ -354,22 +397,46 @@ function modmgr.installmod(modfilename,basename)
targetfolder = get_last_folder(basefolder.path) targetfolder = get_last_folder(basefolder.path)
end end
if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then if targetfolder ~= nil and pkgmgr.isValidModname(targetfolder) then
local targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
core.copy_dir(basefolder.path,targetpath) core.copy_dir(basefolder.path, targetpath)
else else
gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename) return nil, fgettext("Install Mod: unable to find real modname for: $1", modfilename)
end 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 end
core.delete_dir(modpath) return targetpath, nil
modmgr.refresh_globals()
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.preparemodlist(data) 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 retval = {}
local global_mods = {} local global_mods = {}
@ -384,25 +451,27 @@ function modmgr.preparemodlist(data)
end end
for i=1,#global_mods,1 do for i=1,#global_mods,1 do
global_mods[i].typ = "global_mod" global_mods[i].type = "mod"
global_mods[i].loc = "global"
retval[#retval + 1] = global_mods[i] retval[#retval + 1] = global_mods[i]
end end
--read game mods --read game mods
local gamespec = gamemgr.find_by_gameid(data.gameid) local gamespec = pkgmgr.find_by_gameid(data.gameid)
gamemgr.get_game_mods(gamespec, game_mods) pkgmgr.get_game_mods(gamespec, game_mods)
if #game_mods > 0 then if #game_mods > 0 then
-- Add title -- Add title
retval[#retval + 1] = { retval[#retval + 1] = {
typ = "game", type = "game",
is_game_content = true, is_game_content = true,
name = fgettext("Subgame Mods") name = fgettext("Subgame Mods")
} }
end end
for i=1,#game_mods,1 do for i=1,#game_mods,1 do
game_mods[i].typ = "game_mod" game_mods[i].type = "mod"
game_mods[i].loc = "game"
game_mods[i].is_game_content = true game_mods[i].is_game_content = true
retval[#retval + 1] = game_mods[i] retval[#retval + 1] = game_mods[i]
end end
@ -439,8 +508,12 @@ function modmgr.preparemodlist(data)
return retval return retval
end end
function pkgmgr.compare_package(a, b)
return a and b and a.name == b.name and a.path == b.path
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.comparemod(elem1,elem2) function pkgmgr.comparemod(elem1,elem2)
if elem1 == nil or elem2 == nil then if elem1 == nil or elem2 == nil then
return false return false
end end
@ -450,7 +523,7 @@ function modmgr.comparemod(elem1,elem2)
if elem1.is_modpack ~= elem2.is_modpack then if elem1.is_modpack ~= elem2.is_modpack then
return false return false
end end
if elem1.typ ~= elem2.typ then if elem1.type ~= elem2.type then
return false return false
end end
if elem1.modpack ~= elem2.modpack then if elem1.modpack ~= elem2.modpack then
@ -465,13 +538,13 @@ function modmgr.comparemod(elem1,elem2)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.mod_exists(basename) function pkgmgr.mod_exists(basename)
if modmgr.global_mods == nil then if pkgmgr.global_mods == nil then
modmgr.refresh_globals() pkgmgr.refresh_globals()
end end
if modmgr.global_mods:raw_index_by_uid(basename) > 0 then if pkgmgr.global_mods:raw_index_by_uid(basename) > 0 then
return true return true
end end
@ -479,39 +552,35 @@ function modmgr.mod_exists(basename)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.get_global_mod(idx) function pkgmgr.get_global_mod(idx)
if modmgr.global_mods == nil then if pkgmgr.global_mods == nil then
return nil return nil
end end
if idx == nil or idx < 1 or if idx == nil or idx < 1 or
idx > modmgr.global_mods:size() then idx > pkgmgr.global_mods:size() then
return nil return nil
end end
return modmgr.global_mods:get_list()[idx] return pkgmgr.global_mods:get_list()[idx]
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.refresh_globals() function pkgmgr.refresh_globals()
modmgr.global_mods = filterlist.create( local function is_equal(element,uid) --uid match
modmgr.preparemodlist, --refresh if element.name == uid then
modmgr.comparemod, --compare return true
function(element,uid) --uid match end
if element.name == uid then end
return true pkgmgr.global_mods = filterlist.create(pkgmgr.preparemodlist,
end pkgmgr.comparemod, is_equal, nil, {})
end, pkgmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
nil, --filter pkgmgr.global_mods:set_sortmode("alphabetic")
{}
)
modmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
modmgr.global_mods:set_sortmode("alphabetic")
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.identify_filetype(name) function pkgmgr.identify_filetype(name)
if name:sub(-3):lower() == "zip" then if name:sub(-3):lower() == "zip" then
return { return {
@ -547,3 +616,69 @@ function modmgr.identify_filetype(name)
type = "ukn" type = "ukn"
} }
end 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()

View File

@ -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
}

View File

@ -17,7 +17,7 @@
local function current_game() local function current_game()
local last_game_id = core.settings:get("menu_last_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 return game
end end
@ -32,12 +32,12 @@ local function singleplayer_refresh_gamebar()
local function game_buttonbar_button_handler(fields) local function game_buttonbar_button_handler(fields)
for key,value in pairs(fields) do for key,value in pairs(fields) do
for j=1,#gamemgr.games,1 do for j=1,#pkgmgr.games,1 do
if ("game_btnbar_" .. gamemgr.games[j].id == key) then if ("game_btnbar_" .. pkgmgr.games[j].id == key) then
mm_texture.update("singleplayer", gamemgr.games[j]) mm_texture.update("singleplayer", pkgmgr.games[j])
core.set_topleft_text(gamemgr.games[j].name) core.set_topleft_text(pkgmgr.games[j].name)
core.settings:set("menu_last_game",gamemgr.games[j].id) core.settings:set("menu_last_game",pkgmgr.games[j].id)
menudata.worldlist:set_filtercriteria(gamemgr.games[j].id) menudata.worldlist:set_filtercriteria(pkgmgr.games[j].id)
local index = filterlist.get_current_index(menudata.worldlist, local index = filterlist.get_current_index(menudata.worldlist,
tonumber(core.settings:get("mainmenu_last_selected_world"))) tonumber(core.settings:get("mainmenu_last_selected_world")))
if not index or index < 1 then if not index or index < 1 then
@ -59,21 +59,21 @@ local function singleplayer_refresh_gamebar()
game_buttonbar_button_handler, game_buttonbar_button_handler,
{x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15}) {x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15})
for i=1,#gamemgr.games,1 do for i=1,#pkgmgr.games,1 do
local btn_name = "game_btnbar_" .. gamemgr.games[i].id local btn_name = "game_btnbar_" .. pkgmgr.games[i].id
local image = nil local image = nil
local text = 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 if pkgmgr.games[i].menuicon_path ~= nil and
gamemgr.games[i].menuicon_path ~= "" then pkgmgr.games[i].menuicon_path ~= "" then
image = core.formspec_escape(gamemgr.games[i].menuicon_path) image = core.formspec_escape(pkgmgr.games[i].menuicon_path)
else else
local part1 = gamemgr.games[i].id:sub(1,5) local part1 = pkgmgr.games[i].id:sub(1,5)
local part2 = gamemgr.games[i].id:sub(6,10) local part2 = pkgmgr.games[i].id:sub(6,10)
local part3 = gamemgr.games[i].id:sub(11) local part3 = pkgmgr.games[i].id:sub(11)
text = part1 .. "\n" .. part2 text = part1 .. "\n" .. part2
if part3 ~= nil and if part3 ~= nil and
@ -213,7 +213,7 @@ local function main_button_handler(this, fields, name, tabdata)
--update last game --update last game
local world = menudata.worldlist:get_raw_element(gamedata.selected_world) local world = menudata.worldlist:get_raw_element(gamedata.selected_world)
if world then 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) core.settings:set("menu_last_game", game.id)
end end

View File

@ -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
}

View File

@ -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
}

View File

@ -55,10 +55,10 @@ Where `gameid` is unique to each game.
The game directory can contain the following files: The game directory can contain the following files:
* `game.conf`, which contains: * `game.conf`, with the following keys:
* `name = <Human-readable full name of the game>` e.g. `name = Minetest` * `name` - required, human readable name e.g. `name = Minetest`
* Optionally, game.conf can also contain * `description` - Short description to be shown in the content tab
`disallowed_mapgens = <comma-separated mapgens>` * `disallowed_mapgens = <comma-separated mapgens>`
e.g. `disallowed_mapgens = v5,v6,flat` e.g. `disallowed_mapgens = v5,v6,flat`
These mapgens are removed from the list of mapgens for the game. These mapgens are removed from the list of mapgens for the game.
* `minetest.conf`: * `minetest.conf`:
@ -2544,6 +2544,9 @@ Helper functions
* returns time with microsecond precision. May not return wall time. * returns time with microsecond precision. May not return wall time.
* `table.copy(table)`: returns a table * `table.copy(table)`: returns a table
* returns a deep copy of `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 * `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a
position. position.
* returns the exact position on the surface of a pointed node * returns the exact position on the surface of a pointed node

View File

@ -33,14 +33,6 @@ core.close()
Filesystem: Filesystem:
core.get_builtin_path() core.get_builtin_path()
^ returns path to builtin root ^ 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) core.create_dir(absolute_path) (possible in async calls)
^ absolute_path to directory to create (needs to be absolute) ^ absolute_path to directory to create (needs to be absolute)
^ returns true/false ^ returns true/false
@ -71,6 +63,8 @@ core.get_video_drivers()
^ returns list of available video drivers' settings name and 'friendly' display name ^ 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"} } ^ 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 ^ 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: Formspec:
core.update_formspec(formspec) core.update_formspec(formspec)
@ -111,29 +105,58 @@ core.get_screen_info()
window_height = <current window height> window_height = <current window height>
} }
Packages: ### Content and Packages
core.get_game(index)
^ returns { Content - an installed mod, modpack, game, or texture pack (txt)
id = <id>, Package - content which is downloadable from the content db, may or may not be installed.
path = <full path to game>,
gamemods_path = <path>, * core.get_modpath() (possible in async calls)
name = <name of game>, * returns path to global modpath
menuicon_path = <full path to menuicon>, * core.get_clientmodpath() (possible in async calls)
DEPRECATED: * returns path to global client-side modpath
addon_mods_paths = {[1] = <path>,}, * core.get_gamepath() (possible in async calls)
} * returns path to global gamepath
core.get_games() -> table of all games in upper format (possible in async calls) * core.get_texturepath() (possible in async calls)
core.get_mapgen_names([include_hidden=false]) -> table of map generator algorithms * returns path to default textures
registered in the core (possible in async calls) * core.get_game(index)
core.get_mod_info(path) * returns:
^ returns {
name = "name of mod", {
type = "mod" or "modpack", id = <id>,
description = "description", path = <full path to game>,
path = "path/to/mod", gamemods_path = <path>,
depends = {"mod", "names"}, name = <name of game>,
optional_depends = {"mod", "names"}, 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: Favorites:
core.get_favorites(location) -> list of favorites (possible in async calls) core.get_favorites(location) -> list of favorites (possible in async calls)

View File

@ -9,6 +9,7 @@ Texture pack directory structure
textures textures
|-- Texture Pack |-- Texture Pack
| |-- texture_pack.conf
| |-- screenshot.png | |-- screenshot.png
| |-- description.txt | |-- description.txt
| |-- override.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 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. 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` ### `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 A file containing a short description of the texture pack to be shown in the
texture packs tab. content tab.
### `screenshot.png` ### `screenshot.png`
A preview image showing an in-game screenshot of this texture pack; it will be A preview image showing an in-game screenshot of this texture pack; it will be

View File

@ -358,6 +358,7 @@ add_custom_target(GenerateVersion
add_subdirectory(threading) add_subdirectory(threading)
add_subdirectory(content)
add_subdirectory(database) add_subdirectory(database)
add_subdirectory(gui) add_subdirectory(gui)
add_subdirectory(mapgen) add_subdirectory(mapgen)
@ -372,6 +373,7 @@ set(common_SRCS
${database_SRCS} ${database_SRCS}
${mapgen_SRCS} ${mapgen_SRCS}
${server_SRCS} ${server_SRCS}
${content_SRCS}
ban.cpp ban.cpp
chat.cpp chat.cpp
clientiface.cpp clientiface.cpp
@ -403,7 +405,6 @@ set(common_SRCS
mapsector.cpp mapsector.cpp
metadata.cpp metadata.cpp
modchannels.cpp modchannels.cpp
mods.cpp
nameidmapping.cpp nameidmapping.cpp
nodedef.cpp nodedef.cpp
nodemetadata.cpp nodemetadata.cpp
@ -428,7 +429,6 @@ set(common_SRCS
serverobject.cpp serverobject.cpp
settings.cpp settings.cpp
staticobject.cpp staticobject.cpp
subgame.cpp
terminal_chat_console.cpp terminal_chat_console.cpp
tileanimation.cpp tileanimation.cpp
tool.cpp tool.cpp

View File

@ -43,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock.h" #include "mapblock.h"
#include "minimap.h" #include "minimap.h"
#include "modchannels.h" #include "modchannels.h"
#include "mods.h" #include "content/mods.h"
#include "profiler.h" #include "profiler.h"
#include "shader.h" #include "shader.h"
#include "gettext.h" #include "gettext.h"

View File

@ -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
)

108
src/content/content.cpp Normal file
View File

@ -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>());
}
}

33
src/content/content.h Normal file
View File

@ -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);

View File

@ -21,23 +21,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <fstream> #include <fstream>
#include <json/json.h> #include <json/json.h>
#include <algorithm> #include <algorithm>
#include "mods.h" #include "content/mods.h"
#include "filesys.h" #include "filesys.h"
#include "log.h" #include "log.h"
#include "subgame.h" #include "content/subgames.h"
#include "settings.h" #include "settings.h"
#include "porting.h" #include "porting.h"
#include "convert_json.h" #include "convert_json.h"
bool parseDependsString(std::string &dep, bool parseDependsString(std::string &dep, std::unordered_set<char> &symbols)
std::unordered_set<char> &symbols)
{ {
dep = trim(dep); dep = trim(dep);
symbols.clear(); symbols.clear();
size_t pos = dep.size(); size_t pos = dep.size();
while (pos > 0 && !string_allowed(dep.substr(pos-1, 1), MODNAME_ALLOWED_CHARS)) { while (pos > 0 &&
!string_allowed(dep.substr(pos - 1, 1), MODNAME_ALLOWED_CHARS)) {
// last character is a symbol, not part of the modname // last character is a symbol, not part of the modname
symbols.insert(dep[pos-1]); symbols.insert(dep[pos - 1]);
--pos; --pos;
} }
dep = trim(dep.substr(0, pos)); dep = trim(dep.substr(0, pos));
@ -48,19 +48,22 @@ void parseModContents(ModSpec &spec)
{ {
// NOTE: this function works in mutual recursion with getModsInPath // NOTE: this function works in mutual recursion with getModsInPath
Settings info; Settings info;
info.readConfigFile((spec.path+DIR_DELIM+"mod.conf").c_str()); info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str());
if (info.exists("name")) if (info.exists("name"))
spec.name = info.get("name"); spec.name = info.get("name");
if (info.exists("author"))
spec.author = info.get("author");
spec.depends.clear(); spec.depends.clear();
spec.optdepends.clear(); spec.optdepends.clear();
spec.is_modpack = false; spec.is_modpack = false;
spec.modpack_content.clear(); spec.modpack_content.clear();
// Handle modpacks (defined by containing modpack.txt) // Handle modpacks (defined by containing modpack.txt)
std::ifstream modpack_is((spec.path+DIR_DELIM+"modpack.txt").c_str()); std::ifstream modpack_is((spec.path + DIR_DELIM + "modpack.txt").c_str());
if (modpack_is.good()) { // a modpack, recursively get the mods in it if (modpack_is.good()) { // a modpack, recursively get the mods in it
modpack_is.close(); // We don't actually need the file modpack_is.close(); // We don't actually need the file
spec.is_modpack = true; spec.is_modpack = true;
spec.modpack_content = getModsInPath(spec.path, true); spec.modpack_content = getModsInPath(spec.path, true);
@ -73,8 +76,10 @@ void parseModContents(ModSpec &spec)
if (info.exists("depends")) { if (info.exists("depends")) {
mod_conf_has_depends = true; mod_conf_has_depends = true;
std::string dep = info.get("depends"); std::string dep = info.get("depends");
// clang-format off
dep.erase(std::remove_if(dep.begin(), dep.end(), dep.erase(std::remove_if(dep.begin(), dep.end(),
static_cast<int(*)(int)>(&std::isspace)), dep.end()); static_cast<int (*)(int)>(&std::isspace)), dep.end());
// clang-format on
for (const auto &dependency : str_split(dep, ',')) { for (const auto &dependency : str_split(dep, ',')) {
spec.depends.insert(dependency); spec.depends.insert(dependency);
} }
@ -83,8 +88,10 @@ void parseModContents(ModSpec &spec)
if (info.exists("optional_depends")) { if (info.exists("optional_depends")) {
mod_conf_has_depends = true; mod_conf_has_depends = true;
std::string dep = info.get("optional_depends"); std::string dep = info.get("optional_depends");
// clang-format off
dep.erase(std::remove_if(dep.begin(), dep.end(), dep.erase(std::remove_if(dep.begin(), dep.end(),
static_cast<int(*)(int)>(&std::isspace)), dep.end()); static_cast<int (*)(int)>(&std::isspace)), dep.end());
// clang-format on
for (const auto &dependency : str_split(dep, ',')) { for (const auto &dependency : str_split(dep, ',')) {
spec.optdepends.insert(dependency); spec.optdepends.insert(dependency);
} }
@ -116,15 +123,16 @@ void parseModContents(ModSpec &spec)
if (info.exists("description")) { if (info.exists("description")) {
spec.desc = info.get("description"); spec.desc = info.get("description");
} else { } else {
std::ifstream is((spec.path + DIR_DELIM + "description.txt").c_str()); std::ifstream is((spec.path + DIR_DELIM + "description.txt")
.c_str());
spec.desc = std::string((std::istreambuf_iterator<char>(is)), spec.desc = std::string((std::istreambuf_iterator<char>(is)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
} }
} }
} }
std::map<std::string, ModSpec> getModsInPath(const std::string &path, std::map<std::string, ModSpec> getModsInPath(
bool part_of_modpack) const std::string &path, bool part_of_modpack)
{ {
// NOTE: this function works in mutual recursion with parseModContents // NOTE: this function works in mutual recursion with parseModContents
@ -143,9 +151,7 @@ std::map<std::string, ModSpec> getModsInPath(const std::string &path,
continue; continue;
modpath.clear(); modpath.clear();
modpath.append(path) modpath.append(path).append(DIR_DELIM).append(modname);
.append(DIR_DELIM)
.append(modname);
ModSpec spec(modname, modpath, part_of_modpack); ModSpec spec(modname, modpath, part_of_modpack);
parseModContents(spec); parseModContents(spec);
@ -162,10 +168,9 @@ std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
if (mod.is_modpack) { if (mod.is_modpack) {
std::vector<ModSpec> content = flattenMods(mod.modpack_content); std::vector<ModSpec> content = flattenMods(mod.modpack_content);
result.reserve(result.size() + content.size()); result.reserve(result.size() + content.size());
result.insert(result.end(),content.begin(),content.end()); result.insert(result.end(), content.begin(), content.end());
} } else // not a modpack
else //not a modpack
{ {
result.push_back(mod); result.push_back(mod);
} }
@ -180,7 +185,8 @@ ModConfiguration::ModConfiguration(const std::string &worldpath)
void ModConfiguration::printUnsatisfiedModsError() const void ModConfiguration::printUnsatisfiedModsError() const
{ {
for (const ModSpec &mod : m_unsatisfied_mods) { for (const ModSpec &mod : m_unsatisfied_mods) {
errorstream << "mod \"" << mod.name << "\" has unsatisfied dependencies: "; errorstream << "mod \"" << mod.name
<< "\" has unsatisfied dependencies: ";
for (const std::string &unsatisfied_depend : mod.unsatisfied_depends) for (const std::string &unsatisfied_depend : mod.unsatisfied_depends)
errorstream << " \"" << unsatisfied_depend << "\""; errorstream << " \"" << unsatisfied_depend << "\"";
errorstream << std::endl; errorstream << std::endl;
@ -197,12 +203,12 @@ void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods)
// Maintain a map of all existing m_unsatisfied_mods. // Maintain a map of all existing m_unsatisfied_mods.
// Keys are mod names and values are indices into m_unsatisfied_mods. // Keys are mod names and values are indices into m_unsatisfied_mods.
std::map<std::string, u32> existing_mods; std::map<std::string, u32> existing_mods;
for(u32 i = 0; i < m_unsatisfied_mods.size(); ++i){ for (u32 i = 0; i < m_unsatisfied_mods.size(); ++i) {
existing_mods[m_unsatisfied_mods[i].name] = i; existing_mods[m_unsatisfied_mods[i].name] = i;
} }
// Add new mods // Add new mods
for(int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack){ for (int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack) {
// First iteration: // First iteration:
// Add all the mods that come from modpacks // Add all the mods that come from modpacks
// Second iteration: // Second iteration:
@ -218,14 +224,16 @@ void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods)
// GOOD CASE: completely new mod. // GOOD CASE: completely new mod.
m_unsatisfied_mods.push_back(mod); m_unsatisfied_mods.push_back(mod);
existing_mods[mod.name] = m_unsatisfied_mods.size() - 1; existing_mods[mod.name] = m_unsatisfied_mods.size() - 1;
} else if(seen_this_iteration.count(mod.name) == 0) { } else if (seen_this_iteration.count(mod.name) == 0) {
// BAD CASE: name conflict in different levels. // BAD CASE: name conflict in different levels.
u32 oldindex = existing_mods[mod.name]; u32 oldindex = existing_mods[mod.name];
const ModSpec &oldmod = m_unsatisfied_mods[oldindex]; const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
warningstream<<"Mod name conflict detected: \"" warningstream << "Mod name conflict detected: \""
<<mod.name<<"\""<<std::endl << mod.name << "\"" << std::endl
<<"Will not load: "<<oldmod.path<<std::endl << "Will not load: " << oldmod.path
<<"Overridden by: "<<mod.path<<std::endl; << std::endl
<< "Overridden by: " << mod.path
<< std::endl;
m_unsatisfied_mods[oldindex] = mod; m_unsatisfied_mods[oldindex] = mod;
// If there was a "VERY BAD CASE" name conflict // If there was a "VERY BAD CASE" name conflict
@ -235,10 +243,12 @@ void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods)
// VERY BAD CASE: name conflict in the same level. // VERY BAD CASE: name conflict in the same level.
u32 oldindex = existing_mods[mod.name]; u32 oldindex = existing_mods[mod.name];
const ModSpec &oldmod = m_unsatisfied_mods[oldindex]; const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
warningstream<<"Mod name conflict detected: \"" warningstream << "Mod name conflict detected: \""
<<mod.name<<"\""<<std::endl << mod.name << "\"" << std::endl
<<"Will not load: "<<oldmod.path<<std::endl << "Will not load: " << oldmod.path
<<"Will not load: "<<mod.path<<std::endl; << std::endl
<< "Will not load: " << mod.path
<< std::endl;
m_unsatisfied_mods[oldindex] = mod; m_unsatisfied_mods[oldindex] = mod;
m_name_conflicts.insert(mod.name); m_name_conflicts.insert(mod.name);
} }
@ -248,7 +258,8 @@ void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods)
} }
} }
void ModConfiguration::addModsFromConfig(const std::string &settings_path, const std::set<std::string> &mods) void ModConfiguration::addModsFromConfig(
const std::string &settings_path, const std::set<std::string> &mods)
{ {
Settings conf; Settings conf;
std::set<std::string> load_mod_names; std::set<std::string> load_mod_names;
@ -256,7 +267,7 @@ void ModConfiguration::addModsFromConfig(const std::string &settings_path, const
conf.readConfigFile(settings_path.c_str()); conf.readConfigFile(settings_path.c_str());
std::vector<std::string> names = conf.getNames(); std::vector<std::string> names = conf.getNames();
for (const std::string &name : names) { for (const std::string &name : names) {
if (name.compare(0,9,"load_mod_")==0 && conf.getBool(name)) if (name.compare(0, 9, "load_mod_") == 0 && conf.getBool(name))
load_mod_names.insert(name.substr(9)); load_mod_names.insert(name.substr(9));
} }
@ -265,7 +276,7 @@ void ModConfiguration::addModsFromConfig(const std::string &settings_path, const
std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(i)); std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(i));
for (std::vector<ModSpec>::const_iterator it = addon_mods_in_path.begin(); for (std::vector<ModSpec>::const_iterator it = addon_mods_in_path.begin();
it != addon_mods_in_path.end(); ++it) { it != addon_mods_in_path.end(); ++it) {
const ModSpec& mod = *it; const ModSpec &mod = *it;
if (load_mod_names.count(mod.name) != 0) if (load_mod_names.count(mod.name) != 0)
addon_mods.push_back(mod); addon_mods.push_back(mod);
else else
@ -300,8 +311,10 @@ void ModConfiguration::checkConflictsAndDeps()
if (!m_name_conflicts.empty()) { if (!m_name_conflicts.empty()) {
std::string s = "Unresolved name conflicts for mods "; std::string s = "Unresolved name conflicts for mods ";
for (std::unordered_set<std::string>::const_iterator it = for (std::unordered_set<std::string>::const_iterator it =
m_name_conflicts.begin(); it != m_name_conflicts.end(); ++it) { m_name_conflicts.begin();
if (it != m_name_conflicts.begin()) s += ", "; it != m_name_conflicts.end(); ++it) {
if (it != m_name_conflicts.begin())
s += ", ";
s += std::string("\"") + (*it) + "\""; s += std::string("\"") + (*it) + "\"";
} }
s += "."; s += ".";
@ -340,12 +353,12 @@ void ModConfiguration::resolveDependencies()
// Step 3: mods without unmet dependencies can be appended to // Step 3: mods without unmet dependencies can be appended to
// the sorted list. // the sorted list.
while(!satisfied.empty()){ while (!satisfied.empty()) {
ModSpec mod = satisfied.back(); ModSpec mod = satisfied.back();
m_sorted_mods.push_back(mod); m_sorted_mods.push_back(mod);
satisfied.pop_back(); satisfied.pop_back();
for (auto it = unsatisfied.begin(); it != unsatisfied.end(); ) { for (auto it = unsatisfied.begin(); it != unsatisfied.end();) {
ModSpec& mod2 = *it; ModSpec &mod2 = *it;
mod2.unsatisfied_depends.erase(mod.name); mod2.unsatisfied_depends.erase(mod.name);
if (mod2.unsatisfied_depends.empty()) { if (mod2.unsatisfied_depends.empty()) {
satisfied.push_back(mod2); satisfied.push_back(mod2);
@ -361,8 +374,8 @@ void ModConfiguration::resolveDependencies()
} }
#ifndef SERVER #ifndef SERVER
ClientModConfiguration::ClientModConfiguration(const std::string &path): ClientModConfiguration::ClientModConfiguration(const std::string &path) :
ModConfiguration(path) ModConfiguration(path)
{ {
std::set<std::string> paths; std::set<std::string> paths;
std::string path_user = porting::path_user + DIR_DELIM + "clientmods"; std::string path_user = porting::path_user + DIR_DELIM + "clientmods";
@ -374,8 +387,7 @@ ClientModConfiguration::ClientModConfiguration(const std::string &path):
} }
#endif #endif
ModMetadata::ModMetadata(const std::string &mod_name): ModMetadata::ModMetadata(const std::string &mod_name) : m_mod_name(mod_name)
m_mod_name(mod_name)
{ {
} }
@ -395,23 +407,25 @@ bool ModMetadata::save(const std::string &root_path)
if (!fs::PathExists(root_path)) { if (!fs::PathExists(root_path)) {
if (!fs::CreateAllDirs(root_path)) { if (!fs::CreateAllDirs(root_path)) {
errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '" errorstream << "ModMetadata[" << m_mod_name
<< root_path << "' tree cannot be created." << std::endl; << "]: Unable to save. '" << root_path
<< "' tree cannot be created." << std::endl;
return false; return false;
} }
} else if (!fs::IsDir(root_path)) { } else if (!fs::IsDir(root_path)) {
errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '" errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '"
<< root_path << "' is not a directory." << std::endl; << root_path << "' is not a directory." << std::endl;
return false; return false;
} }
bool w_ok = fs::safeWriteToFile(root_path + DIR_DELIM + m_mod_name, bool w_ok = fs::safeWriteToFile(
fastWriteJson(json)); root_path + DIR_DELIM + m_mod_name, fastWriteJson(json));
if (w_ok) { if (w_ok) {
m_modified = false; m_modified = false;
} else { } else {
errorstream << "ModMetadata[" << m_mod_name << "]: failed write file." << std::endl; errorstream << "ModMetadata[" << m_mod_name << "]: failed write file."
<< std::endl;
} }
return w_ok; return w_ok;
} }
@ -420,7 +434,8 @@ bool ModMetadata::load(const std::string &root_path)
{ {
m_stringvars.clear(); m_stringvars.clear();
std::ifstream is((root_path + DIR_DELIM + m_mod_name).c_str(), std::ios_base::binary); std::ifstream is((root_path + DIR_DELIM + m_mod_name).c_str(),
std::ios_base::binary);
if (!is.good()) { if (!is.good()) {
return false; return false;
} }
@ -431,8 +446,10 @@ bool ModMetadata::load(const std::string &root_path)
std::string errs; std::string errs;
if (!Json::parseFromStream(builder, is, &root, &errs)) { if (!Json::parseFromStream(builder, is, &root, &errs)) {
errorstream << "ModMetadata[" << m_mod_name << "]: failed read data " errorstream << "ModMetadata[" << m_mod_name
"(Json decoding failure). Message: " << errs << std::endl; << "]: failed read data "
"(Json decoding failure). Message: "
<< errs << std::endl;
return false; return false;
} }

View File

@ -36,10 +36,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
struct ModSpec struct ModSpec
{ {
std::string name; std::string name;
std::string author;
std::string path; std::string path;
std::string desc; std::string desc;
//if normal mod: // if normal mod:
std::unordered_set<std::string> depends; std::unordered_set<std::string> depends;
std::unordered_set<std::string> optdepends; std::unordered_set<std::string> optdepends;
std::unordered_set<std::string> unsatisfied_depends; std::unordered_set<std::string> unsatisfied_depends;
@ -48,26 +49,25 @@ struct ModSpec
bool is_modpack = false; bool is_modpack = false;
// if modpack: // if modpack:
std::map<std::string,ModSpec> modpack_content; std::map<std::string, ModSpec> modpack_content;
ModSpec(const std::string &name_ = "", const std::string &path_ = ""): ModSpec(const std::string &name = "", const std::string &path = "") :
name(name_), name(name), path(path)
path(path_) {
{} }
ModSpec(const std::string &name_, const std::string &path_, bool part_of_modpack_): ModSpec(const std::string &name, const std::string &path, bool part_of_modpack) :
name(name_), name(name), path(path), part_of_modpack(part_of_modpack)
path(path_), {
part_of_modpack(part_of_modpack_) }
{}
}; };
// Retrieves depends, optdepends, is_modpack and modpack_content // Retrieves depends, optdepends, is_modpack and modpack_content
void parseModContents(ModSpec &mod); void parseModContents(ModSpec &mod);
std::map<std::string,ModSpec> getModsInPath(const std::string &path, std::map<std::string, ModSpec> getModsInPath(
bool part_of_modpack = false); const std::string &path, bool part_of_modpack = false);
// replaces modpack Modspecs with their content // replaces modpack Modspecs with their content
std::vector<ModSpec> flattenMods(std::map<std::string,ModSpec> mods); std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods);
// a ModConfiguration is a subset of installed mods, expected to have // 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 // all dependencies fullfilled, so it can be used as a list of mods to
@ -76,15 +76,9 @@ class ModConfiguration
{ {
public: public:
// checks if all dependencies are fullfilled. // checks if all dependencies are fullfilled.
bool isConsistent() const bool isConsistent() const { return m_unsatisfied_mods.empty(); }
{
return m_unsatisfied_mods.empty();
}
const std::vector<ModSpec> &getMods() const const std::vector<ModSpec> &getMods() const { return m_sorted_mods; }
{
return m_sorted_mods;
}
const std::vector<ModSpec> &getUnsatisfiedMods() const const std::vector<ModSpec> &getUnsatisfiedMods() const
{ {
@ -102,9 +96,11 @@ protected:
// adds all mods in the set. // adds all mods in the set.
void addMods(const std::vector<ModSpec> &new_mods); void addMods(const std::vector<ModSpec> &new_mods);
void addModsFromConfig(const std::string &settings_path, const std::set<std::string> &mods); void addModsFromConfig(const std::string &settings_path,
const std::set<std::string> &mods);
void checkConflictsAndDeps(); void checkConflictsAndDeps();
protected: protected:
// list of mods sorted such that they can be loaded in the // list of mods sorted such that they can be loaded in the
// given order with all dependencies being fullfilled. I.e., // given order with all dependencies being fullfilled. I.e.,
@ -133,18 +129,17 @@ private:
// Deleted default constructor // Deleted default constructor
ModConfiguration() = default; ModConfiguration() = default;
}; };
#ifndef SERVER #ifndef SERVER
class ClientModConfiguration: public ModConfiguration class ClientModConfiguration : public ModConfiguration
{ {
public: public:
ClientModConfiguration(const std::string &path); ClientModConfiguration(const std::string &path);
}; };
#endif #endif
class ModMetadata: public Metadata class ModMetadata : public Metadata
{ {
public: public:
ModMetadata() = delete; ModMetadata() = delete;
@ -160,6 +155,7 @@ public:
const std::string &getModName() const { return m_mod_name; } const std::string &getModName() const { return m_mod_name; }
virtual bool setString(const std::string &name, const std::string &var); virtual bool setString(const std::string &name, const std::string &var);
private: private:
std::string m_mod_name; std::string m_mod_name;
bool m_modified = false; bool m_modified = false;

68
src/content/packages.cpp Normal file
View File

@ -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

49
src/content/packages.h Normal file
View File

@ -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

View File

@ -17,18 +17,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "subgame.h" #include "content/subgames.h"
#include "porting.h" #include "porting.h"
#include "filesys.h" #include "filesys.h"
#include "settings.h" #include "settings.h"
#include "log.h" #include "log.h"
#include "util/strfnd.h" #include "util/strfnd.h"
#include "defaultsettings.h" // for override_default_settings #include "defaultsettings.h" // for override_default_settings
#include "mapgen/mapgen.h" // for MapgenParams #include "mapgen/mapgen.h" // for MapgenParams
#include "util/string.h" #include "util/string.h"
#ifndef SERVER #ifndef SERVER
#include "client/tile.h" // getImagePath #include "client/tile.h" // getImagePath
#endif #endif
bool getGameMinetestConfig(const std::string &game_path, Settings &conf) bool getGameMinetestConfig(const std::string &game_path, Settings &conf)
@ -37,30 +37,14 @@ bool getGameMinetestConfig(const std::string &game_path, Settings &conf)
return conf.readConfigFile(conf_path.c_str()); 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 struct GameFindPath
{ {
std::string path; std::string path;
bool user_specific; bool user_specific;
GameFindPath(const std::string &path, bool user_specific): GameFindPath(const std::string &path, bool user_specific) :
path(path), path(path), user_specific(user_specific)
user_specific(user_specific) {
{} }
}; };
std::string getSubgamePathEnv() std::string getSubgamePathEnv()
@ -75,21 +59,24 @@ SubgameSpec findSubgame(const std::string &id)
return SubgameSpec(); return SubgameSpec();
std::string share = porting::path_share; std::string share = porting::path_share;
std::string user = porting::path_user; std::string user = porting::path_user;
std::vector<GameFindPath> find_paths;
// Get games install locations
Strfnd search_paths(getSubgamePathEnv()); Strfnd search_paths(getSubgamePathEnv());
// Get all possible paths fo game
std::vector<GameFindPath> find_paths;
while (!search_paths.at_end()) { while (!search_paths.at_end()) {
std::string path = search_paths.next(PATH_DELIM); 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, false);
find_paths.emplace_back(path + DIR_DELIM + id + "_game", false); find_paths.emplace_back(path + DIR_DELIM + id + "_game", false);
} }
find_paths.emplace_back(
find_paths.emplace_back(user + DIR_DELIM + "games" + DIR_DELIM + id + "_game", true); 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(user + DIR_DELIM + "games" + DIR_DELIM + id, true);
find_paths.emplace_back(share + DIR_DELIM + "games" + DIR_DELIM + id + "_game", find_paths.emplace_back(
false); share + DIR_DELIM + "games" + DIR_DELIM + id + "_game", false);
find_paths.emplace_back(share + DIR_DELIM + "games" + DIR_DELIM + id, false); find_paths.emplace_back(share + DIR_DELIM + "games" + DIR_DELIM + id, false);
// Find game directory // Find game directory
std::string game_path; std::string game_path;
bool user_game = true; // Game is in user's directory bool user_game = true; // Game is in user's directory
@ -101,24 +88,41 @@ SubgameSpec findSubgame(const std::string &id)
break; break;
} }
} }
if (game_path.empty()) if (game_path.empty())
return SubgameSpec(); return SubgameSpec();
std::string gamemod_path = game_path + DIR_DELIM + "mods"; std::string gamemod_path = game_path + DIR_DELIM + "mods";
// Find mod directories // Find mod directories
std::set<std::string> mods_paths; std::set<std::string> mods_paths;
if(!user_game) if (!user_game)
mods_paths.insert(share + DIR_DELIM + "mods"); mods_paths.insert(share + DIR_DELIM + "mods");
if(user != share || user_game) if (user != share || user_game)
mods_paths.insert(user + DIR_DELIM + "mods"); mods_paths.insert(user + DIR_DELIM + "mods");
std::string game_name = getGameName(game_path);
if (game_name.empty()) // 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; game_name = id;
std::string game_author;
if (conf.exists("author"))
game_author = conf.get("author");
std::string menuicon_path; std::string menuicon_path;
#ifndef SERVER #ifndef SERVER
menuicon_path = getImagePath(game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png"); menuicon_path = getImagePath(
game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png");
#endif #endif
return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name, return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name,
menuicon_path); menuicon_path, game_author);
} }
SubgameSpec findWorldSubgame(const std::string &world_path) SubgameSpec findWorldSubgame(const std::string &world_path)
@ -126,14 +130,21 @@ SubgameSpec findWorldSubgame(const std::string &world_path)
std::string world_gameid = getWorldGameId(world_path, true); std::string world_gameid = getWorldGameId(world_path, true);
// See if world contains an embedded game; if so, use it. // See if world contains an embedded game; if so, use it.
std::string world_gamepath = world_path + DIR_DELIM + "game"; std::string world_gamepath = world_path + DIR_DELIM + "game";
if(fs::PathExists(world_gamepath)){ if (fs::PathExists(world_gamepath)) {
SubgameSpec gamespec; SubgameSpec gamespec;
gamespec.id = world_gameid; gamespec.id = world_gameid;
gamespec.path = world_gamepath; gamespec.path = world_gamepath;
gamespec.gamemods_path= world_gamepath + DIR_DELIM + "mods"; gamespec.gamemods_path = world_gamepath + DIR_DELIM + "mods";
gamespec.name = getGameName(world_gamepath);
if (gamespec.name.empty()) Settings conf;
gamespec.name = "unknown"; 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 gamespec;
} }
return findSubgame(world_gameid); return findSubgame(world_gameid);
@ -154,12 +165,16 @@ std::set<std::string> getAvailableGameIds()
for (const std::string &gamespath : gamespaths) { for (const std::string &gamespath : gamespaths) {
std::vector<fs::DirListNode> dirlist = fs::GetDirListing(gamespath); std::vector<fs::DirListNode> dirlist = fs::GetDirListing(gamespath);
for (const fs::DirListNode &dln : dirlist) { for (const fs::DirListNode &dln : dirlist) {
if(!dln.dir) if (!dln.dir)
continue; continue;
// If configuration file is not found or broken, ignore game // If configuration file is not found or broken, ignore game
Settings conf; Settings conf;
if(!getGameConfig(gamespath + DIR_DELIM + dln.name, conf)) std::string conf_path = gamespath + DIR_DELIM + dln.name +
DIR_DELIM + "game.conf";
if (!conf.readConfigFile(conf_path.c_str()))
continue; continue;
// Add it to result // Add it to result
const char *ends[] = {"_game", NULL}; const char *ends[] = {"_game", NULL};
std::string shorter = removeStringEnd(dln.name, ends); std::string shorter = removeStringEnd(dln.name, ends);
@ -194,18 +209,18 @@ std::string getWorldGameId(const std::string &world_path, bool can_be_legacy)
std::string conf_path = world_path + DIR_DELIM + "world.mt"; std::string conf_path = world_path + DIR_DELIM + "world.mt";
Settings conf; Settings conf;
bool succeeded = conf.readConfigFile(conf_path.c_str()); bool succeeded = conf.readConfigFile(conf_path.c_str());
if(!succeeded){ if (!succeeded) {
if(can_be_legacy){ if (can_be_legacy) {
// If map_meta.txt exists, it is probably an old minetest world // If map_meta.txt exists, it is probably an old minetest world
if(fs::PathExists(world_path + DIR_DELIM + "map_meta.txt")) if (fs::PathExists(world_path + DIR_DELIM + "map_meta.txt"))
return LEGACY_GAMEID; return LEGACY_GAMEID;
} }
return ""; return "";
} }
if(!conf.exists("gameid")) if (!conf.exists("gameid"))
return ""; return "";
// The "mesetint" gameid has been discarded // The "mesetint" gameid has been discarded
if(conf.get("gameid") == "mesetint") if (conf.get("gameid") == "mesetint")
return "minetest"; return "minetest";
return conf.get("gameid"); return conf.get("gameid");
} }
@ -229,10 +244,10 @@ std::vector<WorldSpec> getAvailableWorlds()
worldspaths.insert(porting::path_user + DIR_DELIM + "worlds"); worldspaths.insert(porting::path_user + DIR_DELIM + "worlds");
infostream << "Searching worlds..." << std::endl; infostream << "Searching worlds..." << std::endl;
for (const std::string &worldspath : worldspaths) { for (const std::string &worldspath : worldspaths) {
infostream << " In " << worldspath << ": " <<std::endl; infostream << " In " << worldspath << ": " << std::endl;
std::vector<fs::DirListNode> dirvector = fs::GetDirListing(worldspath); std::vector<fs::DirListNode> dirvector = fs::GetDirListing(worldspath);
for (const fs::DirListNode &dln : dirvector) { for (const fs::DirListNode &dln : dirvector) {
if(!dln.dir) if (!dln.dir)
continue; continue;
std::string fullpath = worldspath + DIR_DELIM + dln.name; std::string fullpath = worldspath + DIR_DELIM + dln.name;
std::string name = dln.name; std::string name = dln.name;
@ -240,27 +255,27 @@ std::vector<WorldSpec> getAvailableWorlds()
bool can_be_legacy = true; bool can_be_legacy = true;
std::string gameid = getWorldGameId(fullpath, can_be_legacy); std::string gameid = getWorldGameId(fullpath, can_be_legacy);
WorldSpec spec(fullpath, name, gameid); WorldSpec spec(fullpath, name, gameid);
if(!spec.isValid()){ if (!spec.isValid()) {
infostream<<"(invalid: "<<name<<") "; infostream << "(invalid: " << name << ") ";
} else { } else {
infostream<<name<<" "; infostream << name << " ";
worlds.push_back(spec); worlds.push_back(spec);
} }
} }
infostream<<std::endl; infostream << std::endl;
} }
// Check old world location // Check old world location
do{ do {
std::string fullpath = porting::path_user + DIR_DELIM + "world"; std::string fullpath = porting::path_user + DIR_DELIM + "world";
if(!fs::PathExists(fullpath)) if (!fs::PathExists(fullpath))
break; break;
std::string name = "Old World"; std::string name = "Old World";
std::string gameid = getWorldGameId(fullpath, true); std::string gameid = getWorldGameId(fullpath, true);
WorldSpec spec(fullpath, name, gameid); WorldSpec spec(fullpath, name, gameid);
infostream<<"Old world found."<<std::endl; infostream << "Old world found." << std::endl;
worlds.push_back(spec); worlds.push_back(spec);
}while(false); } while (false);
infostream<<worlds.size()<<" found."<<std::endl; infostream << worlds.size() << " found." << std::endl;
return worlds; return worlds;
} }
@ -297,8 +312,9 @@ bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamesp
// Create map_meta.txt if does not already exist // Create map_meta.txt if does not already exist
std::string map_meta_path = path + DIR_DELIM + "map_meta.txt"; std::string map_meta_path = path + DIR_DELIM + "map_meta.txt";
if (!fs::PathExists(map_meta_path)){ if (!fs::PathExists(map_meta_path)) {
verbosestream << "Creating map_meta.txt (" << map_meta_path << ")" << std::endl; verbosestream << "Creating map_meta.txt (" << map_meta_path << ")"
<< std::endl;
fs::CreateAllDirs(path); fs::CreateAllDirs(path);
std::ostringstream oss(std::ios_base::binary); std::ostringstream oss(std::ios_base::binary);
@ -314,4 +330,3 @@ bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamesp
} }
return true; return true;
} }

View File

@ -27,39 +27,33 @@ class Settings;
struct SubgameSpec struct SubgameSpec
{ {
std::string id; // "" = game does not exist std::string id;
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 name;
std::string author;
std::string path;
std::string gamemods_path;
std::set<std::string> addon_mods_paths;
std::string menuicon_path; std::string menuicon_path;
SubgameSpec(const std::string &id_ = "", SubgameSpec(const std::string &id = "", const std::string &path = "",
const std::string &path_ = "", const std::string &gamemods_path = "",
const std::string &gamemods_path_ = "", const std::set<std::string> &addon_mods_paths =
const std::set<std::string> &addon_mods_paths_ = std::set<std::string>(), std::set<std::string>(),
const std::string &name_ = "", const std::string &name = "",
const std::string &menuicon_path_ = ""): const std::string &menuicon_path = "",
id(id_), const std::string &author = "") :
path(path_), id(id),
gamemods_path(gamemods_path_), name(name), author(author), path(path),
addon_mods_paths(addon_mods_paths_), gamemods_path(gamemods_path), addon_mods_paths(addon_mods_paths),
name(name_), menuicon_path(menuicon_path)
menuicon_path(menuicon_path_)
{}
bool isValid() const
{ {
return (!id.empty() && !path.empty());
} }
bool isValid() const { return (!id.empty() && !path.empty()); }
}; };
// minetest.conf // minetest.conf
bool getGameMinetestConfig(const std::string &game_path, Settings &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 findSubgame(const std::string &id);
SubgameSpec findWorldSubgame(const std::string &world_path); SubgameSpec findWorldSubgame(const std::string &world_path);
@ -68,8 +62,7 @@ std::set<std::string> getAvailableGameIds();
std::vector<SubgameSpec> getAvailableGames(); std::vector<SubgameSpec> getAvailableGames();
bool getWorldExists(const std::string &world_path); bool getWorldExists(const std::string &world_path);
std::string getWorldGameId(const std::string &world_path, std::string getWorldGameId(const std::string &world_path, bool can_be_legacy = false);
bool can_be_legacy=false);
struct WorldSpec struct WorldSpec
{ {
@ -77,15 +70,12 @@ struct WorldSpec
std::string name; std::string name;
std::string gameid; std::string gameid;
WorldSpec( WorldSpec(const std::string &path = "", const std::string &name = "",
const std::string &path_="", const std::string &gameid = "") :
const std::string &name_="", path(path),
const std::string &gameid_="" name(name), gameid(gameid)
): {
path(path_), }
name(name_),
gameid(gameid_)
{}
bool isValid() const bool isValid() const
{ {

View File

@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <sstream> #include <sstream>
#include "convert_json.h" #include "convert_json.h"
#include "mods.h" #include "content/mods.h"
#include "config.h" #include "config.h"
#include "log.h" #include "log.h"
#include "settings.h" #include "settings.h"

View File

@ -284,6 +284,7 @@ void set_default_settings(Settings *settings)
#endif #endif
settings->setDefault("font_size", font_size_str); settings->setDefault("font_size", font_size_str);
settings->setDefault("mono_font_size", font_size_str); settings->setDefault("mono_font_size", font_size_str);
settings->setDefault("contentdb_url", "https://contentdb.rubenwardy.com");
// Server // Server

View File

@ -245,7 +245,7 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_,
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 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"); curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");
std::string bind_address = g_settings->get("bind_address"); std::string bind_address = g_settings->get("bind_address");

View File

@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "common/c_converter.h" #include "common/c_converter.h"
#include "serverobject.h" #include "serverobject.h"
#include "filesys.h" #include "filesys.h"
#include "mods.h" #include "content/mods.h"
#include "porting.h" #include "porting.h"
#include "util/string.h" #include "util/string.h"
#include "server.h" #include "server.h"

View 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_base.h"
#include "lua_api/l_internal.h" #include "lua_api/l_internal.h"
#include "cpp_api/s_base.h" #include "cpp_api/s_base.h"
#include <mods.h> #include "content/mods.h"
#include <server.h> #include <server.h>
ScriptApiBase *ModApiBase::getScriptApiBase(lua_State *L) ScriptApiBase *ModApiBase::getScriptApiBase(lua_State *L)

View File

@ -25,11 +25,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gui/guiMainMenu.h" #include "gui/guiMainMenu.h"
#include "gui/guiKeyChangeMenu.h" #include "gui/guiKeyChangeMenu.h"
#include "gui/guiPathSelectMenu.h" #include "gui/guiPathSelectMenu.h"
#include "subgame.h"
#include "version.h" #include "version.h"
#include "porting.h" #include "porting.h"
#include "filesys.h" #include "filesys.h"
#include "convert_json.h" #include "convert_json.h"
#include "content/packages.h"
#include "content/content.h"
#include "content/subgames.h"
#include "serverlist.h" #include "serverlist.h"
#include "mapgen/mapgen.h" #include "mapgen/mapgen.h"
#include "settings.h" #include "settings.h"
@ -449,6 +451,10 @@ int ModApiMainMenu::l_get_games(lua_State *L)
lua_pushstring(L, game.path.c_str()); lua_pushstring(L, game.path.c_str());
lua_settable(L, top_lvl2); 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, "gamemods_path");
lua_pushstring(L, game.gamemods_path.c_str()); lua_pushstring(L, game.gamemods_path.c_str());
lua_settable(L, top_lvl2); 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_pushstring(L, game.name.c_str());
lua_settable(L, top_lvl2); 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, "menuicon_path");
lua_pushstring(L, game.menuicon_path.c_str()); lua_pushstring(L, game.menuicon_path.c_str());
lua_settable(L, top_lvl2); 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); std::string path = luaL_checkstring(L, 1);
ModSpec spec; ContentSpec spec;
spec.path = path; spec.path = path;
parseModContents(spec); parseContentInfo(spec);
lua_newtable(L); lua_newtable(L);
lua_pushstring(L, spec.name.c_str()); lua_pushstring(L, spec.name.c_str());
lua_setfield(L, -2, "name"); 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_setfield(L, -2, "type");
lua_pushstring(L, spec.author.c_str());
lua_setfield(L, -2, "author");
lua_pushstring(L, spec.desc.c_str()); lua_pushstring(L, spec.desc.c_str());
lua_setfield(L, -2, "description"); lua_setfield(L, -2, "description");
lua_pushstring(L, spec.path.c_str()); lua_pushstring(L, spec.path.c_str());
lua_setfield(L, -2, "path"); lua_setfield(L, -2, "path");
// Dependencies if (spec.type == "mod") {
lua_newtable(L); ModSpec spec;
int i = 1; spec.path = path;
for (const auto &dep : spec.depends) { parseModContents(spec);
lua_pushstring(L, dep.c_str());
lua_rawseti(L, -2, i);
i++;
}
lua_setfield(L, -2, "depends");
// Optional Dependencies // Dependencies
lua_newtable(L); lua_newtable(L);
i = 1; int i = 1;
for (const auto &dep : spec.optdepends) { for (const auto &dep : spec.depends) {
lua_pushstring(L, dep.c_str()); lua_pushstring(L, dep.c_str());
lua_rawseti(L, -2, i); lua_rawseti(L, -2, i);
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; return 1;
} }
@ -838,6 +857,10 @@ bool ModApiMainMenu::isMinetestPath(std::string path)
if (fs::PathStartsWith(path,fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM + "mods"))) if (fs::PathStartsWith(path,fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM + "mods")))
return true; return true;
/* mods */
if (fs::PathStartsWith(path,fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM + "textures")))
return true;
/* worlds */ /* worlds */
if (fs::PathStartsWith(path,fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM + "worlds"))) if (fs::PathStartsWith(path,fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM + "worlds")))
return true; return true;
@ -972,6 +995,54 @@ int ModApiMainMenu::l_get_screen_info(lua_State *L)
return 1; 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) 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_table_index);
API_FCT(get_worlds); API_FCT(get_worlds);
API_FCT(get_games); API_FCT(get_games);
API_FCT(get_mod_info); API_FCT(get_content_info);
API_FCT(start); API_FCT(start);
API_FCT(close); API_FCT(close);
API_FCT(get_favorites); 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_drivers);
API_FCT(get_video_modes); API_FCT(get_video_modes);
API_FCT(get_screen_info); API_FCT(get_screen_info);
API_FCT(get_package_list);
API_FCT(get_min_supp_proto); API_FCT(get_min_supp_proto);
API_FCT(get_max_supp_proto); API_FCT(get_max_supp_proto);
API_FCT(do_async_callback); 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) void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
{ {
API_FCT(get_worlds); API_FCT(get_worlds);
API_FCT(get_games); API_FCT(get_games);
API_FCT(get_favorites); 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(extract_zip); //TODO remove dependency to GuiEngine
API_FCT(download_file); API_FCT(download_file);
//API_FCT(gettext); (gettext lib isn't threadsafe) //API_FCT(gettext); (gettext lib isn't threadsafe)
API_FCT(get_package_list);
} }

View File

@ -83,7 +83,7 @@ private:
static int l_get_games(lua_State *L); 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 //gui
@ -133,6 +133,9 @@ private:
static int l_get_video_modes(lua_State *L); static int l_get_video_modes(lua_State *L);
//content store
static int l_get_package_list(lua_State *L);
//version compatibility //version compatibility
static int l_get_min_supp_proto(lua_State *L); static int l_get_min_supp_proto(lua_State *L);

View File

@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_storage.h" #include "lua_api/l_storage.h"
#include "l_internal.h" #include "l_internal.h"
#include "mods.h" #include "content/mods.h"
#include "server.h" #include "server.h"
int ModApiStorage::l_get_mod_storage(lua_State *L) int ModApiStorage::l_get_mod_storage(lua_State *L)

View File

@ -18,7 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/ */
#include "scripting_mainmenu.h" #include "scripting_mainmenu.h"
#include "mods.h" #include "content/mods.h"
#include "cpp_api/s_internal.h" #include "cpp_api/s_internal.h"
#include "lua_api/l_base.h" #include "lua_api/l_base.h"
#include "lua_api/l_mainmenu.h" #include "lua_api/l_mainmenu.h"

View File

@ -49,7 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "content_mapnode.h" #include "content_mapnode.h"
#include "content_nodemeta.h" #include "content_nodemeta.h"
#include "content_sao.h" #include "content_sao.h"
#include "mods.h" #include "content/mods.h"
#include "modchannels.h" #include "modchannels.h"
#include "serverlist.h" #include "serverlist.h"
#include "util/string.h" #include "util/string.h"

View File

@ -24,9 +24,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "hud.h" #include "hud.h"
#include "gamedef.h" #include "gamedef.h"
#include "serialization.h" // For SER_FMT_VER_INVALID #include "serialization.h" // For SER_FMT_VER_INVALID
#include "mods.h" #include "content/mods.h"
#include "inventorymanager.h" #include "inventorymanager.h"
#include "subgame.h" #include "content/subgames.h"
#include "tileanimation.h" // struct TileAnimationParams #include "tileanimation.h" // struct TileAnimationParams
#include "network/peerhandler.h" #include "network/peerhandler.h"
#include "network/address.h" #include "network/address.h"

View File

@ -21,7 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "filesys.h" #include "filesys.h"
#include "log.h" #include "log.h"
#include "scripting_server.h" #include "scripting_server.h"
#include "subgame.h" #include "content/subgames.h"
/** /**
* Manage server mods * Manage server mods

View File

@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once #pragma once
#include "../mods.h" #include "content/mods.h"
class ServerScripting; class ServerScripting;

View File

@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <iostream> #include <iostream>
#include "config.h" #include "config.h"
#include "mods.h" #include "content/mods.h"
#include <json/json.h> #include <json/json.h>
#pragma once #pragma once

View File

@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "itemdef.h" #include "itemdef.h"
#include "gamedef.h" #include "gamedef.h"
#include "modchannels.h" #include "modchannels.h"
#include "mods.h" #include "content/mods.h"
#include "util/numeric.h" #include "util/numeric.h"
content_t t_CONTENT_STONE; content_t t_CONTENT_STONE;