aboutsummaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/client/chatcommands.lua5
-rw-r--r--builtin/client/register.lua8
-rw-r--r--builtin/common/after.lua6
-rw-r--r--builtin/common/information_formspecs.lua2
-rw-r--r--builtin/common/misc_helpers.lua4
-rw-r--r--builtin/common/tests/vector_spec.lua4
-rw-r--r--builtin/common/vector.lua6
-rw-r--r--builtin/fstk/ui.lua17
-rw-r--r--builtin/game/chat.lua22
-rw-r--r--builtin/game/deprecated.lua25
-rw-r--r--builtin/game/falling.lua52
-rw-r--r--builtin/game/features.lua2
-rw-r--r--builtin/game/item.lua16
-rw-r--r--builtin/game/item_entity.lua5
-rw-r--r--builtin/game/knockback.lua2
-rw-r--r--builtin/game/misc.lua29
-rw-r--r--builtin/game/register.lua9
-rw-r--r--builtin/game/statbars.lua4
-rw-r--r--builtin/init.lua15
-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
-rw-r--r--builtin/profiler/instrumentation.lua1
-rw-r--r--builtin/settingtypes.txt152
35 files changed, 1336 insertions, 798 deletions
diff --git a/builtin/client/chatcommands.lua b/builtin/client/chatcommands.lua
index 5cb1b40bb..0e8d4dd03 100644
--- a/builtin/client/chatcommands.lua
+++ b/builtin/client/chatcommands.lua
@@ -23,6 +23,11 @@ core.register_on_sending_chat_message(function(message)
return true
end
+ -- Run core.registered_on_chatcommand callbacks.
+ if core.run_callbacks(core.registered_on_chatcommand, 5, cmd, param) then
+ return true
+ end
+
local cmd_def = core.registered_chatcommands[cmd]
if cmd_def then
core.set_last_run_mod(cmd_def.mod_origin)
diff --git a/builtin/client/register.lua b/builtin/client/register.lua
index c1b4965c1..27a6b02d9 100644
--- a/builtin/client/register.lua
+++ b/builtin/client/register.lua
@@ -4,6 +4,13 @@ core.callback_origins = {}
local getinfo = debug.getinfo
debug.getinfo = nil
+--- Runs given callbacks.
+--
+-- Note: this function is also called from C++
+-- @tparam table callbacks a table with registered callbacks, like `core.registered_on_*`
+-- @tparam number mode a RunCallbacksMode, as defined in src/script/common/c_internal.h
+-- @param ... arguments for the callback
+-- @return depends on mode
function core.run_callbacks(callbacks, mode, ...)
assert(type(callbacks) == "table")
local cb_len = #callbacks
@@ -63,6 +70,7 @@ core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration
core.registered_on_shutdown, core.register_on_shutdown = make_registration()
core.registered_on_receiving_chat_message, core.register_on_receiving_chat_message = make_registration()
core.registered_on_sending_chat_message, core.register_on_sending_chat_message = make_registration()
+core.registered_on_chatcommand, core.register_on_chatcommand = make_registration()
core.registered_on_death, core.register_on_death = make_registration()
core.registered_on_hp_modification, core.register_on_hp_modification = make_registration()
core.registered_on_damage_taken, core.register_on_damage_taken = make_registration()
diff --git a/builtin/common/after.lua b/builtin/common/after.lua
index b314711c9..e20f292f0 100644
--- a/builtin/common/after.lua
+++ b/builtin/common/after.lua
@@ -31,11 +31,13 @@ function core.after(after, func, ...)
assert(tonumber(after) and type(func) == "function",
"Invalid minetest.after invocation")
local expire = time + after
- jobs[#jobs + 1] = {
+ local new_job = {
func = func,
expire = expire,
arg = {...},
- mod_origin = core.get_last_run_mod()
+ mod_origin = core.get_last_run_mod(),
}
+ jobs[#jobs + 1] = new_job
time_next = math.min(time_next, expire)
+ return { cancel = function() new_job.func = function() end end }
end
diff --git a/builtin/common/information_formspecs.lua b/builtin/common/information_formspecs.lua
index 8afa5bc87..3e2f1f079 100644
--- a/builtin/common/information_formspecs.lua
+++ b/builtin/common/information_formspecs.lua
@@ -115,7 +115,7 @@ core.register_on_player_receive_fields(function(player, formname, fields)
return
end
- local event = minetest.explode_table_event(fields.list)
+ local event = core.explode_table_event(fields.list)
if event.type ~= "INV" then
local name = player:get_player_name()
core.show_formspec(name, "__builtin:help_cmds",
diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua
index e29a9f422..0f3897f47 100644
--- a/builtin/common/misc_helpers.lua
+++ b/builtin/common/misc_helpers.lua
@@ -697,3 +697,7 @@ function core.privs_to_string(privs, delim)
end
return table.concat(list, delim)
end
+
+function core.is_nan(number)
+ return number ~= number
+end
diff --git a/builtin/common/tests/vector_spec.lua b/builtin/common/tests/vector_spec.lua
index 6f308a4a8..0f287363a 100644
--- a/builtin/common/tests/vector_spec.lua
+++ b/builtin/common/tests/vector_spec.lua
@@ -44,6 +44,10 @@ describe("vector", function()
assert.same({ x = 2, y = 4, z = 6 }, vector.add(vector.new(1, 2, 3), { x = 1, y = 2, z = 3 }))
end)
+ it("offset()", function()
+ assert.same({ x = 41, y = 52, z = 63 }, vector.offset(vector.new(1, 2, 3), 40, 50, 60))
+ end)
+
-- This function is needed because of floating point imprecision.
local function almost_equal(a, b)
if type(a) == "number" then
diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua
index 1fd784ce2..d6437deda 100644
--- a/builtin/common/vector.lua
+++ b/builtin/common/vector.lua
@@ -137,6 +137,12 @@ function vector.divide(a, b)
end
end
+function vector.offset(v, x, y, z)
+ return {x = v.x + x,
+ y = v.y + y,
+ z = v.z + z}
+end
+
function vector.sort(a, b)
return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)},
{x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)}
diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua
index 6d26aabf0..976659ed3 100644
--- a/builtin/fstk/ui.lua
+++ b/builtin/fstk/ui.lua
@@ -18,6 +18,8 @@
ui = {}
ui.childlist = {}
ui.default = nil
+-- Whether fstk is currently showing its own formspec instead of active ui elements.
+ui.overridden = false
--------------------------------------------------------------------------------
function ui.add(child)
@@ -55,6 +57,7 @@ end
--------------------------------------------------------------------------------
function ui.update()
+ ui.overridden = false
local formspec = {}
-- handle errors
@@ -64,12 +67,14 @@ function ui.update()
formspec = {
"size[14,8]",
"real_coordinates[true]",
+ "set_focus[btn_reconnect_yes;true]",
"box[0.5,1.2;13,5;#000]",
("textarea[0.5,1.2;13,5;;%s;%s]"):format(
fgettext("The server has requested a reconnect:"), error_message),
"button[2,6.6;4,1;btn_reconnect_yes;" .. fgettext("Reconnect") .. "]",
"button[8,6.6;4,1;btn_reconnect_no;" .. fgettext("Main menu") .. "]"
}
+ ui.overridden = true
elseif gamedata ~= nil and gamedata.errormessage ~= nil then
local error_message = core.formspec_escape(gamedata.errormessage)
@@ -82,11 +87,13 @@ function ui.update()
formspec = {
"size[14,8]",
"real_coordinates[true]",
+ "set_focus[btn_error_confirm;true]",
"box[0.5,1.2;13,5;#000]",
("textarea[0.5,1.2;13,5;;%s;%s]"):format(
error_title, error_message),
"button[5,6.6;4,1;btn_error_confirm;" .. fgettext("OK") .. "]"
}
+ ui.overridden = true
else
local active_toplevel_ui_elements = 0
for key,value in pairs(ui.childlist) do
@@ -183,6 +190,16 @@ end
--------------------------------------------------------------------------------
core.event_handler = function(event)
+ -- Handle error messages
+ if ui.overridden then
+ if event == "MenuQuit" then
+ gamedata.errormessage = nil
+ gamedata.reconnect_requested = false
+ ui.update()
+ end
+ return
+ end
+
if ui.handle_events(event) then
ui.update()
return
diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua
index aae811794..945707623 100644
--- a/builtin/game/chat.lua
+++ b/builtin/game/chat.lua
@@ -58,6 +58,11 @@ core.register_on_chat_message(function(name, message)
param = param or ""
+ -- Run core.registered_on_chatcommands callbacks.
+ if core.run_callbacks(core.registered_on_chatcommands, 5, name, cmd, param) then
+ return true
+ end
+
local cmd_def = core.registered_chatcommands[cmd]
if not cmd_def then
core.chat_send_player(name, "-!- Invalid command: " .. cmd)
@@ -66,8 +71,17 @@ core.register_on_chat_message(function(name, message)
local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
if has_privs then
core.set_last_run_mod(cmd_def.mod_origin)
- local _, result = cmd_def.func(name, param)
- if result then
+ local success, result = cmd_def.func(name, param)
+ if success == false and result == nil then
+ core.chat_send_player(name, "-!- Invalid command usage")
+ local help_def = core.registered_chatcommands["help"]
+ if help_def then
+ local _, helpmsg = help_def.func(name, cmd)
+ if helpmsg then
+ core.chat_send_player(name, helpmsg)
+ end
+ end
+ elseif result then
core.chat_send_player(name, result)
end
else
@@ -1070,10 +1084,10 @@ core.register_chatcommand("last-login", {
local pauth = core.get_auth_handler().get_auth(param)
if pauth and pauth.last_login and pauth.last_login ~= -1 then
-- Time in UTC, ISO 8601 format
- return true, "Last login time was " ..
+ return true, param.."'s last login time was " ..
os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
end
- return false, "Last login time is unknown"
+ return false, param.."'s last login time is unknown"
end,
})
diff --git a/builtin/game/deprecated.lua b/builtin/game/deprecated.lua
index 20f0482eb..c5c7848f5 100644
--- a/builtin/game/deprecated.lua
+++ b/builtin/game/deprecated.lua
@@ -1,29 +1,6 @@
-- Minetest: builtin/deprecated.lua
--
--- Default material types
---
-local function digprop_err()
- core.log("deprecated", "The core.digprop_* functions are obsolete and need to be replaced by item groups.")
-end
-
-core.digprop_constanttime = digprop_err
-core.digprop_stonelike = digprop_err
-core.digprop_dirtlike = digprop_err
-core.digprop_gravellike = digprop_err
-core.digprop_woodlike = digprop_err
-core.digprop_leaveslike = digprop_err
-core.digprop_glasslike = digprop_err
-
-function core.node_metadata_inventory_move_allow_all()
- core.log("deprecated", "core.node_metadata_inventory_move_allow_all is obsolete and does nothing.")
-end
-
-function core.add_to_creative_inventory(itemstring)
- core.log("deprecated", "core.add_to_creative_inventory is obsolete and does nothing.")
-end
-
---
-- EnvRef
--
core.env = {}
@@ -77,7 +54,7 @@ core.setting_save = setting_proxy("write")
function core.register_on_auth_fail(func)
core.log("deprecated", "core.register_on_auth_fail " ..
- "is obsolete and should be replaced by " ..
+ "is deprecated and should be replaced by " ..
"core.register_on_authplayer instead.")
core.register_on_authplayer(function (player_name, ip, is_success)
diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua
index 714506a5f..057d0d0ed 100644
--- a/builtin/game/falling.lua
+++ b/builtin/game/falling.lua
@@ -52,6 +52,7 @@ core.register_entity(":__builtin:falling_node", {
floats = false,
set_node = function(self, node, meta)
+ node.param2 = node.param2 or 0
self.node = node
meta = meta or {}
if type(meta.to_table) == "function" then
@@ -83,6 +84,9 @@ core.register_entity(":__builtin:falling_node", {
local textures
if def.tiles and def.tiles[1] then
local tile = def.tiles[1]
+ if def.drawtype == "torchlike" and def.paramtype2 ~= "wallmounted" then
+ tile = def.tiles[2] or def.tiles[1]
+ end
if type(tile) == "table" then
tile = tile.name
end
@@ -126,7 +130,7 @@ core.register_entity(":__builtin:falling_node", {
-- Set collision box (certain nodeboxes only for now)
local nb_types = {fixed=true, leveled=true, connected=true}
if def.drawtype == "nodebox" and def.node_box and
- nb_types[def.node_box.type] then
+ nb_types[def.node_box.type] and def.node_box.fixed then
local box = table.copy(def.node_box.fixed)
if type(box[1]) == "table" then
box = #box == 1 and box[1] or nil -- We can only use a single box
@@ -143,9 +147,13 @@ core.register_entity(":__builtin:falling_node", {
-- Rotate entity
if def.drawtype == "torchlike" then
- self.object:set_yaw(math.pi*0.25)
- elseif (node.param2 ~= 0 and (def.wield_image == ""
- or def.wield_image == nil))
+ if def.paramtype2 == "wallmounted" then
+ self.object:set_yaw(math.pi*0.25)
+ else
+ self.object:set_yaw(-math.pi*0.25)
+ end
+ elseif ((node.param2 ~= 0 or def.drawtype == "nodebox" or def.drawtype == "mesh")
+ and (def.wield_image == "" or def.wield_image == nil))
or def.drawtype == "signlike"
or def.drawtype == "mesh"
or def.drawtype == "normal"
@@ -160,16 +168,30 @@ core.register_entity(":__builtin:falling_node", {
elseif (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted") then
local rot = node.param2 % 8
local pitch, yaw, roll = 0, 0, 0
- if rot == 1 then
- pitch, yaw = math.pi, math.pi
- elseif rot == 2 then
- pitch, yaw = math.pi/2, math.pi/2
- elseif rot == 3 then
- pitch, yaw = math.pi/2, -math.pi/2
- elseif rot == 4 then
- pitch, yaw = math.pi/2, math.pi
- elseif rot == 5 then
- pitch, yaw = math.pi/2, 0
+ if def.drawtype == "nodebox" or def.drawtype == "mesh" then
+ if rot == 0 then
+ pitch, yaw = math.pi/2, 0
+ elseif rot == 1 then
+ pitch, yaw = -math.pi/2, math.pi
+ elseif rot == 2 then
+ pitch, yaw = 0, math.pi/2
+ elseif rot == 3 then
+ pitch, yaw = 0, -math.pi/2
+ elseif rot == 4 then
+ pitch, yaw = 0, math.pi
+ end
+ else
+ if rot == 1 then
+ pitch, yaw = math.pi, math.pi
+ elseif rot == 2 then
+ pitch, yaw = math.pi/2, math.pi/2
+ elseif rot == 3 then
+ pitch, yaw = math.pi/2, -math.pi/2
+ elseif rot == 4 then
+ pitch, yaw = math.pi/2, math.pi
+ elseif rot == 5 then
+ pitch, yaw = math.pi/2, 0
+ end
end
if def.drawtype == "signlike" then
pitch = pitch - math.pi/2
@@ -178,7 +200,7 @@ core.register_entity(":__builtin:falling_node", {
elseif rot == 1 then
yaw = yaw - math.pi/2
end
- elseif def.drawtype == "mesh" or def.drawtype == "normal" then
+ elseif def.drawtype == "mesh" or def.drawtype == "normal" or def.drawtype == "nodebox" then
if rot >= 0 and rot <= 1 then
roll = roll + math.pi
else
diff --git a/builtin/game/features.lua b/builtin/game/features.lua
index a15475333..36ff1f0b0 100644
--- a/builtin/game/features.lua
+++ b/builtin/game/features.lua
@@ -17,6 +17,8 @@ core.features = {
area_store_persistent_ids = true,
pathfinder_works = true,
object_step_has_moveresult = true,
+ direct_velocity_on_players = true,
+ use_texture_alpha_string_modes = true,
}
function core.has_feature(arg)
diff --git a/builtin/game/item.lua b/builtin/game/item.lua
index f680ce0d4..b68177c22 100644
--- a/builtin/game/item.lua
+++ b/builtin/game/item.lua
@@ -551,12 +551,13 @@ function core.node_dig(pos, node, digger)
local diggername = user_name(digger)
local log = make_log(diggername)
local def = core.registered_nodes[node.name]
+ -- Copy pos because the callback could modify it
if def and (not def.diggable or
- (def.can_dig and not def.can_dig(pos, digger))) then
+ (def.can_dig and not def.can_dig(vector.new(pos), digger))) then
log("info", diggername .. " tried to dig "
.. node.name .. " which is not diggable "
.. core.pos_to_string(pos))
- return
+ return false
end
if core.is_protected(pos, diggername) then
@@ -565,7 +566,7 @@ function core.node_dig(pos, node, digger)
.. " at protected position "
.. core.pos_to_string(pos))
core.record_protection_violation(pos, diggername)
- return
+ return false
end
log('action', diggername .. " digs "
@@ -648,6 +649,8 @@ function core.node_dig(pos, node, digger)
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
callback(pos_copy, node_copy, digger)
end
+
+ return true
end
function core.itemstring_with_palette(item, palette_index)
@@ -675,7 +678,7 @@ end
-- Item definition defaults
--
-local default_stack_max = tonumber(minetest.settings:get("default_stack_max")) or 99
+local default_stack_max = tonumber(core.settings:get("default_stack_max")) or 99
core.nodedef_default = {
-- Item properties
@@ -704,10 +707,6 @@ core.nodedef_default = {
on_receive_fields = nil,
- on_metadata_inventory_move = core.node_metadata_inventory_move_allow_all,
- on_metadata_inventory_offer = core.node_metadata_inventory_offer_allow_all,
- on_metadata_inventory_take = core.node_metadata_inventory_take_allow_all,
-
-- Node properties
drawtype = "normal",
visual_scale = 1.0,
@@ -718,7 +717,6 @@ core.nodedef_default = {
-- {name="", backface_culling=true},
-- {name="", backface_culling=true},
--},
- alpha = 255,
post_effect_color = {a=0, r=0, g=0, b=0},
paramtype = "none",
paramtype2 = "none",
diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua
index 20dd18044..9b1b23bfd 100644
--- a/builtin/game/item_entity.lua
+++ b/builtin/game/item_entity.lua
@@ -54,8 +54,9 @@ core.register_entity(":__builtin:item", {
local max_count = stack:get_stack_max()
local count = math.min(stack:get_count(), max_count)
local size = 0.2 + 0.1 * (count / max_count) ^ (1 / 3)
- local def = core.registered_nodes[itemname]
- local glow = def and math.floor(def.light_source / 2 + 0.5)
+ local def = core.registered_items[itemname]
+ local glow = def and def.light_source and
+ math.floor(def.light_source / 2 + 0.5)
self.object:set_properties({
is_visible = true,
diff --git a/builtin/game/knockback.lua b/builtin/game/knockback.lua
index b5c4cbc5a..a937aa186 100644
--- a/builtin/game/knockback.lua
+++ b/builtin/game/knockback.lua
@@ -42,5 +42,5 @@ core.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool
return -- barely noticeable, so don't even send
end
- player:add_player_velocity(kdir)
+ player:add_velocity(kdir)
end)
diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua
index 341e613c2..b8c5e16a9 100644
--- a/builtin/game/misc.lua
+++ b/builtin/game/misc.lua
@@ -151,6 +151,12 @@ function core.setting_get_pos(name)
end
+-- See l_env.cpp for the other functions
+function core.get_artificial_light(param1)
+ return math.floor(param1 / 16)
+end
+
+
-- To be overriden by protection mods
function core.is_protected(pos, name)
@@ -260,3 +266,26 @@ end
function core.cancel_shutdown_requests()
core.request_shutdown("", false, -1)
end
+
+
+-- Callback handling for dynamic_add_media
+
+local dynamic_add_media_raw = core.dynamic_add_media_raw
+core.dynamic_add_media_raw = nil
+function core.dynamic_add_media(filepath, callback)
+ local ret = dynamic_add_media_raw(filepath)
+ if ret == false then
+ return ret
+ end
+ if callback == nil then
+ core.log("deprecated", "Calling minetest.dynamic_add_media without "..
+ "a callback is deprecated and will stop working in future versions.")
+ else
+ -- At the moment async loading is not actually implemented, so we
+ -- immediately call the callback ourselves
+ for _, name in ipairs(ret) do
+ callback(name)
+ end
+ end
+ return true
+end
diff --git a/builtin/game/register.lua b/builtin/game/register.lua
index 1034d4f2b..1cff85813 100644
--- a/builtin/game/register.lua
+++ b/builtin/game/register.lua
@@ -320,13 +320,6 @@ for name in pairs(forbidden_item_names) do
register_alias_raw(name, "")
end
-
--- Obsolete:
--- Aliases for core.register_alias (how ironic...)
--- core.alias_node = core.register_alias
--- core.alias_tool = core.register_alias
--- core.alias_craftitem = core.register_alias
-
--
-- Built-in node definitions. Also defined in C.
--
@@ -584,6 +577,7 @@ core.unregister_biome = make_wrap_deregistration(core.register_biome,
core.clear_registered_biomes, core.registered_biomes)
core.registered_on_chat_messages, core.register_on_chat_message = make_registration()
+core.registered_on_chatcommands, core.register_on_chatcommand = make_registration()
core.registered_globalsteps, core.register_globalstep = make_registration()
core.registered_playerevents, core.register_playerevent = make_registration()
core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration()
@@ -612,6 +606,7 @@ core.registered_can_bypass_userlimit, core.register_can_bypass_userlimit = make_
core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration()
core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration()
core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration()
+core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_registration()
--
-- Compatibility for on_mapgen_init()
diff --git a/builtin/game/statbars.lua b/builtin/game/statbars.lua
index d192029c5..db5087a16 100644
--- a/builtin/game/statbars.lua
+++ b/builtin/game/statbars.lua
@@ -84,8 +84,8 @@ local function update_builtin_statbars(player)
end
if hud.id_breathbar and (not show_breathbar or breath == breath_max) then
- minetest.after(1, function(player_name, breath_bar)
- local player = minetest.get_player_by_name(player_name)
+ core.after(1, function(player_name, breath_bar)
+ local player = core.get_player_by_name(player_name)
if player then
player:hud_remove(breath_bar)
end
diff --git a/builtin/init.lua b/builtin/init.lua
index 75bb3db85..89b1fdc64 100644
--- a/builtin/init.lua
+++ b/builtin/init.lua
@@ -39,9 +39,20 @@ if INIT == "game" then
assert(not core.get_http_api)
elseif INIT == "mainmenu" then
local mm_script = core.settings:get("main_menu_script")
+ local custom_loaded = false
if mm_script and mm_script ~= "" then
- dofile(mm_script)
- else
+ local testfile = io.open(mm_script, "r")
+ if testfile then
+ testfile:close()
+ dofile(mm_script)
+ custom_loaded = true
+ core.log("info", "Loaded custom main menu script: "..mm_script)
+ else
+ core.log("error", "Failed to load custom main menu script: "..mm_script)
+ core.log("info", "Falling back to default main menu script")
+ end
+ end
+ if not custom_loaded then
dofile(core.get_mainmenu_path() .. DIR_DELIM .. "init.lua")
end
elseif INIT == "async" then
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)
diff --git a/builtin/profiler/instrumentation.lua b/builtin/profiler/instrumentation.lua
index 237f048fb..6b951a2c2 100644
--- a/builtin/profiler/instrumentation.lua
+++ b/builtin/profiler/instrumentation.lua
@@ -160,6 +160,7 @@ local function init()
-- Simple iteration would ignore lookup via __index.
local entity_instrumentation = {
"on_activate",
+ "on_deactivate",
"on_step",
"on_punch",
"on_rightclick",
diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt
index c787aea2c..f800f71ab 100644
--- a/builtin/settingtypes.txt
+++ b/builtin/settingtypes.txt
@@ -110,9 +110,9 @@ doubletap_jump (Double tap jump for fly) bool false
# enabled.
always_fly_fast (Always fly and fast) bool true
-# The time in seconds it takes between repeated right clicks when holding the right
-# mouse button.
-repeat_rightclick_time (Rightclick repetition interval) float 0.25 0.001
+# The time in seconds it takes between repeated node placements when holding
+# the place button.
+repeat_place_time (Place repetition interval) float 0.25 0.001
# Automatically jump up single-node obstacles.
autojump (Automatic jumping) bool false
@@ -152,6 +152,9 @@ joystick_type (Joystick type) enum auto auto,generic,xbox
# when holding down a joystick button combination.
repeat_joystick_button_time (Joystick button repetition interval) float 0.17 0.001
+# The deadzone of the joystick
+joystick_deadzone (Joystick deadzone) int 2048
+
# The sensitivity of the joystick axes for moving the
# ingame view frustum around.
joystick_frustum_sensitivity (Joystick frustum sensitivity) float 170
@@ -182,6 +185,14 @@ keymap_jump (Jump key) key KEY_SPACE
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
keymap_sneak (Sneak key) key KEY_LSHIFT
+# Key for digging.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_dig (Dig key) key KEY_LBUTTON
+
+# Key for placing.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_place (Place key) key KEY_RBUTTON
+
# Key for opening the inventory.
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
keymap_inventory (Inventory key) key KEY_KEY_I
@@ -440,6 +451,10 @@ keymap_decrease_viewing_range_min (View range decrease key) key -
[**Basic]
+# Whether nametag backgrounds should be shown by default.
+# Mods may still set a background.
+show_nametag_backgrounds (Show nametag backgrounds by default) bool true
+
# Enable vertex buffer objects.
# This should greatly improve graphics performance.
enable_vbo (VBO) bool true
@@ -505,8 +520,13 @@ texture_clean_transparent (Clean transparent textures) bool false
# texture autoscaling.
texture_min_size (Minimum texture size) int 64
-# Experimental option, might cause visible spaces between blocks
-# when set to higher number than 0.
+# Use multi-sample antialiasing (MSAA) to smooth out block edges.
+# This algorithm smooths out the 3D viewport while keeping the image sharp,
+# but it doesn't affect the insides of textures
+# (which is especially noticeable with transparent textures).
+# Visible spaces appear between nodes when shaders are disabled.
+# If set to 0, MSAA is disabled.
+# A restart is required after changing this option.
fsaa (FSAA) enum 0 0,1,2,4,8,16
# Undersampling is similar to using a lower screen resolution, but it applies
@@ -533,43 +553,6 @@ shader_path (Shader path) path
# enhanced, highlights and shadows are gradually compressed.
tone_mapping (Filmic tone mapping) bool false
-[***Bumpmapping]
-
-# Enables bumpmapping for textures. Normalmaps need to be supplied by the texture pack
-# or need to be auto-generated.
-# Requires shaders to be enabled.
-enable_bumpmapping (Bumpmapping) bool false
-
-# Enables on the fly normalmap generation (Emboss effect).
-# Requires bumpmapping to be enabled.
-generate_normalmaps (Generate normalmaps) bool false
-
-# Strength of generated normalmaps.
-normalmaps_strength (Normalmaps strength) float 0.6
-
-# Defines sampling step of texture.
-# A higher value results in smoother normal maps.
-normalmaps_smooth (Normalmaps sampling) int 0 0 2
-
-[***Parallax Occlusion]
-
-# Enables parallax occlusion mapping.
-# Requires shaders to be enabled.
-enable_parallax_occlusion (Parallax occlusion) bool false
-
-# 0 = parallax occlusion with slope information (faster).
-# 1 = relief mapping (slower, more accurate).
-parallax_occlusion_mode (Parallax occlusion mode) int 1 0 1
-
-# Number of parallax occlusion iterations.
-parallax_occlusion_iterations (Parallax occlusion iterations) int 4
-
-# Overall scale of parallax occlusion effect.
-parallax_occlusion_scale (Parallax occlusion scale) float 0.08
-
-# Overall bias of parallax occlusion effect, usually scale/2.
-parallax_occlusion_bias (Parallax occlusion bias) float 0.04
-
[***Waving Nodes]
# Set to true to enable waving liquids (like water).
@@ -610,15 +593,15 @@ arm_inertia (Arm inertia) bool true
# to not waste CPU power for no benefit.
fps_max (Maximum FPS) int 60 1
-# Maximum FPS when game is paused.
-pause_fps_max (FPS in pause menu) int 20 1
+# Maximum FPS when the window is not focused, or when the game is paused.
+fps_max_unfocused (FPS when unfocused or paused) int 20 1
# Open the pause menu when the window's focus is lost. Does not pause if a formspec is
# open.
pause_on_lost_focus (Pause on lost window focus) bool false
# View distance in nodes.
-viewing_range (Viewing range) int 100 20 4000
+viewing_range (Viewing range) int 190 20 4000
# Camera 'near clipping plane' distance in nodes, between 0 and 0.25
# Only works on GLES platforms. Most users will not need to change this.
@@ -682,8 +665,8 @@ texture_path (Texture path) path
# The rendering back-end for Irrlicht.
# A restart is required after changing this.
# Note: On Android, stick with OGLES1 if unsure! App may fail to start otherwise.
-# On other platforms, OpenGL is recommended, and it’s the only driver with
-# shader support currently.
+# On other platforms, OpenGL is recommended.
+# Shaders are supported by OpenGL (desktop only) and OGLES2 (experimental)
video_driver (Video driver) enum opengl null,software,burningsvideo,direct3d8,direct3d9,opengl,ogles1,ogles2
# Radius of cloud area stated in number of 64 node cloud squares.
@@ -741,9 +724,11 @@ selectionbox_color (Selection box color) string (0,0,0)
selectionbox_width (Selection box width) int 2 1 5
# Crosshair color (R,G,B).
+# Also controls the object crosshair color
crosshair_color (Crosshair color) string (255,255,255)
# Crosshair alpha (opaqueness, between 0 and 255).
+# Also controls the object crosshair color
crosshair_alpha (Crosshair alpha) int 255 0 255
# Maximum number of recent chat messages to show
@@ -817,7 +802,8 @@ world_aligned_mode (World-aligned textures mode) enum enable disable,enable,forc
autoscale_mode (Autoscaling mode) enum disable disable,enable,force
# Show entity selection boxes
-show_entity_selectionbox (Show entity selection boxes) bool true
+# A restart is required after changing this.
+show_entity_selectionbox (Show entity selection boxes) bool false
[*Menus]
@@ -982,7 +968,7 @@ serverlist_url (Serverlist URL) string servers.minetest.net
# File in client/serverlist/ that contains your favorite servers displayed in the
# Multiplayer Tab.
-serverlist_file (Serverlist file) string favoriteservers.txt
+serverlist_file (Serverlist file) string favoriteservers.json
# Maximum size of the out chat queue.
# 0 to disable queueing and -1 to make the queue size unlimited.
@@ -999,7 +985,7 @@ client_unload_unused_data_timeout (Mapblock unload timeout) int 600
# Maximum number of mapblocks for client to be kept in memory.
# Set to -1 for unlimited amount.
-client_mapblock_limit (Mapblock limit) int 5000
+client_mapblock_limit (Mapblock limit) int 7500
# Whether to show the client debug info (has the same effect as hitting F5).
show_debug (Show debug info) bool false
@@ -1069,6 +1055,13 @@ full_block_send_enable_min_time_from_building (Delay in sending blocks after bui
# client number.
max_packets_per_iteration (Max. packets per iteration) int 1024
+# ZLib compression level to use when sending mapblocks to the client.
+# -1 - Zlib's default compression level
+# 0 - no compresson, fastest
+# 9 - best compression, slowest
+# (levels 1-3 use Zlib's "fast" method, 4-9 use the normal method)
+map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9
+
[*Game]
# Default game when creating a new world.
@@ -1096,7 +1089,7 @@ default_stack_max (Default stack size) int 99
# Enable players getting damage and dying.
enable_damage (Damage) bool false
-# Enable creative mode for new created maps.
+# Enable creative mode for all players
creative_mode (Creative) bool false
# A chosen map seed for a new map, leave empty for random.
@@ -1158,17 +1151,17 @@ ask_reconnect_on_crash (Ask to reconnect after crash) bool false
# Setting this larger than active_block_range will also cause the server
# to maintain active objects up to this distance in the direction the
# player is looking. (This can avoid mobs suddenly disappearing from view)
-active_object_send_range_blocks (Active object send range) int 4
+active_object_send_range_blocks (Active object send range) int 8
# The radius of the volume of blocks around every player that is subject to the
# active block stuff, stated in mapblocks (16 nodes).
# In active blocks objects are loaded and ABMs run.
# This is also the minimum range in which active objects (mobs) are maintained.
# This should be configured together with active_object_send_range_blocks.
-active_block_range (Active block range) int 3
+active_block_range (Active block range) int 4
# From how far blocks are sent to clients, stated in mapblocks (16 nodes).
-max_block_send_distance (Max block send distance) int 10
+max_block_send_distance (Max block send distance) int 12
# Maximum number of forceloaded mapblocks.
max_forceloaded_blocks (Maximum forceloaded blocks) int 16
@@ -1241,10 +1234,10 @@ movement_gravity (Gravity) float 9.81
[**Advanced]
# Handling for deprecated Lua API calls:
-# - legacy: (try to) mimic old behaviour (default for release).
-# - log: mimic and log backtrace of deprecated call (default for debug).
+# - none: Do not log deprecated calls
+# - log: mimic and log backtrace of deprecated call (default).
# - error: abort on usage of deprecated call (suggested for mod developers).
-deprecated_lua_api_handling (Deprecated Lua API handling) enum legacy legacy,log,error
+deprecated_lua_api_handling (Deprecated Lua API handling) enum log none,log,error
# Number of extra blocks that can be loaded by /clearobjects at once.
# This is a trade-off between sqlite transaction overhead and
@@ -1261,6 +1254,13 @@ max_objects_per_block (Maximum objects per block) int 64
# See https://www.sqlite.org/pragma.html#pragma_synchronous
sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2
+# ZLib compression level to use when saving mapblocks to disk.
+# -1 - Zlib's default compression level
+# 0 - no compresson, fastest
+# 9 - best compression, slowest
+# (levels 1-3 use Zlib's "fast" method, 4-9 use the normal method)
+map_compression_level_disk (Map Compression Level for Disk Storage) int 3 -1 9
+
# Length of a server tick and the interval at which objects are generally updated over
# network.
dedicated_server_step (Dedicated server step) float 0.09
@@ -1271,6 +1271,10 @@ active_block_mgmt_interval (Active block management interval) float 2.0
# Length of time between Active Block Modifier (ABM) execution cycles
abm_interval (ABM interval) float 1.0
+# The time budget allowed for ABMs to execute on each step
+# (as a fraction of the ABM Interval)
+abm_time_budget (ABM time budget) float 0.2 0.1 0.9
+
# Length of time between NodeTimer execution cycles
nodetimer_interval (NodeTimer interval) float 0.2
@@ -1431,12 +1435,6 @@ curl_file_download_timeout (cURL file download timeout) int 300000
# Makes DirectX work with LuaJIT. Disable if it causes troubles.
high_precision_fpu (High-precision FPU) bool true
-# Changes the main menu UI:
-# - Full: Multiple singleplayer worlds, game choice, texture pack chooser, etc.
-# - Simple: One singleplayer world, no game or texture pack choosers. May be
-# necessary for smaller screens.
-main_menu_style (Main menu style) enum full full,simple
-
# Replaces the default main menu with a custom one.
main_menu_script (Main menu script) string
@@ -1456,7 +1454,7 @@ mg_name (Mapgen name) enum v7 v7,valleys,carpathian,v5,flat,fractal,singlenode,v
water_level (Water level) int 1
# From how far blocks are generated for clients, stated in mapblocks (16 nodes).
-max_block_generate_distance (Max block generate distance) int 8
+max_block_generate_distance (Max block generate distance) int 10
# Limit of map generation, in nodes, in all 6 directions from (0, 0, 0).
# Only mapchunks completely within the mapgen limit are generated.
@@ -1466,7 +1464,7 @@ mapgen_limit (Map generation limit) int 31000 0 31000
# Global map generation attributes.
# In Mapgen v6 the 'decorations' flag controls all decorations except trees
# and junglegrass, in all other mapgens this flag controls all decorations.
-mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes caves,dungeons,light,decorations,biomes,nocaves,nodungeons,nolight,nodecorations,nobiomes
+mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores,nocaves,nodungeons,nolight,nodecorations,nobiomes,noores
[*Biome API temperature and humidity noise parameters]
@@ -1857,7 +1855,7 @@ mgcarpathian_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 50
# Map generation attributes specific to Mapgen Flat.
# Occasional lakes and hills can be added to the flat world.
-mgflat_spflags (Mapgen Flat specific flags) flags nolakes,nohills lakes,hills,nolakes,nohills
+mgflat_spflags (Mapgen Flat specific flags) flags nolakes,nohills,nocaverns lakes,hills,caverns,nolakes,nohills,nocaverns
# Y of flat ground.
mgflat_ground_level (Ground level) int 8
@@ -1901,6 +1899,15 @@ mgflat_hill_threshold (Hill threshold) float 0.45
# Controls steepness/height of hills.
mgflat_hill_steepness (Hill steepness) float 64.0
+# Y-level of cavern upper limit.
+mgflat_cavern_limit (Cavern limit) int -256
+
+# Y-distance over which caverns expand to full size.
+mgflat_cavern_taper (Cavern taper) int 256
+
+# Defines full size of caverns, smaller values create larger caverns.
+mgflat_cavern_threshold (Cavern threshold) float 0.7
+
# Lower Y limit of dungeons.
mgflat_dungeon_ymin (Dungeon minimum Y) int -31000
@@ -1921,6 +1928,9 @@ mgflat_np_cave1 (Cave1 noise) noise_params_3d 0, 12, (61, 61, 61), 52534, 3, 0.5
# Second of two 3D noises that together define tunnels.
mgflat_np_cave2 (Cave2 noise) noise_params_3d 0, 12, (67, 67, 67), 10325, 3, 0.5, 2.0
+# 3D noise defining giant caverns.
+mgflat_np_cavern (Cavern noise) noise_params_3d 0, 1, (384, 128, 384), 723, 5, 0.63, 2.0
+
# 3D noise that determines number of dungeons per mapchunk.
mgflat_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, 2, 0.8, 2.0
@@ -2163,15 +2173,15 @@ chunksize (Chunk size) int 5
enable_mapgen_debug_info (Mapgen debug) bool false
# Maximum number of blocks that can be queued for loading.
-emergequeue_limit_total (Absolute limit of queued blocks to emerge) int 512
+emergequeue_limit_total (Absolute limit of queued blocks to emerge) int 1024
# Maximum number of blocks to be queued that are to be loaded from file.
# This limit is enforced per player.
-emergequeue_limit_diskonly (Per-player limit of queued blocks load from disk) int 64
+emergequeue_limit_diskonly (Per-player limit of queued blocks load from disk) int 128
# Maximum number of blocks to be queued that are to be generated.
# This limit is enforced per player.
-emergequeue_limit_generate (Per-player limit of queued blocks to generate) int 64
+emergequeue_limit_generate (Per-player limit of queued blocks to generate) int 128
# Number of emerge threads to use.
# Value 0:
@@ -2197,3 +2207,7 @@ contentdb_url (ContentDB URL) string https://content.minetest.net
# These flags are independent from Minetest versions,
# so see a full list at https://content.minetest.net/help/content_flags/
contentdb_flag_blacklist (ContentDB Flag Blacklist) string nonfree, desktop_default
+
+# Maximum number of concurrent downloads. Downloads exceeding this limit will be queued.
+# This should be lower than curl_parallel_limit.
+contentdb_max_concurrent_downloads (ContentDB Max Concurrent Downloads) int 3