summaryrefslogtreecommitdiff
path: root/builtin/mainmenu
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/mainmenu')
-rw-r--r--builtin/mainmenu/common.lua91
-rw-r--r--builtin/mainmenu/dlg_config_world.lua2
-rw-r--r--builtin/mainmenu/dlg_contentstore.lua576
-rw-r--r--builtin/mainmenu/dlg_create_world.lua19
-rw-r--r--builtin/mainmenu/init.lua112
-rw-r--r--builtin/mainmenu/pkgmgr.lua65
-rw-r--r--builtin/mainmenu/serverlistmgr.lua250
-rw-r--r--builtin/mainmenu/tab_credits.lua74
-rw-r--r--builtin/mainmenu/tab_local.lua34
-rw-r--r--builtin/mainmenu/tab_online.lua124
-rw-r--r--builtin/mainmenu/tab_settings.lua116
-rw-r--r--builtin/mainmenu/tab_simple_main.lua220
-rw-r--r--builtin/mainmenu/tests/favorites_wellformed.txt29
-rw-r--r--builtin/mainmenu/tests/serverlistmgr_spec.lua36
14 files changed, 1088 insertions, 660 deletions
diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua
index 782d6973f..cd896f9ec 100644
--- a/builtin/mainmenu/common.lua
+++ b/builtin/mainmenu/common.lua
@@ -62,24 +62,6 @@ function image_column(tooltip, flagname)
"5=" .. core.formspec_escape(defaulttexturedir .. "server_ping_1.png")
end
---------------------------------------------------------------------------------
-function order_favorite_list(list)
- local res = {}
- --orders the favorite list after support
- for i = 1, #list do
- local fav = list[i]
- if is_server_protocol_compat(fav.proto_min, fav.proto_max) then
- res[#res + 1] = fav
- end
- end
- for i = 1, #list do
- local fav = list[i]
- if not is_server_protocol_compat(fav.proto_min, fav.proto_max) then
- res[#res + 1] = fav
- end
- end
- return res
-end
--------------------------------------------------------------------------------
function render_serverlist_row(spec, is_favorite)
@@ -87,7 +69,7 @@ function render_serverlist_row(spec, is_favorite)
if spec.name then
text = text .. core.formspec_escape(spec.name:trim())
elseif spec.address then
- text = text .. spec.address:trim()
+ text = text .. core.formspec_escape(spec.address:trim())
if spec.port then
text = text .. ":" .. spec.port
end
@@ -164,35 +146,15 @@ end
--------------------------------------------------------------------------------
os.tempfolder = function()
- if core.settings:get("TMPFolder") then
- return core.settings:get("TMPFolder") .. DIR_DELIM .. "MT_" .. math.random(0,10000)
- end
-
- local filetocheck = os.tmpname()
- os.remove(filetocheck)
-
- -- luacheck: ignore
- -- https://blogs.msdn.microsoft.com/vcblog/2014/06/18/c-runtime-crt-features-fixes-and-breaking-changes-in-visual-studio-14-ctp1/
- -- The C runtime (CRT) function called by os.tmpname is tmpnam.
- -- Microsofts tmpnam implementation in older CRT / MSVC releases is defective.
- -- tmpnam return values starting with a backslash characterize this behavior.
- -- https://sourceforge.net/p/mingw-w64/bugs/555/
- -- MinGW tmpnam implementation is forwarded to the CRT directly.
- -- https://sourceforge.net/p/mingw-w64/discussion/723797/thread/55520785/
- -- MinGW links to an older CRT release (msvcrt.dll).
- -- Due to legal concerns MinGW will never use a newer CRT.
- --
- -- Make use of TEMP to compose the temporary filename if an old
- -- style tmpnam return value is detected.
- if filetocheck:sub(1, 1) == "\\" then
- local tempfolder = os.getenv("TEMP")
- return tempfolder .. filetocheck
- end
+ local temp = core.get_temp_path()
+ return temp .. DIR_DELIM .. "MT_" .. math.random(0, 10000)
+end
- local randname = "MTTempModFolder_" .. math.random(0,10000)
- local backstring = filetocheck:reverse()
- return filetocheck:sub(0, filetocheck:len() - backstring:find(DIR_DELIM) + 1) ..
- randname
+--------------------------------------------------------------------------------
+os.tmpname = function()
+ local path = os.tempfolder()
+ io.open(path, "w"):close()
+ return path
end
--------------------------------------------------------------------------------
@@ -227,41 +189,6 @@ function menu_handle_key_up_down(fields, textlist, settingname)
end
--------------------------------------------------------------------------------
-function asyncOnlineFavourites()
- if not menudata.public_known then
- menudata.public_known = {{
- name = fgettext("Loading..."),
- description = fgettext_ne("Try reenabling public serverlist and check your internet connection.")
- }}
- end
- menudata.favorites = menudata.public_known
- menudata.favorites_is_public = true
-
- if not menudata.public_downloading then
- menudata.public_downloading = true
- else
- return
- end
-
- core.handle_async(
- function(param)
- return core.get_favorites("online")
- end,
- nil,
- function(result)
- menudata.public_downloading = nil
- local favs = order_favorite_list(result)
- if favs[1] then
- menudata.public_known = favs
- menudata.favorites = menudata.public_known
- menudata.favorites_is_public = true
- end
- core.event_handler("Refresh")
- end
- )
-end
-
---------------------------------------------------------------------------------
function text2textlist(xpos, ypos, width, height, tl_name, textlen, text, transparency)
local textlines = core.wrap_text(text, textlen, true)
local retval = "textlist[" .. xpos .. "," .. ypos .. ";" .. width ..
diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua
index 2cf70c9c9..9bdf92a74 100644
--- a/builtin/mainmenu/dlg_config_world.lua
+++ b/builtin/mainmenu/dlg_config_world.lua
@@ -74,7 +74,7 @@ local function get_formspec(data)
"label[1.75,0;" .. data.worldspec.name .. "]"
if mod.is_modpack or mod.type == "game" then
- local info = minetest.formspec_escape(
+ local info = core.formspec_escape(
core.get_content_info(mod.path).description)
if info == "" then
if mod.is_modpack then
diff --git a/builtin/mainmenu/dlg_contentstore.lua b/builtin/mainmenu/dlg_contentstore.lua
index 01c42be0b..b0736a4fd 100644
--- a/builtin/mainmenu/dlg_contentstore.lua
+++ b/builtin/mainmenu/dlg_contentstore.lua
@@ -15,7 +15,7 @@
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-if not minetest.get_http_api then
+if not core.get_http_api then
function create_store_dlg()
return messagebox("store",
fgettext("ContentDB is not available when Minetest was compiled without cURL"))
@@ -23,9 +23,11 @@ if not minetest.get_http_api then
return
end
-local store = { packages = {}, packages_full = {} }
+-- Unordered preserves the original order of the ContentDB API,
+-- before the package list is ordered based on installed state.
+local store = { packages = {}, packages_full = {}, packages_full_unordered = {} }
-local http = minetest.get_http_api()
+local http = core.get_http_api()
-- Screenshot
local screenshot_dir = core.get_cache_path() .. DIR_DELIM .. "cdb"
@@ -45,6 +47,9 @@ local filter_types_titles = {
fgettext("Texture packs"),
}
+local number_downloading = 0
+local download_queue = {}
+
local filter_types_type = {
nil,
"game",
@@ -67,12 +72,14 @@ local function download_package(param)
end
end
-local function start_install(calling_dialog, package)
+local function start_install(package)
local params = {
package = package,
filename = os.tempfolder() .. "_MODNAME_" .. package.name .. ".zip",
}
+ number_downloading = number_downloading + 1
+
local function callback(result)
if result.successful then
local path, msg = pkgmgr.install(package.type,
@@ -121,9 +128,20 @@ local function start_install(calling_dialog, package)
end
package.downloading = false
+
+ number_downloading = number_downloading - 1
+
+ local next = download_queue[1]
+ if next then
+ table.remove(download_queue, 1)
+
+ start_install(next)
+ end
+
ui.update()
end
+ package.queued = false
package.downloading = true
if not core.handle_async(download_package, params, callback) then
@@ -133,6 +151,341 @@ local function start_install(calling_dialog, package)
end
end
+local function queue_download(package)
+ local max_concurrent_downloads = tonumber(core.settings:get("contentdb_max_concurrent_downloads"))
+ if number_downloading < max_concurrent_downloads then
+ start_install(package)
+ else
+ table.insert(download_queue, package)
+ package.queued = true
+ end
+end
+
+local function get_raw_dependencies(package)
+ if package.raw_deps then
+ return package.raw_deps
+ end
+
+ local url_fmt = "/api/packages/%s/dependencies/?only_hard=1&protocol_version=%s&engine_version=%s"
+ local version = core.get_version()
+ local base_url = core.settings:get("contentdb_url")
+ local url = base_url .. url_fmt:format(package.id, core.get_max_supp_proto(), version.string)
+
+ local response = http.fetch_sync({ url = url })
+ if not response.succeeded then
+ return
+ end
+
+ local data = core.parse_json(response.data) or {}
+
+ local content_lookup = {}
+ for _, pkg in pairs(store.packages_full) do
+ content_lookup[pkg.id] = pkg
+ end
+
+ for id, raw_deps in pairs(data) do
+ local package2 = content_lookup[id:lower()]
+ if package2 and not package2.raw_deps then
+ package2.raw_deps = raw_deps
+
+ for _, dep in pairs(raw_deps) do
+ local packages = {}
+ for i=1, #dep.packages do
+ packages[#packages + 1] = content_lookup[dep.packages[i]:lower()]
+ end
+ dep.packages = packages
+ end
+ end
+ end
+
+ return package.raw_deps
+end
+
+local function has_hard_deps(raw_deps)
+ for i=1, #raw_deps do
+ if not raw_deps[i].is_optional then
+ return true
+ end
+ end
+
+ return false
+end
+
+-- Recursively resolve dependencies, given the installed mods
+local function resolve_dependencies_2(raw_deps, installed_mods, out)
+ local function resolve_dep(dep)
+ -- Check whether it's already installed
+ if installed_mods[dep.name] then
+ return {
+ is_optional = dep.is_optional,
+ name = dep.name,
+ installed = true,
+ }
+ end
+
+ -- Find exact name matches
+ local fallback
+ for _, package in pairs(dep.packages) do
+ if package.type ~= "game" then
+ if package.name == dep.name then
+ return {
+ is_optional = dep.is_optional,
+ name = dep.name,
+ installed = false,
+ package = package,
+ }
+ elseif not fallback then
+ fallback = package
+ end
+ end
+ end
+
+ -- Otherwise, find the first mod that fulfils it
+ if fallback then
+ return {
+ is_optional = dep.is_optional,
+ name = dep.name,
+ installed = false,
+ package = fallback,
+ }
+ end
+
+ return {
+ is_optional = dep.is_optional,
+ name = dep.name,
+ installed = false,
+ }
+ end
+
+ for _, dep in pairs(raw_deps) do
+ if not dep.is_optional and not out[dep.name] then
+ local result = resolve_dep(dep)
+ out[dep.name] = result
+ if result and result.package and not result.installed then
+ local raw_deps2 = get_raw_dependencies(result.package)
+ if raw_deps2 then
+ resolve_dependencies_2(raw_deps2, installed_mods, out)
+ end
+ end
+ end
+ end
+
+ return true
+end
+
+-- Resolve dependencies for a package, calls the recursive version.
+local function resolve_dependencies(raw_deps, game)
+ assert(game)
+
+ local installed_mods = {}
+
+ local mods = {}
+ pkgmgr.get_game_mods(game, mods)
+ for _, mod in pairs(mods) do
+ installed_mods[mod.name] = true
+ end
+
+ for _, mod in pairs(pkgmgr.global_mods:get_list()) do
+ installed_mods[mod.name] = true
+ end
+
+ local out = {}
+ if not resolve_dependencies_2(raw_deps, installed_mods, out) then
+ return nil
+ end
+
+ local retval = {}
+ for _, dep in pairs(out) do
+ retval[#retval + 1] = dep
+ end
+
+ table.sort(retval, function(a, b)
+ return a.name < b.name
+ end)
+
+ return retval
+end
+
+local install_dialog = {}
+function install_dialog.get_formspec()
+ local package = install_dialog.package
+ local raw_deps = install_dialog.raw_deps
+ local will_install_deps = install_dialog.will_install_deps
+
+ local selected_game_idx = 1
+ local selected_gameid = core.settings:get("menu_last_game")
+ local games = table.copy(pkgmgr.games)
+ for i=1, #games do
+ if selected_gameid and games[i].id == selected_gameid then
+ selected_game_idx = i
+ end
+
+ games[i] = core.formspec_escape(games[i].name)
+ end
+
+ local selected_game = pkgmgr.games[selected_game_idx]
+ local deps_to_install = 0
+ local deps_not_found = 0
+
+ install_dialog.dependencies = resolve_dependencies(raw_deps, selected_game)
+ local formatted_deps = {}
+ for _, dep in pairs(install_dialog.dependencies) do
+ formatted_deps[#formatted_deps + 1] = "#fff"
+ formatted_deps[#formatted_deps + 1] = core.formspec_escape(dep.name)
+ if dep.installed then
+ formatted_deps[#formatted_deps + 1] = "#ccf"
+ formatted_deps[#formatted_deps + 1] = fgettext("Already installed")
+ elseif dep.package then
+ formatted_deps[#formatted_deps + 1] = "#cfc"
+ formatted_deps[#formatted_deps + 1] = fgettext("$1 by $2", dep.package.title, dep.package.author)
+ deps_to_install = deps_to_install + 1
+ else
+ formatted_deps[#formatted_deps + 1] = "#f00"
+ formatted_deps[#formatted_deps + 1] = fgettext("Not found")
+ deps_not_found = deps_not_found + 1
+ end
+ end
+
+ local message_bg = "#3333"
+ local message
+ if will_install_deps then
+ message = fgettext("$1 and $2 dependencies will be installed.", package.title, deps_to_install)
+ else
+ message = fgettext("$1 will be installed, and $2 dependencies will be skipped.", package.title, deps_to_install)
+ end
+ if deps_not_found > 0 then
+ message = fgettext("$1 required dependencies could not be found.", deps_not_found) ..
+ " " .. fgettext("Please check that the base game is correct.", deps_not_found) ..
+ "\n" .. message
+ message_bg = mt_color_orange
+ end
+
+ local formspec = {
+ "formspec_version[3]",
+ "size[7,7.85]",
+ "style[title;border=false]",
+ "box[0,0;7,0.5;#3333]",
+ "button[0,0;7,0.5;title;", fgettext("Install $1", package.title) , "]",
+
+ "container[0.375,0.70]",
+
+ "label[0,0.25;", fgettext("Base Game:"), "]",
+ "dropdown[2,0;4.25,0.5;gameid;", table.concat(games, ","), ";", selected_game_idx, "]",
+
+ "label[0,0.8;", fgettext("Dependencies:"), "]",
+
+ "tablecolumns[color;text;color;text]",
+ "table[0,1.1;6.25,3;packages;", table.concat(formatted_deps, ","), "]",
+
+ "container_end[]",
+
+ "checkbox[0.375,5.1;will_install_deps;",
+ fgettext("Install missing dependencies"), ";",
+ will_install_deps and "true" or "false", "]",
+
+ "box[0,5.4;7,1.2;", message_bg, "]",
+ "textarea[0.375,5.5;6.25,1;;;", message, "]",
+
+ "container[1.375,6.85]",
+ "button[0,0;2,0.8;install_all;", fgettext("Install"), "]",
+ "button[2.25,0;2,0.8;cancel;", fgettext("Cancel"), "]",
+ "container_end[]",
+ }
+
+ return table.concat(formspec, "")
+end
+
+function install_dialog.handle_submit(this, fields)
+ if fields.cancel then
+ this:delete()
+ return true
+ end
+
+ if fields.will_install_deps ~= nil then
+ install_dialog.will_install_deps = core.is_yes(fields.will_install_deps)
+ return true
+ end
+
+ if fields.install_all then
+ queue_download(install_dialog.package)
+
+ if install_dialog.will_install_deps then
+ for _, dep in pairs(install_dialog.dependencies) do
+ if not dep.is_optional and not dep.installed and dep.package then
+ queue_download(dep.package)
+ end
+ end
+ end
+
+ this:delete()
+ return true
+ end
+
+ if fields.gameid then
+ for _, game in pairs(pkgmgr.games) do
+ if game.name == fields.gameid then
+ core.settings:set("menu_last_game", game.id)
+ break
+ end
+ end
+ return true
+ end
+
+ return false
+end
+
+function install_dialog.create(package, raw_deps)
+ install_dialog.dependencies = nil
+ install_dialog.package = package
+ install_dialog.raw_deps = raw_deps
+ install_dialog.will_install_deps = true
+ return dialog_create("install_dialog",
+ install_dialog.get_formspec,
+ install_dialog.handle_submit,
+ nil)
+end
+
+
+local confirm_overwrite = {}
+function confirm_overwrite.get_formspec()
+ local package = confirm_overwrite.package
+
+ return "size[11.5,4.5,true]" ..
+ "label[2,2;" ..
+ fgettext("\"$1\" already exists. Would you like to overwrite it?", package.name) .. "]"..
+ "style[install;bgcolor=red]" ..
+ "button[3.25,3.5;2.5,0.5;install;" .. fgettext("Overwrite") .. "]" ..
+ "button[5.75,3.5;2.5,0.5;cancel;" .. fgettext("Cancel") .. "]"
+end
+
+function confirm_overwrite.handle_submit(this, fields)
+ if fields.cancel then
+ this:delete()
+ return true
+ end
+
+ if fields.install then
+ this:delete()
+ confirm_overwrite.callback()
+ return true
+ end
+
+ return false
+end
+
+function confirm_overwrite.create(package, callback)
+ assert(type(package) == "table")
+ assert(type(callback) == "function")
+
+ confirm_overwrite.package = package
+ confirm_overwrite.callback = callback
+ return dialog_create("confirm_overwrite",
+ confirm_overwrite.get_formspec,
+ confirm_overwrite.handle_submit,
+ nil)
+end
+
+
local function get_file_extension(path)
local parts = path:split(".")
return parts[#parts]
@@ -200,7 +553,7 @@ function store.load()
end
end
- local timeout = tonumber(minetest.settings:get("curl_file_download_timeout"))
+ local timeout = tonumber(core.settings:get("curl_file_download_timeout"))
local response = http.fetch_sync({ url = url, timeout = timeout })
if not response.succeeded then
return
@@ -221,6 +574,7 @@ function store.load()
end
end
+ store.packages_full_unordered = store.packages_full
store.packages = store.packages_full
store.loaded = true
end
@@ -229,7 +583,7 @@ function store.update_paths()
local mod_hash = {}
pkgmgr.refresh_globals()
for _, mod in pairs(pkgmgr.global_mods:get_list()) do
- if mod.author then
+ if mod.author and mod.release > 0 then
mod_hash[mod.author:lower() .. "/" .. mod.name] = mod
end
end
@@ -237,14 +591,14 @@ function store.update_paths()
local game_hash = {}
pkgmgr.update_gamelist()
for _, game in pairs(pkgmgr.games) do
- if game.author ~= "" then
+ if game.author ~= "" and game.release > 0 then
game_hash[game.author:lower() .. "/" .. game.id] = game
end
end
local txp_hash = {}
for _, txp in pairs(pkgmgr.get_texture_packs()) do
- if txp.author then
+ if txp.author and txp.release > 0 then
txp_hash[txp.author:lower() .. "/" .. txp.name] = txp
end
end
@@ -268,6 +622,33 @@ function store.update_paths()
end
end
+function store.sort_packages()
+ local ret = {}
+
+ -- Add installed content
+ for i=1, #store.packages_full_unordered do
+ local package = store.packages_full_unordered[i]
+ if package.path then
+ ret[#ret + 1] = package
+ end
+ end
+
+ -- Sort installed content by title
+ table.sort(ret, function(a, b)
+ return a.title < b.title
+ end)
+
+ -- Add uninstalled content
+ for i=1, #store.packages_full_unordered do
+ local package = store.packages_full_unordered[i]
+ if not package.path then
+ ret[#ret + 1] = package
+ end
+ end
+
+ store.packages_full = ret
+end
+
function store.filter_packages(query)
if query == "" and filter_type == 1 then
store.packages = store.packages_full
@@ -279,7 +660,7 @@ function store.filter_packages(query)
table.insert(keywords, word)
end
- local function matches_keywords(package, keywords)
+ local function matches_keywords(package)
for k = 1, #keywords do
local keyword = keywords[k]
@@ -296,12 +677,11 @@ function store.filter_packages(query)
store.packages = {}
for _, package in pairs(store.packages_full) do
- if (query == "" or matches_keywords(package, keywords)) and
+ if (query == "" or matches_keywords(package)) 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(dlgdata)
@@ -314,18 +694,21 @@ function store.get_formspec(dlgdata)
local W = 15.75
local H = 9.5
-
local formspec
if #store.packages_full > 0 then
formspec = {
"formspec_version[3]",
"size[15.75,9.5]",
"position[0.5,0.55]",
+
+ "style[status,downloading,queued;border=false]",
+
"container[0.375,0.375]",
- "field[0,0;10.225,0.8;search_string;;", core.formspec_escape(search_string), "]",
+ "field[0,0;7.225,0.8;search_string;;", core.formspec_escape(search_string), "]",
"field_close_on_enter[search_string;false]",
- "button[10.225,0;2,0.8;search;", fgettext("Search"), "]",
- "dropdown[12.6,0;2.4,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]",
+ "image_button[7.3,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
+ "image_button[8.125,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]",
+ "dropdown[9.6,0;2.4,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]",
"container_end[]",
-- Page nav buttons
@@ -344,6 +727,35 @@ function store.get_formspec(dlgdata)
"container_end[]",
}
+ if number_downloading > 0 then
+ formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;downloading;"
+ if #download_queue > 0 then
+ formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued", number_downloading, #download_queue)
+ else
+ formspec[#formspec + 1] = fgettext("$1 downloading...", number_downloading)
+ end
+ formspec[#formspec + 1] = "]"
+ else
+ local num_avail_updates = 0
+ for i=1, #store.packages_full do
+ local package = store.packages_full[i]
+ if package.path and package.installed_release < package.release and
+ not (package.downloading or package.queued) then
+ num_avail_updates = num_avail_updates + 1
+ end
+ end
+
+ if num_avail_updates == 0 then
+ formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;status;"
+ formspec[#formspec + 1] = fgettext("No updates")
+ formspec[#formspec + 1] = "]"
+ else
+ formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;update_all;"
+ formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates)
+ formspec[#formspec + 1] = "]"
+ end
+ end
+
if #store.packages == 0 then
formspec[#formspec + 1] = "label[4,3;"
formspec[#formspec + 1] = fgettext("No results")
@@ -360,11 +772,17 @@ function store.get_formspec(dlgdata)
}
end
+ -- download/queued tooltips always have the same message
+ local tooltip_colors = ";#dff6f5;#302c2e]"
+ formspec[#formspec + 1] = "tooltip[downloading;" .. fgettext("Downloading...") .. tooltip_colors
+ formspec[#formspec + 1] = "tooltip[queued;" .. fgettext("Queued") .. tooltip_colors
+
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]
+ local container_y = (i - start_idx) * 1.375 + (2*0.375 + 0.8)
formspec[#formspec + 1] = "container[0.375,"
- formspec[#formspec + 1] = (i - start_idx) * 1.375 + (2*0.375 + 0.8)
+ formspec[#formspec + 1] = container_y
formspec[#formspec + 1] = "]"
-- image
@@ -375,55 +793,55 @@ function store.get_formspec(dlgdata)
-- title
formspec[#formspec + 1] = "label[1.875,0.1;"
formspec[#formspec + 1] = core.formspec_escape(
- minetest.colorize(mt_color_green, package.title) ..
- minetest.colorize("#BFBFBF", " by " .. package.author))
+ core.colorize(mt_color_green, package.title) ..
+ core.colorize("#BFBFBF", " by " .. package.author))
formspec[#formspec + 1] = "]"
-- buttons
- local description_width = W - 0.375*5 - 1 - 2*1.5
+ local left_base = "image_button[-1.55,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "container["
formspec[#formspec + 1] = W - 0.375*2
formspec[#formspec + 1] = ",0.1]"
if package.downloading then
- formspec[#formspec + 1] = "style[download;border=false]"
-
- formspec[#formspec + 1] = "button[-3.5,0;2,0.8;download;"
- formspec[#formspec + 1] = fgettext("Downloading...")
- formspec[#formspec + 1] = "]"
+ formspec[#formspec + 1] = "animated_image[-1.7,-0.15;1,1;downloading;"
+ formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
+ formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
+ elseif package.queued then
+ formspec[#formspec + 1] = left_base
+ formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
+ formspec[#formspec + 1] = "cdb_queued.png;queued]"
elseif not package.path then
- formspec[#formspec + 1] = "button[-3,0;1.5,0.8;install_"
- formspec[#formspec + 1] = tostring(i)
- formspec[#formspec + 1] = ";"
- formspec[#formspec + 1] = fgettext("Install")
- formspec[#formspec + 1] = "]"
+ local elem_name = "install_" .. i .. ";"
+ formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]"
+ formspec[#formspec + 1] = left_base .. "cdb_add.png;" .. elem_name .. "]"
+ formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Install") .. tooltip_colors
else
if package.installed_release < package.release then
- description_width = description_width - 1.5
-- The install_ action also handles updating
- formspec[#formspec + 1] = "button[-4.5,0;1.5,0.8;install_"
- formspec[#formspec + 1] = tostring(i)
- formspec[#formspec + 1] = ";"
- formspec[#formspec + 1] = fgettext("Update")
- formspec[#formspec + 1] = "]"
- end
+ local elem_name = "install_" .. i .. ";"
+ formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#28ccdf]"
+ formspec[#formspec + 1] = left_base .. "cdb_update.png;" .. elem_name .. "]"
+ formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Update") .. tooltip_colors
+ else
- formspec[#formspec + 1] = "button[-3,0;1.5,0.8;uninstall_"
- formspec[#formspec + 1] = tostring(i)
- formspec[#formspec + 1] = ";"
- formspec[#formspec + 1] = fgettext("Uninstall")
- formspec[#formspec + 1] = "]"
+ local elem_name = "uninstall_" .. i .. ";"
+ formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#a93b3b]"
+ formspec[#formspec + 1] = left_base .. "cdb_clear.png;" .. elem_name .. "]"
+ formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Uninstall") .. tooltip_colors
+ end
end
- formspec[#formspec + 1] = "button[-1.5,0;1.5,0.8;view_"
- formspec[#formspec + 1] = tostring(i)
- formspec[#formspec + 1] = ";"
- formspec[#formspec + 1] = fgettext("View")
- formspec[#formspec + 1] = "]"
+ local web_elem_name = "view_" .. i .. ";"
+ formspec[#formspec + 1] = "image_button[-0.7,0;0.7,0.7;" ..
+ core.formspec_escape(defaulttexturedir) .. "cdb_viewonline.png;" .. web_elem_name .. "]"
+ formspec[#formspec + 1] = "tooltip[" .. web_elem_name ..
+ fgettext("View more information in a web browser") .. tooltip_colors
formspec[#formspec + 1] = "container_end[]"
-- description
+ local description_width = W - 0.375*5 - 0.85 - 2*0.7
formspec[#formspec + 1] = "textarea[1.855,0.3;"
formspec[#formspec + 1] = tostring(description_width)
formspec[#formspec + 1] = ",0.8;;;"
@@ -444,6 +862,13 @@ function store.handle_submit(this, fields)
return true
end
+ if fields.clear then
+ search_string = ""
+ cur_page = 1
+ store.filter_packages("")
+ return true
+ end
+
if fields.back then
this:delete()
return true
@@ -485,6 +910,17 @@ function store.handle_submit(this, fields)
end
end
+ if fields.update_all then
+ for i=1, #store.packages_full do
+ local package = store.packages_full[i]
+ if package.path and package.installed_release < package.release and
+ not (package.downloading or package.queued) then
+ queue_download(package)
+ end
+ end
+ return true
+ 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
@@ -492,21 +928,54 @@ function store.handle_submit(this, fields)
assert(package)
if fields["install_" .. i] then
- start_install(this, package)
+ local install_parent
+ if package.type == "mod" then
+ install_parent = core.get_modpath()
+ elseif package.type == "game" then
+ install_parent = core.get_gamepath()
+ elseif package.type == "txp" then
+ install_parent = core.get_texturepath()
+ else
+ error("Unknown package type: " .. package.type)
+ end
+
+
+ local function on_confirm()
+ local deps = get_raw_dependencies(package)
+ if deps and has_hard_deps(deps) then
+ local dlg = install_dialog.create(package, deps)
+ dlg:set_parent(this)
+ this:hide()
+ dlg:show()
+ else
+ queue_download(package)
+ end
+ end
+
+ if not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
+ local dlg = confirm_overwrite.create(package, on_confirm)
+ dlg:set_parent(this)
+ this:hide()
+ dlg:show()
+ else
+ on_confirm()
+ end
+
return true
end
if fields["uninstall_" .. i] then
- local dlg_delmod = create_delete_content_dlg(package)
- dlg_delmod:set_parent(this)
+ local dlg = create_delete_content_dlg(package)
+ dlg:set_parent(this)
this:hide()
- dlg_delmod:show()
+ dlg:show()
return true
end
if fields["view_" .. i] then
- local url = ("%s/packages/%s?protocol_version=%d"):format(
- core.settings:get("contentdb_url"), package.id, core.get_max_supp_proto())
+ local url = ("%s/packages/%s/%s?protocol_version=%d"):format(
+ core.settings:get("contentdb_url"),
+ package.author, package.name, core.get_max_supp_proto())
core.open_url(url)
return true
end
@@ -520,6 +989,9 @@ function create_store_dlg(type)
store.load()
end
+ store.update_paths()
+ store.sort_packages()
+
search_string = ""
cur_page = 1
diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua
index 36df23cce..1938747fe 100644
--- a/builtin/mainmenu/dlg_create_world.lua
+++ b/builtin/mainmenu/dlg_create_world.lua
@@ -61,6 +61,7 @@ local flag_checkboxes = {
fgettext("Low humidity and high heat causes shallow or dry rivers") },
},
flat = {
+ cb_caverns,
{ "hills", fgettext("Hills"), "hills" },
{ "lakes", fgettext("Lakes"), "lakes" },
},
@@ -97,7 +98,7 @@ local function create_world_formspec(dialogdata)
-- Error out when no games found
if #pkgmgr.games == 0 then
return "size[12.25,3,true]" ..
- "box[0,0;12,2;#ff8800]" ..
+ "box[0,0;12,2;" .. mt_color_orange .. "]" ..
"textarea[0.3,0;11.7,2;;;"..
fgettext("You have no games installed.") .. "\n" ..
fgettext("Download one from minetest.net") .. "]" ..
@@ -362,10 +363,18 @@ local function create_world_buttonhandler(this, fields)
local gameindex = core.get_textlist_index("games")
if gameindex ~= nil then
+ -- For unnamed worlds use the generated name 'world<number>',
+ -- where the number increments: it is set to 1 larger than the largest
+ -- generated name number found.
if worldname == "" then
- local random_number = math.random(10000, 99999)
- local random_world_name = "Unnamed" .. random_number
- worldname = random_world_name
+ local worldnum_max = 0
+ for _, world in ipairs(menudata.worldlist:get_list()) do
+ if world.name:match("^world%d+$") then
+ local worldnum = tonumber(world.name:sub(6))
+ worldnum_max = math.max(worldnum_max, worldnum)
+ end
+ end
+ worldname = "world" .. worldnum_max + 1
end
core.settings:set("fixed_map_seed", fields["te_seed"])
@@ -434,7 +443,7 @@ local function create_world_buttonhandler(this, fields)
end
if fields["mgv6_biomes"] then
- local entry = minetest.formspec_escape(fields["mgv6_biomes"])
+ local entry = core.formspec_escape(fields["mgv6_biomes"])
for b=1, #mgv6_biomes do
if entry == mgv6_biomes[b][1] then
local ftable = core.settings:get_flags("mgv6_spflags")
diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua
index c17e79270..45089c7c9 100644
--- a/builtin/mainmenu/init.lua
+++ b/builtin/mainmenu/init.lua
@@ -19,10 +19,10 @@ mt_color_grey = "#AAAAAA"
mt_color_blue = "#6389FF"
mt_color_green = "#72FF63"
mt_color_dark_green = "#25C191"
+mt_color_orange = "#FF8800"
local menupath = core.get_mainmenu_path()
local basepath = core.get_builtin_path()
-local menustyle = core.settings:get("main_menu_style")
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
DIR_DELIM .. "pack" .. DIR_DELIM
@@ -34,29 +34,24 @@ dofile(basepath .. "fstk" .. DIR_DELIM .. "ui.lua")
dofile(menupath .. DIR_DELIM .. "async_event.lua")
dofile(menupath .. DIR_DELIM .. "common.lua")
dofile(menupath .. DIR_DELIM .. "pkgmgr.lua")
+dofile(menupath .. DIR_DELIM .. "serverlistmgr.lua")
dofile(menupath .. DIR_DELIM .. "textures.lua")
dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua")
dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua")
dofile(menupath .. DIR_DELIM .. "dlg_contentstore.lua")
-if menustyle ~= "simple" then
- dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua")
- dofile(menupath .. DIR_DELIM .. "dlg_delete_content.lua")
- dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua")
- dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua")
-end
+dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua")
+dofile(menupath .. DIR_DELIM .. "dlg_delete_content.lua")
+dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua")
+dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua")
local tabs = {}
tabs.settings = dofile(menupath .. DIR_DELIM .. "tab_settings.lua")
tabs.content = dofile(menupath .. DIR_DELIM .. "tab_content.lua")
tabs.credits = dofile(menupath .. DIR_DELIM .. "tab_credits.lua")
-if menustyle == "simple" then
- tabs.simple_main = dofile(menupath .. DIR_DELIM .. "tab_simple_main.lua")
-else
- tabs.local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua")
- tabs.play_online = dofile(menupath .. DIR_DELIM .. "tab_online.lua")
-end
+tabs.local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua")
+tabs.play_online = dofile(menupath .. DIR_DELIM .. "tab_online.lua")
--------------------------------------------------------------------------------
local function main_event_handler(tabview, event)
@@ -71,68 +66,35 @@ local function init_globals()
-- Init gamedata
gamedata.worldindex = 0
- if menustyle == "simple" then
- local world_list = core.get_worlds()
- local world_index
-
- local found_singleplayerworld = false
- for i, world in ipairs(world_list) do
- if world.name == "singleplayerworld" then
- found_singleplayerworld = true
- world_index = i
- break
- end
- end
-
- if not found_singleplayerworld then
- core.create_world("singleplayerworld", 1)
-
- world_list = core.get_worlds()
-
- for i, world in ipairs(world_list) do
- if world.name == "singleplayerworld" then
- world_index = i
- break
- end
- end
+ menudata.worldlist = filterlist.create(
+ core.get_worlds,
+ compare_worlds,
+ -- Unique id comparison function
+ function(element, uid)
+ return element.name == uid
+ end,
+ -- Filter function
+ function(element, gameid)
+ return element.gameid == gameid
end
+ )
- gamedata.worldindex = world_index
- else
- menudata.worldlist = filterlist.create(
- core.get_worlds,
- compare_worlds,
- -- Unique id comparison function
- function(element, uid)
- return element.name == uid
- end,
- -- Filter function
- function(element, gameid)
- return element.gameid == gameid
- end
- )
-
- menudata.worldlist:add_sort_mechanism("alphabetic", sort_worlds_alphabetic)
- menudata.worldlist:set_sortmode("alphabetic")
-
- if not core.settings:get("menu_last_game") then
- local default_game = core.settings:get("default_game") or "minetest"
- core.settings:set("menu_last_game", default_game)
- end
+ menudata.worldlist:add_sort_mechanism("alphabetic", sort_worlds_alphabetic)
+ menudata.worldlist:set_sortmode("alphabetic")
- mm_texture.init()
+ if not core.settings:get("menu_last_game") then
+ local default_game = core.settings:get("default_game") or "minetest"
+ core.settings:set("menu_last_game", default_game)
end
+ mm_texture.init()
+
-- Create main tabview
local tv_main = tabview_create("maintab", {x = 12, y = 5.4}, {x = 0, y = 0})
- if menustyle == "simple" then
- tv_main:add(tabs.simple_main)
- else
- tv_main:set_autosave_tab(true)
- tv_main:add(tabs.local_game)
- tv_main:add(tabs.play_online)
- end
+ tv_main:set_autosave_tab(true)
+ tv_main:add(tabs.local_game)
+ tv_main:add(tabs.play_online)
tv_main:add(tabs.content)
tv_main:add(tabs.settings)
@@ -141,12 +103,20 @@ local function init_globals()
tv_main:set_global_event_handler(main_event_handler)
tv_main:set_fixed_size(false)
- if menustyle ~= "simple" then
- local last_tab = core.settings:get("maintab_LAST")
- if last_tab and tv_main.current_tab ~= last_tab then
- tv_main:set_tab(last_tab)
+ local last_tab = core.settings:get("maintab_LAST")
+ if last_tab and tv_main.current_tab ~= last_tab then
+ tv_main:set_tab(last_tab)
+ end
+
+ -- In case the folder of the last selected game has been deleted,
+ -- display "Minetest" as a header
+ if tv_main.current_tab == "local" then
+ local game = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
+ if game == nil then
+ mm_texture.reset()
end
end
+
ui.set_default("maintab")
tv_main:show()
diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua
index 5b8807310..19127d8d3 100644
--- a/builtin/mainmenu/pkgmgr.lua
+++ b/builtin/mainmenu/pkgmgr.lua
@@ -72,6 +72,34 @@ local function cleanup_path(temppath)
return temppath
end
+local function load_texture_packs(txtpath, retval)
+ local list = core.get_dir_list(txtpath, true)
+ local current_texture_path = core.settings:get("texture_path")
+
+ for _, item in ipairs(list) do
+ 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"),
+ release = tonumber(conf:get("release") or "0"),
+ list_name = name,
+ type = "txp",
+ path = path,
+ enabled = path == current_texture_path,
+ }
+ end
+ end
+end
+
function get_mods(path,retval,modpack)
local mods = core.get_dir_list(path, true)
@@ -112,7 +140,7 @@ function get_mods(path,retval,modpack)
toadd.type = "mod"
-- Check modpack.txt
- -- Note: modpack.conf is already checked above
+ -- Note: modpack.conf is already checked above
local modpackfile = io.open(prefix .. DIR_DELIM .. "modpack.txt")
if modpackfile then
modpackfile:close()
@@ -136,32 +164,13 @@ pkgmgr = {}
function pkgmgr.get_texture_packs()
local txtpath = core.get_texturepath()
- local list = core.get_dir_list(txtpath, true)
+ local txtpath_system = core.get_texturepath_share()
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"),
- release = tonumber(conf:get("release") or "0"),
- list_name = name,
- type = "txp",
- path = path,
- enabled = path == current_texture_path,
- }
- end
+ load_texture_packs(txtpath, retval)
+ -- on portable versions these two paths coincide. It avoids loading the path twice
+ if txtpath ~= txtpath_system then
+ load_texture_packs(txtpath_system, retval)
end
table.sort(retval, function(a, b)
@@ -450,7 +459,7 @@ function pkgmgr.enable_mod(this, toset)
if not toset then
-- Mod(s) were disabled, so no dependencies need to be enabled
table.sort(toggled_mods)
- minetest.log("info", "Following mods were disabled: " ..
+ core.log("info", "Following mods were disabled: " ..
table.concat(toggled_mods, ", "))
return
end
@@ -487,7 +496,7 @@ function pkgmgr.enable_mod(this, toset)
enabled_mods[name] = true
local mod_to_enable = list[mod_ids[name]]
if not mod_to_enable then
- minetest.log("warning", "Mod dependency \"" .. name ..
+ core.log("warning", "Mod dependency \"" .. name ..
"\" not found!")
else
if mod_to_enable.enabled == false then
@@ -508,7 +517,7 @@ function pkgmgr.enable_mod(this, toset)
-- Log the list of enabled mods
table.sort(toggled_mods)
- minetest.log("info", "Following mods were enabled: " ..
+ core.log("info", "Following mods were enabled: " ..
table.concat(toggled_mods, ", "))
end
diff --git a/builtin/mainmenu/serverlistmgr.lua b/builtin/mainmenu/serverlistmgr.lua
new file mode 100644
index 000000000..9876d8ac5
--- /dev/null
+++ b/builtin/mainmenu/serverlistmgr.lua
@@ -0,0 +1,250 @@
+--Minetest
+--Copyright (C) 2020 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.
+
+serverlistmgr = {}
+
+--------------------------------------------------------------------------------
+local function order_server_list(list)
+ local res = {}
+ --orders the favorite list after support
+ for i = 1, #list do
+ local fav = list[i]
+ if is_server_protocol_compat(fav.proto_min, fav.proto_max) then
+ res[#res + 1] = fav
+ end
+ end
+ for i = 1, #list do
+ local fav = list[i]
+ if not is_server_protocol_compat(fav.proto_min, fav.proto_max) then
+ res[#res + 1] = fav
+ end
+ end
+ return res
+end
+
+local public_downloading = false
+
+--------------------------------------------------------------------------------
+function serverlistmgr.sync()
+ if not serverlistmgr.servers then
+ serverlistmgr.servers = {{
+ name = fgettext("Loading..."),
+ description = fgettext_ne("Try reenabling public serverlist and check your internet connection.")
+ }}
+ end
+
+ local serverlist_url = core.settings:get("serverlist_url") or ""
+ if not core.get_http_api or serverlist_url == "" then
+ serverlistmgr.servers = {{
+ name = fgettext("Public server list is disabled"),
+ description = ""
+ }}
+ return
+ end
+
+ if public_downloading then
+ return
+ end
+ public_downloading = true
+
+ core.handle_async(
+ function(param)
+ local http = core.get_http_api()
+ local url = ("%s/list?proto_version_min=%d&proto_version_max=%d"):format(
+ core.settings:get("serverlist_url"),
+ core.get_min_supp_proto(),
+ core.get_max_supp_proto())
+
+ local response = http.fetch_sync({ url = url })
+ if not response.succeeded then
+ return {}
+ end
+
+ local retval = core.parse_json(response.data)
+ return retval and retval.list or {}
+ end,
+ nil,
+ function(result)
+ public_downloading = nil
+ local favs = order_server_list(result)
+ if favs[1] then
+ serverlistmgr.servers = favs
+ end
+ core.event_handler("Refresh")
+ end
+ )
+end
+
+--------------------------------------------------------------------------------
+local function get_favorites_path()
+ local base = core.get_user_path() .. DIR_DELIM .. "client" .. DIR_DELIM .. "serverlist" .. DIR_DELIM
+ return base .. core.settings:get("serverlist_file")
+end
+
+--------------------------------------------------------------------------------
+local function save_favorites(favorites)
+ local filename = core.settings:get("serverlist_file")
+ -- If setting specifies legacy format change the filename to the new one
+ if filename:sub(#filename - 3):lower() == ".txt" then
+ core.settings:set("serverlist_file", filename:sub(1, #filename - 4) .. ".json")
+ end
+
+ local path = get_favorites_path()
+ core.create_dir(path)
+ core.safe_file_write(path, core.write_json(favorites))
+end
+
+--------------------------------------------------------------------------------
+function serverlistmgr.read_legacy_favorites(path)
+ local file = io.open(path, "r")
+ if not file then
+ return nil
+ end
+
+ local lines = {}
+ for line in file:lines() do
+ lines[#lines + 1] = line
+ end
+ file:close()
+
+ local favorites = {}
+
+ local i = 1
+ while i < #lines do
+ local function pop()
+ local line = lines[i]
+ i = i + 1
+ return line and line:trim()
+ end
+
+ if pop():lower() == "[server]" then
+ local name = pop()
+ local address = pop()
+ local port = tonumber(pop())
+ local description = pop()
+
+ if name == "" then
+ name = nil
+ end
+
+ if description == "" then
+ description = nil
+ end
+
+ if not address or #address < 3 then
+ core.log("warning", "Malformed favorites file, missing address at line " .. i)
+ elseif not port or port < 1 or port > 65535 then
+ core.log("warning", "Malformed favorites file, missing port at line " .. i)
+ elseif (name and name:upper() == "[SERVER]") or
+ (address and address:upper() == "[SERVER]") or
+ (description and description:upper() == "[SERVER]") then
+ core.log("warning", "Potentially malformed favorites file, overran at line " .. i)
+ else
+ favorites[#favorites + 1] = {
+ name = name,
+ address = address,
+ port = port,
+ description = description
+ }
+ end
+ end
+ end
+
+ return favorites
+end
+
+--------------------------------------------------------------------------------
+local function read_favorites()
+ local path = get_favorites_path()
+
+ -- If new format configured fall back to reading the legacy file
+ if path:sub(#path - 4):lower() == ".json" then
+ local file = io.open(path, "r")
+ if file then
+ local json = file:read("*all")
+ file:close()
+ return core.parse_json(json)
+ end
+
+ path = path:sub(1, #path - 5) .. ".txt"
+ end
+
+ local favs = serverlistmgr.read_legacy_favorites(path)
+ if favs then
+ save_favorites(favs)
+ os.remove(path)
+ end
+ return favs
+end
+
+--------------------------------------------------------------------------------
+local function delete_favorite(favorites, del_favorite)
+ for i=1, #favorites do
+ local fav = favorites[i]
+
+ if fav.address == del_favorite.address and fav.port == del_favorite.port then
+ table.remove(favorites, i)
+ return
+ end
+ end
+end
+
+--------------------------------------------------------------------------------
+function serverlistmgr.get_favorites()
+ if serverlistmgr.favorites then
+ return serverlistmgr.favorites
+ end
+
+ serverlistmgr.favorites = {}
+
+ -- Add favorites, removing duplicates
+ local seen = {}
+ for _, fav in ipairs(read_favorites() or {}) do
+ local key = ("%s:%d"):format(fav.address:lower(), fav.port)
+ if not seen[key] then
+ seen[key] = true
+ serverlistmgr.favorites[#serverlistmgr.favorites + 1] = fav
+ end
+ end
+
+ return serverlistmgr.favorites
+end
+
+--------------------------------------------------------------------------------
+function serverlistmgr.add_favorite(new_favorite)
+ assert(type(new_favorite.port) == "number")
+
+ -- Whitelist favorite keys
+ new_favorite = {
+ name = new_favorite.name,
+ address = new_favorite.address,
+ port = new_favorite.port,
+ description = new_favorite.description,
+ }
+
+ local favorites = serverlistmgr.get_favorites()
+ delete_favorite(favorites, new_favorite)
+ table.insert(favorites, 1, new_favorite)
+ save_favorites(favorites)
+end
+
+--------------------------------------------------------------------------------
+function serverlistmgr.delete_favorite(del_favorite)
+ local favorites = serverlistmgr.get_favorites()
+ delete_favorite(favorites, del_favorite)
+ save_favorites(favorites)
+end
diff --git a/builtin/mainmenu/tab_credits.lua b/builtin/mainmenu/tab_credits.lua
index c2b7e503a..a34dd58bb 100644
--- a/builtin/mainmenu/tab_credits.lua
+++ b/builtin/mainmenu/tab_credits.lua
@@ -23,28 +23,37 @@ local core_developers = {
"Nathanaël Courant (Nore/Ekdohibs) <nore@mesecons.net>",
"Loic Blot (nerzhul/nrz) <loic.blot@unix-experience.fr>",
"paramat",
- "Auke Kok (sofar) <sofar@foo-projects.org>",
"Andrew Ward (rubenwardy) <rw@rubenwardy.com>",
"Krock/SmallJoker <mk939@ymail.com>",
"Lars Hofhansl <larsh@apache.org>",
+ "Pierre-Yves Rollo <dev@pyrollo.com>",
+ "v-rob <robinsonvincent89@gmail.com>",
}
+-- For updating active/previous contributors, see the script in ./util/gather_git_credits.py
+
local active_contributors = {
- "Hugues Ross [Formspecs]",
+ "Wuzzy [devtest game, visual corrections]",
+ "Zughy [Visual improvements, various fixes]",
"Maksim (MoNTE48) [Android]",
- "DS [Formspecs]",
- "pyrollo [Formspecs: Hypertext]",
- "v-rob [Formspecs]",
- "Jordach [set_sky]",
- "random-geek [Formspecs]",
- "Wuzzy [Pathfinder, builtin, translations]",
- "ANAND (ClobberXD) [Fixes, per-player FOV]",
- "Warr1024 [Fixes]",
- "Paul Ouellette (pauloue) [Fixes, Script API]",
- "Jean-Patrick G (kilbith) <jeanpatrick.guerrero@gmail.com> [Audiovisuals]",
- "HybridDog [Script API]",
+ "numzero [Graphics and rendering]",
+ "appgurueu [Various internal fixes]",
+ "Desour [Formspec and vector API changes]",
+ "HybridDog [Rendering fixes and documentation]",
+ "Hugues Ross [Graphics-related improvements]",
+ "ANAND (ClobberXD) [Mouse buttons rebinding]",
+ "luk3yx [Fixes]",
+ "hecks [Audiovisuals, Lua API]",
+ "LoneWolfHT [Object crosshair, documentation fixes]",
+ "Lejo [Server-related improvements]",
+ "EvidenceB [Compass HUD element]",
+ "Paul Ouellette (pauloue) [Lua API, documentation]",
+ "TheTermos [Collision detection, physics]",
+ "David CARLIER [Unix & Haiku build fixes]",
"dcbrwn [Object shading]",
- "srifqi [Fixes]",
+ "Elias Fleckenstein [API features/fixes]",
+ "Jean-Patrick Guerrero (kilbith) [model element, visual fixes]",
+ "k.h.lai [Memory leak fixes, documentation]",
}
local previous_core_developers = {
@@ -60,30 +69,23 @@ local previous_core_developers = {
"sapier",
"Zeno",
"ShadowNinja <shadowninja@minetest.net>",
+ "Auke Kok (sofar) <sofar@foo-projects.org>",
}
local previous_contributors = {
"Nils Dagsson Moskopp (erlehmann) <nils@dieweltistgarnichtso.net> [Minetest Logo]",
- "Dániel Juhász (juhdanad) <juhdanad@gmail.com>",
"red-001 <red-001@outlook.ie>",
- "numberZero [Audiovisuals: meshgen]",
"Giuseppe Bilotta",
+ "Dániel Juhász (juhdanad) <juhdanad@gmail.com>",
"MirceaKitsune <mirceakitsune@gmail.com>",
"Constantin Wenger (SpeedProg)",
"Ciaran Gultnieks (CiaranG)",
"stujones11 [Android UX improvements]",
- "Jeija <jeija@mesecons.net> [HTTP, particles]",
- "Vincent Glize (Dumbeldor) [Cleanups, CSM APIs]",
- "Ben Deutsch [Rendering, Fixes, SQLite auth]",
- "TeTpaAka [Hand overriding, nametag colors]",
- "Rui [Sound Pitch]",
- "Duane Robertson <duane@duanerobertson.com> [MGValleys]",
- "Raymoo [Tool Capabilities]",
"Rogier <rogier777@gmail.com> [Fixes]",
"Gregory Currie (gregorycu) [optimisation]",
- "TriBlade9 <triblade9@mail.com> [Audiovisuals]",
- "T4im [Profiler]",
- "Jurgen Doser (doserj) <jurgen.doser@gmail.com>",
+ "srifqi [Fixes]",
+ "JacobF",
+ "Jeija <jeija@mesecons.net> [HTTP, particles]",
}
local function buildCreditList(source)
@@ -100,9 +102,10 @@ return {
cbf_formspec = function(tabview, name, tabdata)
local logofile = defaulttexturedir .. "logo.png"
local version = core.get_version()
- return "image[0.5,1;" .. core.formspec_escape(logofile) .. "]" ..
- "label[0.5,2.8;" .. version.project .. " " .. version.string .. "]" ..
- "button[0.5,3;2,2;homepage;minetest.net]" ..
+ local fs = "image[0.75,0.5;2.2,2.2;" .. core.formspec_escape(logofile) .. "]" ..
+ "style[label_button;border=false]" ..
+ "button[0.5,2;2.5,2;label_button;" .. version.project .. " " .. version.string .. "]" ..
+ "button[0.75,2.75;2,2;homepage;minetest.net]" ..
"tablecolumns[color;text]" ..
"tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
"table[3.5,-0.25;8.5,6.05;list_credits;" ..
@@ -115,10 +118,23 @@ return {
"#FFFF00," .. fgettext("Previous Contributors") .. ",," ..
buildCreditList(previous_contributors) .. "," ..
";1]"
+
+ if PLATFORM ~= "Android" then
+ fs = fs .. "tooltip[userdata;" ..
+ fgettext("Opens the directory that contains user-provided worlds, games, mods,\n" ..
+ "and texture packs in a file manager / explorer.") .. "]"
+ fs = fs .. "button[0,4.75;3.5,1;userdata;" .. fgettext("Open User Data Directory") .. "]"
+ end
+
+ return fs
end,
cbf_button_handler = function(this, fields, name, tabdata)
if fields.homepage then
core.open_url("https://www.minetest.net")
end
+
+ if fields.userdata then
+ core.open_dir(core.get_user_path())
+ end
end,
}
diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua
index a21cf12b1..0e06c3bef 100644
--- a/builtin/mainmenu/tab_local.lua
+++ b/builtin/mainmenu/tab_local.lua
@@ -18,6 +18,7 @@
local enable_gamebar = PLATFORM ~= "Android"
local current_game, singleplayer_refresh_gamebar
+
if enable_gamebar then
function current_game()
local last_game_id = core.settings:get("menu_last_game")
@@ -114,45 +115,44 @@ local function get_formspec(tabview, name, tabdata)
)
retval = retval ..
- "button[4,3.95;2.6,1;world_delete;".. fgettext("Delete") .. "]" ..
- "button[6.5,3.95;2.8,1;world_configure;".. fgettext("Configure") .. "]" ..
- "button[9.2,3.95;2.5,1;world_create;".. fgettext("New") .. "]" ..
- "label[4,-0.25;".. fgettext("Select World:") .. "]"..
- "checkbox[0.25,0.25;cb_creative_mode;".. fgettext("Creative Mode") .. ";" ..
+ "button[3.9,3.8;2.8,1;world_delete;".. fgettext("Delete") .. "]" ..
+ "button[6.55,3.8;2.8,1;world_configure;".. fgettext("Select Mods") .. "]" ..
+ "button[9.2,3.8;2.8,1;world_create;".. fgettext("New") .. "]" ..
+ "label[3.9,-0.05;".. fgettext("Select World:") .. "]"..
+ "checkbox[0,-0.20;cb_creative_mode;".. fgettext("Creative Mode") .. ";" ..
dump(core.settings:get_bool("creative_mode")) .. "]"..
- "checkbox[0.25,0.7;cb_enable_damage;".. fgettext("Enable Damage") .. ";" ..
+ "checkbox[0,0.25;cb_enable_damage;".. fgettext("Enable Damage") .. ";" ..
dump(core.settings:get_bool("enable_damage")) .. "]"..
- "checkbox[0.25,1.15;cb_server;".. fgettext("Host Server") ..";" ..
+ "checkbox[0,0.7;cb_server;".. fgettext("Host Server") ..";" ..
dump(core.settings:get_bool("enable_server")) .. "]" ..
- "textlist[4,0.25;7.5,3.7;sp_worlds;" ..
+ "textlist[3.9,0.4;7.9,3.45;sp_worlds;" ..
menu_render_worldlist() ..
";" .. index .. "]"
if core.settings:get_bool("enable_server") then
retval = retval ..
- "button[8.5,4.8;3.2,1;play;".. fgettext("Host Game") .. "]" ..
- "checkbox[0.25,1.6;cb_server_announce;" .. fgettext("Announce Server") .. ";" ..
+ "button[7.9,4.75;4.1,1;play;".. fgettext("Host Game") .. "]" ..
+ "checkbox[0,1.15;cb_server_announce;" .. fgettext("Announce Server") .. ";" ..
dump(core.settings:get_bool("server_announce")) .. "]" ..
- "label[0.25,2.2;" .. fgettext("Name/Password") .. "]" ..
- "field[0.55,3.2;3.5,0.5;te_playername;;" ..
+ "field[0.3,2.85;3.8,0.5;te_playername;" .. fgettext("Name") .. ";" ..
core.formspec_escape(core.settings:get("name")) .. "]" ..
- "pwdfield[0.55,4;3.5,0.5;te_passwd;]"
+ "pwdfield[0.3,4.05;3.8,0.5;te_passwd;" .. fgettext("Password") .. "]"
local bind_addr = core.settings:get("bind_address")
if bind_addr ~= nil and bind_addr ~= "" then
retval = retval ..
- "field[0.55,5.2;2.25,0.5;te_serveraddr;" .. fgettext("Bind Address") .. ";" ..
+ "field[0.3,5.25;2.5,0.5;te_serveraddr;" .. fgettext("Bind Address") .. ";" ..
core.formspec_escape(core.settings:get("bind_address")) .. "]" ..
- "field[2.8,5.2;1.25,0.5;te_serverport;" .. fgettext("Port") .. ";" ..
+ "field[2.85,5.25;1.25,0.5;te_serverport;" .. fgettext("Port") .. ";" ..
core.formspec_escape(core.settings:get("port")) .. "]"
else
retval = retval ..
- "field[0.55,5.2;3.5,0.5;te_serverport;" .. fgettext("Server Port") .. ";" ..
+ "field[0.3,5.25;3.8,0.5;te_serverport;" .. fgettext("Server Port") .. ";" ..
core.formspec_escape(core.settings:get("port")) .. "]"
end
else
retval = retval ..
- "button[8.5,4.8;3.2,1;play;".. fgettext("Play Game") .. "]"
+ "button[7.9,4.75;4.1,1;play;" .. fgettext("Play Game") .. "]"
end
return retval
diff --git a/builtin/mainmenu/tab_online.lua b/builtin/mainmenu/tab_online.lua
index 7985fd84a..e6748ed88 100644
--- a/builtin/mainmenu/tab_online.lua
+++ b/builtin/mainmenu/tab_online.lua
@@ -20,11 +20,11 @@ local function get_formspec(tabview, name, tabdata)
-- Update the cached supported proto info,
-- it may have changed after a change by the settings menu.
common_update_cached_supp_proto()
- local fav_selected
+ local selected
if menudata.search_result then
- fav_selected = menudata.search_result[tabdata.fav_selected]
+ selected = menudata.search_result[tabdata.selected]
else
- fav_selected = menudata.favorites[tabdata.fav_selected]
+ selected = serverlistmgr.servers[tabdata.selected]
end
if not tabdata.search_for then
@@ -34,7 +34,8 @@ local function get_formspec(tabview, name, tabdata)
local retval =
-- Search
"field[0.15,0.075;5.91,1;te_search;;" .. core.formspec_escape(tabdata.search_for) .. "]" ..
- "button[5.62,-0.25;1.5,1;btn_mp_search;" .. fgettext("Search") .. "]" ..
+ "image_button[5.63,-.165;.83,.83;" .. core.formspec_escape(defaulttexturedir .. "search.png") .. ";btn_mp_search;]" ..
+ "image_button[6.3,-.165;.83,.83;" .. core.formspec_escape(defaulttexturedir .. "clear.png") .. ";btn_mp_clear;]" ..
"image_button[6.97,-.165;.83,.83;" .. core.formspec_escape(defaulttexturedir .. "refresh.png")
.. ";btn_mp_refresh;]" ..
@@ -57,18 +58,18 @@ local function get_formspec(tabview, name, tabdata)
-- Connect
"button[9.88,4.9;2.3,1;btn_mp_connect;" .. fgettext("Connect") .. "]"
- if tabdata.fav_selected and fav_selected then
+ if tabdata.selected and selected then
if gamedata.fav then
retval = retval .. "button[7.73,4.9;2.3,1;btn_delete_favorite;" ..
fgettext("Del. Favorite") .. "]"
end
- if fav_selected.description then
+ if selected.description then
retval = retval .. "textarea[8.1,2.3;4.23,2.9;;;" ..
core.formspec_escape((gamedata.serverdescription or ""), true) .. "]"
end
end
- --favourites
+ --favorites
retval = retval .. "tablecolumns[" ..
image_column(fgettext("Favorite"), "favorite") .. ";" ..
image_column(fgettext("Ping")) .. ",padding=0.25;" ..
@@ -82,13 +83,12 @@ local function get_formspec(tabview, name, tabdata)
image_column(fgettext("PvP enabled"), "pvp") .. ",padding=0.25;" ..
"color,span=1;" ..
"text,padding=1]" ..
- "table[-0.15,0.6;7.75,5.15;favourites;"
+ "table[-0.15,0.6;7.75,5.15;favorites;"
if menudata.search_result then
+ local favs = serverlistmgr.get_favorites()
for i = 1, #menudata.search_result do
- local favs = core.get_favorites("local")
local server = menudata.search_result[i]
-
for fav_id = 1, #favs do
if server.address == favs[fav_id].address and
server.port == favs[fav_id].port then
@@ -102,29 +102,30 @@ local function get_formspec(tabview, name, tabdata)
retval = retval .. render_serverlist_row(server, server.is_favorite)
end
- elseif #menudata.favorites > 0 then
- local favs = core.get_favorites("local")
+ elseif #serverlistmgr.servers > 0 then
+ local favs = serverlistmgr.get_favorites()
if #favs > 0 then
for i = 1, #favs do
- for j = 1, #menudata.favorites do
- if menudata.favorites[j].address == favs[i].address and
- menudata.favorites[j].port == favs[i].port then
- table.insert(menudata.favorites, i, table.remove(menudata.favorites, j))
+ for j = 1, #serverlistmgr.servers do
+ if serverlistmgr.servers[j].address == favs[i].address and
+ serverlistmgr.servers[j].port == favs[i].port then
+ table.insert(serverlistmgr.servers, i, table.remove(serverlistmgr.servers, j))
+ end
end
- end
- if favs[i].address ~= menudata.favorites[i].address then
- table.insert(menudata.favorites, i, favs[i])
+ if favs[i].address ~= serverlistmgr.servers[i].address then
+ table.insert(serverlistmgr.servers, i, favs[i])
end
end
end
- retval = retval .. render_serverlist_row(menudata.favorites[1], (#favs > 0))
- for i = 2, #menudata.favorites do
- retval = retval .. "," .. render_serverlist_row(menudata.favorites[i], (i <= #favs))
+
+ retval = retval .. render_serverlist_row(serverlistmgr.servers[1], (#favs > 0))
+ for i = 2, #serverlistmgr.servers do
+ retval = retval .. "," .. render_serverlist_row(serverlistmgr.servers[i], (i <= #favs))
end
end
- if tabdata.fav_selected then
- retval = retval .. ";" .. tabdata.fav_selected .. "]"
+ if tabdata.selected then
+ retval = retval .. ";" .. tabdata.selected .. "]"
else
retval = retval .. ";0]"
end
@@ -134,21 +135,20 @@ end
--------------------------------------------------------------------------------
local function main_button_handler(tabview, fields, name, tabdata)
- local serverlist = menudata.search_result or menudata.favorites
+ local serverlist = menudata.search_result or serverlistmgr.servers
if fields.te_name then
gamedata.playername = fields.te_name
core.settings:set("name", fields.te_name)
end
- if fields.favourites then
- local event = core.explode_table_event(fields.favourites)
+ if fields.favorites then
+ local event = core.explode_table_event(fields.favorites)
local fav = serverlist[event.row]
if event.type == "DCL" then
if event.row <= #serverlist then
- if menudata.favorites_is_public and
- not is_server_protocol_compat_or_error(
+ if not is_server_protocol_compat_or_error(
fav.proto_min, fav.proto_max) then
return true
end
@@ -177,7 +177,7 @@ local function main_button_handler(tabview, fields, name, tabdata)
if event.type == "CHG" then
if event.row <= #serverlist then
gamedata.fav = false
- local favs = core.get_favorites("local")
+ local favs = serverlistmgr.get_favorites()
local address = fav.address
local port = fav.port
gamedata.serverdescription = fav.description
@@ -193,28 +193,28 @@ local function main_button_handler(tabview, fields, name, tabdata)
core.settings:set("address", address)
core.settings:set("remote_port", port)
end
- tabdata.fav_selected = event.row
+ tabdata.selected = event.row
end
return true
end
end
if fields.key_up or fields.key_down then
- local fav_idx = core.get_table_index("favourites")
+ local fav_idx = core.get_table_index("favorites")
local fav = serverlist[fav_idx]
if fav_idx then
if fields.key_up and fav_idx > 1 then
fav_idx = fav_idx - 1
- elseif fields.key_down and fav_idx < #menudata.favorites then
+ elseif fields.key_down and fav_idx < #serverlistmgr.servers then
fav_idx = fav_idx + 1
end
else
fav_idx = 1
end
- if not menudata.favorites or not fav then
- tabdata.fav_selected = 0
+ if not serverlistmgr.servers or not fav then
+ tabdata.selected = 0
return true
end
@@ -226,29 +226,35 @@ local function main_button_handler(tabview, fields, name, tabdata)
core.settings:set("remote_port", port)
end
- tabdata.fav_selected = fav_idx
+ tabdata.selected = fav_idx
return true
end
if fields.btn_delete_favorite then
- local current_favourite = core.get_table_index("favourites")
- if not current_favourite then return end
+ local current_favorite = core.get_table_index("favorites")
+ if not current_favorite then return end
- core.delete_favorite(current_favourite)
- asyncOnlineFavourites()
- tabdata.fav_selected = nil
+ serverlistmgr.delete_favorite(serverlistmgr.servers[current_favorite])
+ serverlistmgr.sync()
+ tabdata.selected = nil
core.settings:set("address", "")
core.settings:set("remote_port", "30000")
return true
end
+ if fields.btn_mp_clear then
+ tabdata.search_for = ""
+ menudata.search_result = nil
+ return true
+ end
+
if fields.btn_mp_search or fields.key_enter_field == "te_search" then
- tabdata.fav_selected = 1
+ tabdata.selected = 1
local input = fields.te_search:lower()
tabdata.search_for = fields.te_search
- if #menudata.favorites < 2 then
+ if #serverlistmgr.servers < 2 then
return true
end
@@ -268,8 +274,8 @@ local function main_button_handler(tabview, fields, name, tabdata)
-- Search the serverlist
local search_result = {}
- for i = 1, #menudata.favorites do
- local server = menudata.favorites[i]
+ for i = 1, #serverlistmgr.servers do
+ local server = serverlistmgr.servers[i]
local found = 0
for k = 1, #keywords do
local keyword = keywords[k]
@@ -286,7 +292,7 @@ local function main_button_handler(tabview, fields, name, tabdata)
end
end
if found > 0 then
- local points = (#menudata.favorites - i) / 5 + found
+ local points = (#serverlistmgr.servers - i) / 5 + found
server.points = points
table.insert(search_result, server)
end
@@ -305,7 +311,7 @@ local function main_button_handler(tabview, fields, name, tabdata)
end
if fields.btn_mp_refresh then
- asyncOnlineFavourites()
+ serverlistmgr.sync()
return true
end
@@ -314,30 +320,36 @@ local function main_button_handler(tabview, fields, name, tabdata)
gamedata.playername = fields.te_name
gamedata.password = fields.te_pwd
gamedata.address = fields.te_address
- gamedata.port = fields.te_port
+ gamedata.port = tonumber(fields.te_port)
gamedata.selected_world = 0
- local fav_idx = core.get_table_index("favourites")
+ local fav_idx = core.get_table_index("favorites")
local fav = serverlist[fav_idx]
if fav_idx and fav_idx <= #serverlist and
- fav.address == fields.te_address and
- fav.port == fields.te_port then
+ fav.address == gamedata.address and
+ fav.port == gamedata.port then
+
+ serverlistmgr.add_favorite(fav)
gamedata.servername = fav.name
gamedata.serverdescription = fav.description
- if menudata.favorites_is_public and
- not is_server_protocol_compat_or_error(
+ if not is_server_protocol_compat_or_error(
fav.proto_min, fav.proto_max) then
return true
end
else
gamedata.servername = ""
gamedata.serverdescription = ""
+
+ serverlistmgr.add_favorite({
+ address = gamedata.address,
+ port = gamedata.port,
+ })
end
- core.settings:set("address", fields.te_address)
- core.settings:set("remote_port", fields.te_port)
+ core.settings:set("address", gamedata.address)
+ core.settings:set("remote_port", gamedata.port)
core.start()
return true
@@ -347,7 +359,7 @@ end
local function on_change(type, old_tab, new_tab)
if type == "LEAVE" then return end
- asyncOnlineFavourites()
+ serverlistmgr.sync()
end
--------------------------------------------------------------------------------
diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua
index 1e5264904..29744048a 100644
--- a/builtin/mainmenu/tab_settings.lua
+++ b/builtin/mainmenu/tab_settings.lua
@@ -122,56 +122,6 @@ local function antialiasing_fname_to_name(fname)
return 0
end
-local function dlg_confirm_reset_formspec(data)
- return "size[8,3]" ..
- "label[1,1;" .. fgettext("Are you sure to reset your singleplayer world?") .. "]" ..
- "button[1,2;2.6,0.5;dlg_reset_singleplayer_confirm;" .. fgettext("Yes") .. "]" ..
- "button[4,2;2.8,0.5;dlg_reset_singleplayer_cancel;" .. fgettext("No") .. "]"
-end
-
-local function dlg_confirm_reset_btnhandler(this, fields, dialogdata)
-
- if fields["dlg_reset_singleplayer_confirm"] ~= nil then
- local worldlist = core.get_worlds()
- local found_singleplayerworld = false
-
- for i = 1, #worldlist do
- if worldlist[i].name == "singleplayerworld" then
- found_singleplayerworld = true
- gamedata.worldindex = i
- end
- end
-
- if found_singleplayerworld then
- core.delete_world(gamedata.worldindex)
- end
-
- core.create_world("singleplayerworld", 1)
- worldlist = core.get_worlds()
-
- for i = 1, #worldlist do
- if worldlist[i].name == "singleplayerworld" then
- gamedata.worldindex = i
- end
- end
- end
-
- this.parent:show()
- this:hide()
- this:delete()
- return true
-end
-
-local function showconfirm_reset(tabview)
- local new_dlg = dialog_create("reset_spworld",
- dlg_confirm_reset_formspec,
- dlg_confirm_reset_btnhandler,
- nil)
- new_dlg:set_parent(tabview)
- tabview:hide()
- new_dlg:show()
-end
-
local function formspec(tabview, name, tabdata)
local tab_string =
"box[0,0;3.75,4.5;#999999]" ..
@@ -204,30 +154,26 @@ local function formspec(tabview, name, tabdata)
"box[8,0;3.75,4.5;#999999]"
local video_driver = core.settings:get("video_driver")
- local shaders_supported = video_driver == "opengl"
- local shaders_enabled = false
- if shaders_supported then
- shaders_enabled = core.settings:get_bool("enable_shaders")
+ local shaders_enabled = core.settings:get_bool("enable_shaders")
+ if video_driver == "opengl" then
tab_string = tab_string ..
"checkbox[8.25,0;cb_shaders;" .. fgettext("Shaders") .. ";"
.. tostring(shaders_enabled) .. "]"
+ elseif video_driver == "ogles2" then
+ tab_string = tab_string ..
+ "checkbox[8.25,0;cb_shaders;" .. fgettext("Shaders (experimental)") .. ";"
+ .. tostring(shaders_enabled) .. "]"
else
core.settings:set_bool("enable_shaders", false)
+ shaders_enabled = false
tab_string = tab_string ..
"label[8.38,0.2;" .. core.colorize("#888888",
fgettext("Shaders (unavailable)")) .. "]"
end
- if core.settings:get("main_menu_style") == "simple" then
- -- 'Reset singleplayer world' only functions with simple menu
- tab_string = tab_string ..
- "button[8,4.75;3.95,1;btn_reset_singleplayer;"
- .. fgettext("Reset singleplayer world") .. "]"
- else
- tab_string = tab_string ..
- "button[8,4.75;3.95,1;btn_change_keys;"
- .. fgettext("Change Keys") .. "]"
- end
+ tab_string = tab_string ..
+ "button[8,4.75;3.95,1;btn_change_keys;"
+ .. fgettext("Change Keys") .. "]"
tab_string = tab_string ..
"button[0,4.75;3.95,1;btn_advanced_settings;"
@@ -244,35 +190,23 @@ local function formspec(tabview, name, tabdata)
if shaders_enabled then
tab_string = tab_string ..
- "checkbox[8.25,0.5;cb_bumpmapping;" .. fgettext("Bump Mapping") .. ";"
- .. dump(core.settings:get_bool("enable_bumpmapping")) .. "]" ..
- "checkbox[8.25,1;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";"
+ "checkbox[8.25,0.5;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";"
.. dump(core.settings:get_bool("tone_mapping")) .. "]" ..
- "checkbox[8.25,1.5;cb_generate_normalmaps;" .. fgettext("Generate Normal Maps") .. ";"
- .. dump(core.settings:get_bool("generate_normalmaps")) .. "]" ..
- "checkbox[8.25,2;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";"
- .. dump(core.settings:get_bool("enable_parallax_occlusion")) .. "]" ..
- "checkbox[8.25,2.5;cb_waving_water;" .. fgettext("Waving Liquids") .. ";"
+ "checkbox[8.25,1;cb_waving_water;" .. fgettext("Waving Liquids") .. ";"
.. dump(core.settings:get_bool("enable_waving_water")) .. "]" ..
- "checkbox[8.25,3;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";"
+ "checkbox[8.25,1.5;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";"
.. dump(core.settings:get_bool("enable_waving_leaves")) .. "]" ..
- "checkbox[8.25,3.5;cb_waving_plants;" .. fgettext("Waving Plants") .. ";"
+ "checkbox[8.25,2;cb_waving_plants;" .. fgettext("Waving Plants") .. ";"
.. dump(core.settings:get_bool("enable_waving_plants")) .. "]"
else
tab_string = tab_string ..
"label[8.38,0.7;" .. core.colorize("#888888",
- fgettext("Bump Mapping")) .. "]" ..
- "label[8.38,1.2;" .. core.colorize("#888888",
fgettext("Tone Mapping")) .. "]" ..
- "label[8.38,1.7;" .. core.colorize("#888888",
- fgettext("Generate Normal Maps")) .. "]" ..
- "label[8.38,2.2;" .. core.colorize("#888888",
- fgettext("Parallax Occlusion")) .. "]" ..
- "label[8.38,2.7;" .. core.colorize("#888888",
+ "label[8.38,1.2;" .. core.colorize("#888888",
fgettext("Waving Liquids")) .. "]" ..
- "label[8.38,3.2;" .. core.colorize("#888888",
+ "label[8.38,1.7;" .. core.colorize("#888888",
fgettext("Waving Leaves")) .. "]" ..
- "label[8.38,3.7;" .. core.colorize("#888888",
+ "label[8.38,2.2;" .. core.colorize("#888888",
fgettext("Waving Plants")) .. "]"
end
@@ -324,22 +258,10 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
end
return true
end
- if fields["cb_bumpmapping"] then
- core.settings:set("enable_bumpmapping", fields["cb_bumpmapping"])
- return true
- end
if fields["cb_tonemapping"] then
core.settings:set("tone_mapping", fields["cb_tonemapping"])
return true
end
- if fields["cb_generate_normalmaps"] then
- core.settings:set("generate_normalmaps", fields["cb_generate_normalmaps"])
- return true
- end
- if fields["cb_parallax"] then
- core.settings:set("enable_parallax_occlusion", fields["cb_parallax"])
- return true
- end
if fields["cb_waving_water"] then
core.settings:set("enable_waving_water", fields["cb_waving_water"])
return true
@@ -359,10 +281,6 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
core.settings:set("touchtarget", fields["cb_touchscreen_target"])
return true
end
- if fields["btn_reset_singleplayer"] then
- showconfirm_reset(this)
- return true
- end
--Note dropdowns have to be handled LAST!
local ddhandled = false
diff --git a/builtin/mainmenu/tab_simple_main.lua b/builtin/mainmenu/tab_simple_main.lua
deleted file mode 100644
index 7ec95158a..000000000
--- a/builtin/mainmenu/tab_simple_main.lua
+++ /dev/null
@@ -1,220 +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.
-
---------------------------------------------------------------------------------
-local function get_formspec(tabview, name, tabdata)
- -- Update the cached supported proto info,
- -- it may have changed after a change by the settings menu.
- common_update_cached_supp_proto()
- local fav_selected = menudata.favorites[tabdata.fav_selected]
-
- local retval =
- "label[9.5,0;".. fgettext("Name / Password") .. "]" ..
- "field[0.25,3.35;5.5,0.5;te_address;;" ..
- core.formspec_escape(core.settings:get("address")) .."]" ..
- "field[5.75,3.35;2.25,0.5;te_port;;" ..
- core.formspec_escape(core.settings:get("remote_port")) .."]" ..
- "button[10,2.6;2,1.5;btn_mp_connect;".. fgettext("Connect") .. "]" ..
- "field[9.8,1;2.6,0.5;te_name;;" ..
- core.formspec_escape(core.settings:get("name")) .."]" ..
- "pwdfield[9.8,2;2.6,0.5;te_pwd;]"
-
-
- if tabdata.fav_selected and fav_selected then
- if gamedata.fav then
- retval = retval .. "button[7.7,2.6;2.3,1.5;btn_delete_favorite;" ..
- fgettext("Del. Favorite") .. "]"
- end
- end
-
- retval = retval .. "tablecolumns[" ..
- image_column(fgettext("Favorite"), "favorite") .. ";" ..
- image_column(fgettext("Ping"), "") .. ",padding=0.25;" ..
- "color,span=3;" ..
- "text,align=right;" .. -- clients
- "text,align=center,padding=0.25;" .. -- "/"
- "text,align=right,padding=0.25;" .. -- clients_max
- image_column(fgettext("Creative mode"), "creative") .. ",padding=1;" ..
- image_column(fgettext("Damage enabled"), "damage") .. ",padding=0.25;" ..
- image_column(fgettext("PvP enabled"), "pvp") .. ",padding=0.25;" ..
- "color,span=1;" ..
- "text,padding=1]" .. -- name
- "table[-0.05,0;9.2,2.75;favourites;"
-
- if #menudata.favorites > 0 then
- local favs = core.get_favorites("local")
- if #favs > 0 then
- for i = 1, #favs do
- for j = 1, #menudata.favorites do
- if menudata.favorites[j].address == favs[i].address and
- menudata.favorites[j].port == favs[i].port then
- table.insert(menudata.favorites, i,
- table.remove(menudata.favorites, j))
- end
- end
- if favs[i].address ~= menudata.favorites[i].address then
- table.insert(menudata.favorites, i, favs[i])
- end
- end
- end
- retval = retval .. render_serverlist_row(menudata.favorites[1], (#favs > 0))
- for i = 2, #menudata.favorites do
- retval = retval .. "," .. render_serverlist_row(menudata.favorites[i], (i <= #favs))
- end
- end
-
- if tabdata.fav_selected then
- retval = retval .. ";" .. tabdata.fav_selected .. "]"
- else
- retval = retval .. ";0]"
- end
-
- -- separator
- retval = retval .. "box[-0.28,3.75;12.4,0.1;#FFFFFF]"
-
- -- checkboxes
- retval = retval ..
- "checkbox[8.0,3.9;cb_creative;".. fgettext("Creative Mode") .. ";" ..
- dump(core.settings:get_bool("creative_mode")) .. "]"..
- "checkbox[8.0,4.4;cb_damage;".. fgettext("Enable Damage") .. ";" ..
- dump(core.settings:get_bool("enable_damage")) .. "]"
- -- buttons
- retval = retval ..
- "button[0,3.7;8,1.5;btn_start_singleplayer;" .. fgettext("Start Singleplayer") .. "]" ..
- "button[0,4.5;8,1.5;btn_config_sp_world;" .. fgettext("Config mods") .. "]"
-
- return retval
-end
-
---------------------------------------------------------------------------------
-local function main_button_handler(tabview, fields, name, tabdata)
- if fields.btn_start_singleplayer then
- gamedata.selected_world = gamedata.worldindex
- gamedata.singleplayer = true
- core.start()
- return true
- end
-
- if fields.favourites then
- local event = core.explode_table_event(fields.favourites)
- if event.type == "CHG" then
- if event.row <= #menudata.favorites then
- gamedata.fav = false
- local favs = core.get_favorites("local")
- local fav = menudata.favorites[event.row]
- local address = fav.address
- local port = fav.port
- gamedata.serverdescription = fav.description
-
- for i = 1, #favs do
- if fav.address == favs[i].address and
- fav.port == favs[i].port then
- gamedata.fav = true
- end
- end
-
- if address and port then
- core.settings:set("address", address)
- core.settings:set("remote_port", port)
- end
- tabdata.fav_selected = event.row
- end
- return true
- end
- end
-
- if fields.btn_delete_favorite then
- local current_favourite = core.get_table_index("favourites")
- if not current_favourite then return end
-
- core.delete_favorite(current_favourite)
- asyncOnlineFavourites()
- tabdata.fav_selected = nil
-
- core.settings:set("address", "")
- core.settings:set("remote_port", "30000")
- return true
- end
-
- if fields.cb_creative then
- core.settings:set("creative_mode", fields.cb_creative)
- return true
- end
-
- if fields.cb_damage then
- core.settings:set("enable_damage", fields.cb_damage)
- return true
- end
-
- if fields.btn_mp_connect or fields.key_enter then
- gamedata.playername = fields.te_name
- gamedata.password = fields.te_pwd
- gamedata.address = fields.te_address
- gamedata.port = fields.te_port
- local fav_idx = core.get_textlist_index("favourites")
-
- if fav_idx and fav_idx <= #menudata.favorites and
- menudata.favorites[fav_idx].address == fields.te_address and
- menudata.favorites[fav_idx].port == fields.te_port then
- local fav = menudata.favorites[fav_idx]
- gamedata.servername = fav.name
- gamedata.serverdescription = fav.description
-
- if menudata.favorites_is_public and
- not is_server_protocol_compat_or_error(
- fav.proto_min, fav.proto_max) then
- return true
- end
- else
- gamedata.servername = ""
- gamedata.serverdescription = ""
- end
-
- gamedata.selected_world = 0
-
- core.settings:set("address", fields.te_address)
- core.settings:set("remote_port", fields.te_port)
-
- core.start()
- return true
- end
-
- if fields.btn_config_sp_world then
- local configdialog = create_configure_world_dlg(1)
- if configdialog then
- configdialog:set_parent(tabview)
- tabview:hide()
- configdialog:show()
- end
- return true
- end
-end
-
---------------------------------------------------------------------------------
-local function on_activate(type,old_tab,new_tab)
- if type == "LEAVE" then return end
- asyncOnlineFavourites()
-end
-
---------------------------------------------------------------------------------
-return {
- name = "main",
- caption = fgettext("Main"),
- cbf_formspec = get_formspec,
- cbf_button_handler = main_button_handler,
- on_change = on_activate
-}
diff --git a/builtin/mainmenu/tests/favorites_wellformed.txt b/builtin/mainmenu/tests/favorites_wellformed.txt
new file mode 100644
index 000000000..8b87b4398
--- /dev/null
+++ b/builtin/mainmenu/tests/favorites_wellformed.txt
@@ -0,0 +1,29 @@
+[server]
+
+127.0.0.1
+30000
+
+
+[server]
+
+localhost
+30000
+
+
+[server]
+
+vps.rubenwardy.com
+30001
+
+
+[server]
+
+gundul.ddnss.de
+39155
+
+
+[server]
+VanessaE's Dreambuilder creative Server
+daconcepts.com
+30000
+VanessaE's Dreambuilder creative-mode server. Lots of mods, whitelisted buckets.
diff --git a/builtin/mainmenu/tests/serverlistmgr_spec.lua b/builtin/mainmenu/tests/serverlistmgr_spec.lua
new file mode 100644
index 000000000..148e9b794
--- /dev/null
+++ b/builtin/mainmenu/tests/serverlistmgr_spec.lua
@@ -0,0 +1,36 @@
+_G.core = {}
+_G.unpack = table.unpack
+_G.serverlistmgr = {}
+
+dofile("builtin/common/misc_helpers.lua")
+dofile("builtin/mainmenu/serverlistmgr.lua")
+
+local base = "builtin/mainmenu/tests/"
+
+describe("legacy favorites", function()
+ it("loads well-formed correctly", function()
+ local favs = serverlistmgr.read_legacy_favorites(base .. "favorites_wellformed.txt")
+
+ local expected = {
+ {
+ address = "127.0.0.1",
+ port = 30000,
+ },
+
+ { address = "localhost", port = 30000 },
+
+ { address = "vps.rubenwardy.com", port = 30001 },
+
+ { address = "gundul.ddnss.de", port = 39155 },
+
+ {
+ address = "daconcepts.com",
+ port = 30000,
+ name = "VanessaE's Dreambuilder creative Server",
+ description = "VanessaE's Dreambuilder creative-mode server. Lots of mods, whitelisted buckets."
+ },
+ }
+
+ assert.same(expected, favs)
+ end)
+end)