aboutsummaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/common/misc_helpers.lua15
-rw-r--r--builtin/common/serialize.lua11
-rw-r--r--builtin/common/tests/serialize_spec.lua12
-rw-r--r--builtin/common/tests/vector_spec.lua142
-rw-r--r--builtin/common/vector.lua93
-rw-r--r--builtin/fstk/dialog.lua19
-rw-r--r--builtin/fstk/ui.lua2
-rw-r--r--builtin/game/auth.lua3
-rw-r--r--builtin/game/chat.lua126
-rw-r--r--builtin/game/constants.lua2
-rw-r--r--builtin/game/deprecated.lua16
-rw-r--r--builtin/game/falling.lua263
-rw-r--r--builtin/game/features.lua1
-rw-r--r--builtin/game/item.lua10
-rw-r--r--builtin/game/item_entity.lua113
-rw-r--r--builtin/game/misc.lua6
-rw-r--r--builtin/game/register.lua2
-rw-r--r--builtin/game/statbars.lua40
-rw-r--r--builtin/init.lua1
-rw-r--r--builtin/mainmenu/async_event.lua (renamed from builtin/common/async_event.lua)10
-rw-r--r--builtin/mainmenu/dlg_config_world.lua95
-rw-r--r--builtin/mainmenu/dlg_contentstore.lua313
-rw-r--r--builtin/mainmenu/dlg_create_world.lua341
-rw-r--r--builtin/mainmenu/init.lua4
-rw-r--r--builtin/mainmenu/tab_credits.lua11
-rw-r--r--builtin/mainmenu/tab_local.lua65
-rw-r--r--builtin/settingtypes.txt102
27 files changed, 1288 insertions, 530 deletions
diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua
index 715f89bc4..e29a9f422 100644
--- a/builtin/common/misc_helpers.lua
+++ b/builtin/common/misc_helpers.lua
@@ -20,6 +20,8 @@ local function basic_dump(o)
-- dump's output is intended for humans.
--elseif tp == "function" then
-- return string.format("loadstring(%q)", string.dump(o))
+ elseif tp == "userdata" then
+ return tostring(o)
else
return string.format("<%s>", tp)
end
@@ -290,7 +292,8 @@ if INIT == "game" then
return
end
local undef = core.registered_nodes[unode.name]
- if undef and undef.on_rightclick then
+ local sneaking = placer and placer:get_player_control().sneak
+ if undef and undef.on_rightclick and not sneaking then
return undef.on_rightclick(pointed_thing.under, unode, placer,
itemstack, pointed_thing)
end
@@ -344,18 +347,12 @@ if INIT == "game" then
--Wrapper for rotate_and_place() to check for sneak and assume Creative mode
--implies infinite stacks when performing a 6d rotation.
--------------------------------------------------------------------------------
- local creative_mode_cache = core.settings:get_bool("creative_mode")
- local function is_creative(name)
- return creative_mode_cache or
- core.check_player_privs(name, {creative = true})
- end
-
core.rotate_node = function(itemstack, placer, pointed_thing)
local name = placer and placer:get_player_name() or ""
local invert_wall = placer and placer:get_player_control().sneak or false
return core.rotate_and_place(itemstack, placer, pointed_thing,
- is_creative(name),
- {invert_wall = invert_wall}, true)
+ core.is_creative_enabled(name),
+ {invert_wall = invert_wall}, true)
end
end
diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua
index 163aa67ad..300b394c6 100644
--- a/builtin/common/serialize.lua
+++ b/builtin/common/serialize.lua
@@ -120,15 +120,8 @@ function core.serialize(x)
elseif tp == "function" then
return string.format("loadstring(%q)", string.dump(x))
elseif tp == "number" then
- -- Serialize integers with string.format to prevent
- -- scientific notation, which doesn't preserve
- -- precision and breaks things like node position
- -- hashes. Serialize floats normally.
- if math.floor(x) == x then
- return string.format("%d", x)
- else
- return tostring(x)
- end
+ -- Serialize numbers reversibly with string.format
+ return string.format("%.17g", x)
elseif tp == "table" then
local vals = {}
local idx_dumped = {}
diff --git a/builtin/common/tests/serialize_spec.lua b/builtin/common/tests/serialize_spec.lua
index c41b0a372..17c6a60f7 100644
--- a/builtin/common/tests/serialize_spec.lua
+++ b/builtin/common/tests/serialize_spec.lua
@@ -18,6 +18,18 @@ describe("serialize", function()
assert.same(test_in, test_out)
end)
+ it("handles precise numbers", function()
+ local test_in = 0.2695949158945771
+ local test_out = core.deserialize(core.serialize(test_in))
+ assert.same(test_in, test_out)
+ end)
+
+ it("handles big integers", function()
+ local test_in = 269594915894577
+ local test_out = core.deserialize(core.serialize(test_in))
+ assert.same(test_in, test_out)
+ end)
+
it("handles recursive structures", function()
local test_in = { hello = "world" }
test_in.foo = test_in
diff --git a/builtin/common/tests/vector_spec.lua b/builtin/common/tests/vector_spec.lua
index 79f032f28..6f308a4a8 100644
--- a/builtin/common/tests/vector_spec.lua
+++ b/builtin/common/tests/vector_spec.lua
@@ -43,4 +43,146 @@ describe("vector", function()
it("add()", function()
assert.same({ x = 2, y = 4, z = 6 }, vector.add(vector.new(1, 2, 3), { x = 1, y = 2, z = 3 }))
end)
+
+ -- This function is needed because of floating point imprecision.
+ local function almost_equal(a, b)
+ if type(a) == "number" then
+ return math.abs(a - b) < 0.00000000001
+ end
+ return vector.distance(a, b) < 0.000000000001
+ end
+
+ describe("rotate_around_axis()", function()
+ it("rotates", function()
+ assert.True(almost_equal({x = -1, y = 0, z = 0},
+ vector.rotate_around_axis({x = 1, y = 0, z = 0}, {x = 0, y = 1, z = 0}, math.pi)))
+ assert.True(almost_equal({x = 0, y = 1, z = 0},
+ vector.rotate_around_axis({x = 0, y = 0, z = 1}, {x = 1, y = 0, z = 0}, math.pi / 2)))
+ assert.True(almost_equal({x = 4, y = 1, z = 1},
+ vector.rotate_around_axis({x = 4, y = 1, z = 1}, {x = 4, y = 1, z = 1}, math.pi / 6)))
+ end)
+ it("keeps distance to axis", function()
+ local rotate1 = {x = 1, y = 3, z = 1}
+ local axis1 = {x = 1, y = 3, z = 2}
+ local rotated1 = vector.rotate_around_axis(rotate1, axis1, math.pi / 13)
+ assert.True(almost_equal(vector.distance(axis1, rotate1), vector.distance(axis1, rotated1)))
+ local rotate2 = {x = 1, y = 1, z = 3}
+ local axis2 = {x = 2, y = 6, z = 100}
+ local rotated2 = vector.rotate_around_axis(rotate2, axis2, math.pi / 23)
+ assert.True(almost_equal(vector.distance(axis2, rotate2), vector.distance(axis2, rotated2)))
+ local rotate3 = {x = 1, y = -1, z = 3}
+ local axis3 = {x = 2, y = 6, z = 100}
+ local rotated3 = vector.rotate_around_axis(rotate3, axis3, math.pi / 2)
+ assert.True(almost_equal(vector.distance(axis3, rotate3), vector.distance(axis3, rotated3)))
+ end)
+ it("rotates back", function()
+ local rotate1 = {x = 1, y = 3, z = 1}
+ local axis1 = {x = 1, y = 3, z = 2}
+ local rotated1 = vector.rotate_around_axis(rotate1, axis1, math.pi / 13)
+ rotated1 = vector.rotate_around_axis(rotated1, axis1, -math.pi / 13)
+ assert.True(almost_equal(rotate1, rotated1))
+ local rotate2 = {x = 1, y = 1, z = 3}
+ local axis2 = {x = 2, y = 6, z = 100}
+ local rotated2 = vector.rotate_around_axis(rotate2, axis2, math.pi / 23)
+ rotated2 = vector.rotate_around_axis(rotated2, axis2, -math.pi / 23)
+ assert.True(almost_equal(rotate2, rotated2))
+ local rotate3 = {x = 1, y = -1, z = 3}
+ local axis3 = {x = 2, y = 6, z = 100}
+ local rotated3 = vector.rotate_around_axis(rotate3, axis3, math.pi / 2)
+ rotated3 = vector.rotate_around_axis(rotated3, axis3, -math.pi / 2)
+ assert.True(almost_equal(rotate3, rotated3))
+ end)
+ it("is right handed", function()
+ local v_before1 = {x = 0, y = 1, z = -1}
+ local v_after1 = vector.rotate_around_axis(v_before1, {x = 1, y = 0, z = 0}, math.pi / 4)
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after1, v_before1)), {x = 1, y = 0, z = 0}))
+
+ local v_before2 = {x = 0, y = 3, z = 4}
+ local v_after2 = vector.rotate_around_axis(v_before2, {x = 1, y = 0, z = 0}, 2 * math.pi / 5)
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after2, v_before2)), {x = 1, y = 0, z = 0}))
+
+ local v_before3 = {x = 1, y = 0, z = -1}
+ local v_after3 = vector.rotate_around_axis(v_before3, {x = 0, y = 1, z = 0}, math.pi / 4)
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after3, v_before3)), {x = 0, y = 1, z = 0}))
+
+ local v_before4 = {x = 3, y = 0, z = 4}
+ local v_after4 = vector.rotate_around_axis(v_before4, {x = 0, y = 1, z = 0}, 2 * math.pi / 5)
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after4, v_before4)), {x = 0, y = 1, z = 0}))
+
+ local v_before5 = {x = 1, y = -1, z = 0}
+ local v_after5 = vector.rotate_around_axis(v_before5, {x = 0, y = 0, z = 1}, math.pi / 4)
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after5, v_before5)), {x = 0, y = 0, z = 1}))
+
+ local v_before6 = {x = 3, y = 4, z = 0}
+ local v_after6 = vector.rotate_around_axis(v_before6, {x = 0, y = 0, z = 1}, 2 * math.pi / 5)
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after6, v_before6)), {x = 0, y = 0, z = 1}))
+ end)
+ end)
+
+ describe("rotate()", function()
+ it("rotates", function()
+ assert.True(almost_equal({x = -1, y = 0, z = 0},
+ vector.rotate({x = 1, y = 0, z = 0}, {x = 0, y = math.pi, z = 0})))
+ assert.True(almost_equal({x = 0, y = -1, z = 0},
+ vector.rotate({x = 1, y = 0, z = 0}, {x = 0, y = 0, z = math.pi / 2})))
+ assert.True(almost_equal({x = 1, y = 0, z = 0},
+ vector.rotate({x = 1, y = 0, z = 0}, {x = math.pi / 123, y = 0, z = 0})))
+ end)
+ it("is counterclockwise", function()
+ local v_before1 = {x = 0, y = 1, z = -1}
+ local v_after1 = vector.rotate(v_before1, {x = math.pi / 4, y = 0, z = 0})
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after1, v_before1)), {x = 1, y = 0, z = 0}))
+
+ local v_before2 = {x = 0, y = 3, z = 4}
+ local v_after2 = vector.rotate(v_before2, {x = 2 * math.pi / 5, y = 0, z = 0})
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after2, v_before2)), {x = 1, y = 0, z = 0}))
+
+ local v_before3 = {x = 1, y = 0, z = -1}
+ local v_after3 = vector.rotate(v_before3, {x = 0, y = math.pi / 4, z = 0})
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after3, v_before3)), {x = 0, y = 1, z = 0}))
+
+ local v_before4 = {x = 3, y = 0, z = 4}
+ local v_after4 = vector.rotate(v_before4, {x = 0, y = 2 * math.pi / 5, z = 0})
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after4, v_before4)), {x = 0, y = 1, z = 0}))
+
+ local v_before5 = {x = 1, y = -1, z = 0}
+ local v_after5 = vector.rotate(v_before5, {x = 0, y = 0, z = math.pi / 4})
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after5, v_before5)), {x = 0, y = 0, z = 1}))
+
+ local v_before6 = {x = 3, y = 4, z = 0}
+ local v_after6 = vector.rotate(v_before6, {x = 0, y = 0, z = 2 * math.pi / 5})
+ assert.True(almost_equal(vector.normalize(vector.cross(v_after6, v_before6)), {x = 0, y = 0, z = 1}))
+ end)
+ end)
+
+ it("dir_to_rotation()", function()
+ -- Comparing rotations (pitch, yaw, roll) is hard because of certain ambiguities,
+ -- e.g. (pi, 0, pi) looks exactly the same as (0, pi, 0)
+ -- So instead we convert the rotation back to vectors and compare these.
+ local function forward_at_rot(rot)
+ return vector.rotate(vector.new(0, 0, 1), rot)
+ end
+ local function up_at_rot(rot)
+ return vector.rotate(vector.new(0, 1, 0), rot)
+ end
+ local rot1 = vector.dir_to_rotation({x = 1, y = 0, z = 0}, {x = 0, y = 1, z = 0})
+ assert.True(almost_equal({x = 1, y = 0, z = 0}, forward_at_rot(rot1)))
+ assert.True(almost_equal({x = 0, y = 1, z = 0}, up_at_rot(rot1)))
+ local rot2 = vector.dir_to_rotation({x = 1, y = 1, z = 0}, {x = 0, y = 0, z = 1})
+ assert.True(almost_equal({x = 1/math.sqrt(2), y = 1/math.sqrt(2), z = 0}, forward_at_rot(rot2)))
+ assert.True(almost_equal({x = 0, y = 0, z = 1}, up_at_rot(rot2)))
+ for i = 1, 1000 do
+ local rand_vec = vector.new(math.random(), math.random(), math.random())
+ if vector.length(rand_vec) ~= 0 then
+ local rot_1 = vector.dir_to_rotation(rand_vec)
+ local rot_2 = {
+ x = math.atan2(rand_vec.y, math.sqrt(rand_vec.z * rand_vec.z + rand_vec.x * rand_vec.x)),
+ y = -math.atan2(rand_vec.x, rand_vec.z),
+ z = 0
+ }
+ assert.True(almost_equal(rot_1, rot_2))
+ end
+ end
+
+ end)
end)
diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua
index ca6541eb4..1fd784ce2 100644
--- a/builtin/common/vector.lua
+++ b/builtin/common/vector.lua
@@ -141,3 +141,96 @@ 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)}
end
+
+local function sin(x)
+ if x % math.pi == 0 then
+ return 0
+ else
+ return math.sin(x)
+ end
+end
+
+local function cos(x)
+ if x % math.pi == math.pi / 2 then
+ return 0
+ else
+ return math.cos(x)
+ end
+end
+
+function vector.rotate_around_axis(v, axis, angle)
+ local cosangle = cos(angle)
+ local sinangle = sin(angle)
+ axis = vector.normalize(axis)
+ -- https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
+ local dot_axis = vector.multiply(axis, vector.dot(axis, v))
+ local cross = vector.cross(v, axis)
+ return vector.new(
+ cross.x * sinangle + (v.x - dot_axis.x) * cosangle + dot_axis.x,
+ cross.y * sinangle + (v.y - dot_axis.y) * cosangle + dot_axis.y,
+ cross.z * sinangle + (v.z - dot_axis.z) * cosangle + dot_axis.z
+ )
+end
+
+function vector.rotate(v, rot)
+ local sinpitch = sin(-rot.x)
+ local sinyaw = sin(-rot.y)
+ local sinroll = sin(-rot.z)
+ local cospitch = cos(rot.x)
+ local cosyaw = cos(rot.y)
+ local cosroll = math.cos(rot.z)
+ -- Rotation matrix that applies yaw, pitch and roll
+ local matrix = {
+ {
+ sinyaw * sinpitch * sinroll + cosyaw * cosroll,
+ sinyaw * sinpitch * cosroll - cosyaw * sinroll,
+ sinyaw * cospitch,
+ },
+ {
+ cospitch * sinroll,
+ cospitch * cosroll,
+ -sinpitch,
+ },
+ {
+ cosyaw * sinpitch * sinroll - sinyaw * cosroll,
+ cosyaw * sinpitch * cosroll + sinyaw * sinroll,
+ cosyaw * cospitch,
+ },
+ }
+ -- Compute matrix multiplication: `matrix` * `v`
+ return vector.new(
+ matrix[1][1] * v.x + matrix[1][2] * v.y + matrix[1][3] * v.z,
+ matrix[2][1] * v.x + matrix[2][2] * v.y + matrix[2][3] * v.z,
+ matrix[3][1] * v.x + matrix[3][2] * v.y + matrix[3][3] * v.z
+ )
+end
+
+function vector.dir_to_rotation(forward, up)
+ forward = vector.normalize(forward)
+ local rot = {x = math.asin(forward.y), y = -math.atan2(forward.x, forward.z), z = 0}
+ if not up then
+ return rot
+ end
+ assert(vector.dot(forward, up) < 0.000001,
+ "Invalid vectors passed to vector.dir_to_rotation().")
+ up = vector.normalize(up)
+ -- Calculate vector pointing up with roll = 0, just based on forward vector.
+ local forwup = vector.rotate({x = 0, y = 1, z = 0}, rot)
+ -- 'forwup' and 'up' are now in a plane with 'forward' as normal.
+ -- The angle between them is the absolute of the roll value we're looking for.
+ rot.z = vector.angle(forwup, up)
+
+ -- Since vector.angle never returns a negative value or a value greater
+ -- than math.pi, rot.z has to be inverted sometimes.
+ -- To determine wether this is the case, we rotate the up vector back around
+ -- the forward vector and check if it worked out.
+ local back = vector.rotate_around_axis(up, forward, -rot.z)
+
+ -- We don't use vector.equals for this because of floating point imprecision.
+ if (back.x - forwup.x) * (back.x - forwup.x) +
+ (back.y - forwup.y) * (back.y - forwup.y) +
+ (back.z - forwup.z) * (back.z - forwup.z) > 0.0000001 then
+ rot.z = -rot.z
+ end
+ return rot
+end
diff --git a/builtin/fstk/dialog.lua b/builtin/fstk/dialog.lua
index df887f413..ea57df1d2 100644
--- a/builtin/fstk/dialog.lua
+++ b/builtin/fstk/dialog.lua
@@ -67,3 +67,22 @@ function dialog_create(name,get_formspec,buttonhandler,eventhandler)
ui.add(self)
return self
end
+
+function messagebox(name, message)
+ return dialog_create(name,
+ function()
+ return ([[
+ formspec_version[3]
+ size[8,3]
+ textarea[0.375,0.375;7.25,1.2;;;%s]
+ button[3,1.825;2,0.8;ok;%s]
+ ]]):format(message, fgettext("OK"))
+ end,
+ function(this, fields)
+ if fields.ok then
+ this:delete()
+ return true
+ end
+ end,
+ nil)
+end
diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua
index 884100543..6d26aabf0 100644
--- a/builtin/fstk/ui.lua
+++ b/builtin/fstk/ui.lua
@@ -85,7 +85,7 @@ function ui.update()
"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") .. "]"
+ "button[5,6.6;4,1;btn_error_confirm;" .. fgettext("OK") .. "]"
}
else
local active_toplevel_ui_elements = 0
diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua
index 7aedfc82e..fc061666c 100644
--- a/builtin/game/auth.lua
+++ b/builtin/game/auth.lua
@@ -41,7 +41,6 @@ core.builtin_auth_handler = {
return {
password = auth_entry.password,
privileges = privileges,
- -- Is set to nil if unknown
last_login = auth_entry.last_login,
}
end,
@@ -53,7 +52,7 @@ core.builtin_auth_handler = {
name = name,
password = password,
privileges = core.string_to_privs(core.settings:get("default_privs")),
- last_login = os.time(),
+ last_login = -1, -- Defer login time calculation until record_login (called by on_joinplayer)
})
end,
delete_auth = function(name)
diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua
index fd1379162..aae811794 100644
--- a/builtin/game/chat.lua
+++ b/builtin/game/chat.lua
@@ -239,57 +239,76 @@ core.register_chatcommand("grantme", {
end,
})
+local function handle_revoke_command(caller, revokename, revokeprivstr)
+ local caller_privs = core.get_player_privs(caller)
+ if not (caller_privs.privs or caller_privs.basic_privs) then
+ return false, "Your privileges are insufficient."
+ end
+
+ if not core.get_auth_handler().get_auth(revokename) then
+ return false, "Player " .. revokename .. " does not exist."
+ end
+
+ local revokeprivs = core.string_to_privs(revokeprivstr)
+ local privs = core.get_player_privs(revokename)
+ local basic_privs =
+ core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
+ for priv, _ in pairs(revokeprivs) do
+ if not basic_privs[priv] and not caller_privs.privs then
+ return false, "Your privileges are insufficient."
+ end
+ end
+
+ if revokeprivstr == "all" then
+ revokeprivs = privs
+ privs = {}
+ else
+ for priv, _ in pairs(revokeprivs) do
+ privs[priv] = nil
+ end
+ end
+
+ for priv, _ in pairs(revokeprivs) do
+ -- call the on_revoke callbacks
+ core.run_priv_callbacks(revokename, priv, caller, "revoke")
+ end
+
+ core.set_player_privs(revokename, privs)
+ core.log("action", caller..' revoked ('
+ ..core.privs_to_string(revokeprivs, ', ')
+ ..') privileges from '..revokename)
+ if revokename ~= caller then
+ core.chat_send_player(revokename, caller
+ .. " revoked privileges from you: "
+ .. core.privs_to_string(revokeprivs, ' '))
+ end
+ return true, "Privileges of " .. revokename .. ": "
+ .. core.privs_to_string(
+ core.get_player_privs(revokename), ' ')
+end
+
core.register_chatcommand("revoke", {
params = "<name> (<privilege> | all)",
description = "Remove privileges from player",
privs = {},
func = function(name, param)
- if not core.check_player_privs(name, {privs=true}) and
- not core.check_player_privs(name, {basic_privs=true}) then
- return false, "Your privileges are insufficient."
- end
- local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
- if not revoke_name or not revoke_priv_str then
+ local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)")
+ if not revokename or not revokeprivstr then
return false, "Invalid parameters (see /help revoke)"
- elseif not core.get_auth_handler().get_auth(revoke_name) then
- return false, "Player " .. revoke_name .. " does not exist."
- end
- local revoke_privs = core.string_to_privs(revoke_priv_str)
- local privs = core.get_player_privs(revoke_name)
- local basic_privs =
- core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
- for priv, _ in pairs(revoke_privs) do
- if not basic_privs[priv] and
- not core.check_player_privs(name, {privs=true}) then
- return false, "Your privileges are insufficient."
- end
- end
- if revoke_priv_str == "all" then
- revoke_privs = privs
- privs = {}
- else
- for priv, _ in pairs(revoke_privs) do
- privs[priv] = nil
- end
- end
-
- for priv, _ in pairs(revoke_privs) do
- -- call the on_revoke callbacks
- core.run_priv_callbacks(revoke_name, priv, name, "revoke")
end
+ return handle_revoke_command(name, revokename, revokeprivstr)
+ end,
+})
- core.set_player_privs(revoke_name, privs)
- core.log("action", name..' revoked ('
- ..core.privs_to_string(revoke_privs, ', ')
- ..') privileges from '..revoke_name)
- if revoke_name ~= name then
- core.chat_send_player(revoke_name, name
- .. " revoked privileges from you: "
- .. core.privs_to_string(revoke_privs, ' '))
+core.register_chatcommand("revokeme", {
+ params = "<privilege> | all",
+ description = "Revoke privileges from yourself",
+ privs = {},
+ func = function(name, param)
+ if param == "" then
+ return false, "Invalid parameters (see /help revokeme)"
end
- return true, "Privileges of " .. revoke_name .. ": "
- .. core.privs_to_string(
- core.get_player_privs(revoke_name), ' ')
+ return handle_revoke_command(name, name, param)
end,
})
@@ -424,6 +443,9 @@ core.register_chatcommand("teleport", {
end
local teleportee = core.get_player_by_name(name)
if teleportee then
+ if teleportee:get_attach() then
+ return false, "Can't teleport, you're attached to an object!"
+ end
teleportee:set_pos(p)
return true, "Teleporting to "..core.pos_to_string(p)
end
@@ -441,6 +463,9 @@ core.register_chatcommand("teleport", {
end
if teleportee and p then
+ if teleportee:get_attach() then
+ return false, "Can't teleport, you're attached to an object!"
+ end
p = find_free_position_near(p)
teleportee:set_pos(p)
return true, "Teleporting to " .. target_name
@@ -461,6 +486,9 @@ core.register_chatcommand("teleport", {
teleportee = core.get_player_by_name(teleportee_name)
end
if teleportee and p.x and p.y and p.z then
+ if teleportee:get_attach() then
+ return false, "Can't teleport, player is attached to an object!"
+ end
teleportee:set_pos(p)
return true, "Teleporting " .. teleportee_name
.. " to " .. core.pos_to_string(p)
@@ -479,6 +507,9 @@ core.register_chatcommand("teleport", {
end
end
if teleportee and p then
+ if teleportee:get_attach() then
+ return false, "Can't teleport, player is attached to an object!"
+ end
p = find_free_position_near(p)
teleportee:set_pos(p)
return true, "Teleporting " .. teleportee_name
@@ -717,8 +748,9 @@ core.register_chatcommand("spawnentity", {
end
end
p.y = p.y + 1
- core.add_entity(p, entityname)
- return true, ("%q spawned."):format(entityname)
+ local obj = core.add_entity(p, entityname)
+ local msg = obj and "%q spawned." or "%q failed to spawn."
+ return true, msg:format(entityname)
end,
})
@@ -757,7 +789,7 @@ core.register_chatcommand("rollback_check", {
params = "[<range>] [<seconds>] [<limit>]",
description = "Check who last touched a node or a node near it"
.. " within the time specified by <seconds>. Default: range = 0,"
- .. " seconds = 86400 = 24h, limit = 5",
+ .. " seconds = 86400 = 24h, limit = 5. Set <seconds> to inf for no time limit",
privs = {rollback=true},
func = function(name, param)
if not core.settings:get_bool("enable_rollback_recording") then
@@ -808,7 +840,7 @@ core.register_chatcommand("rollback_check", {
core.register_chatcommand("rollback", {
params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
- description = "Revert actions of a player. Default for <seconds> is 60",
+ description = "Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit",
privs = {rollback=true},
func = function(name, param)
if not core.settings:get_bool("enable_rollback_recording") then
@@ -1036,7 +1068,7 @@ core.register_chatcommand("last-login", {
param = name
end
local pauth = core.get_auth_handler().get_auth(param)
- if pauth and pauth.last_login then
+ if pauth and pauth.last_login and pauth.last_login ~= -1 then
-- Time in UTC, ISO 8601 format
return true, "Last login time was " ..
os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
diff --git a/builtin/game/constants.lua b/builtin/game/constants.lua
index 0ee2a7237..54eeea50f 100644
--- a/builtin/game/constants.lua
+++ b/builtin/game/constants.lua
@@ -24,7 +24,7 @@ core.MAP_BLOCKSIZE = 16
-- Default maximal HP of a player
core.PLAYER_MAX_HP_DEFAULT = 20
-- Default maximal breath of a player
-core.PLAYER_MAX_BREATH_DEFAULT = 11
+core.PLAYER_MAX_BREATH_DEFAULT = 10
-- light.h
-- Maximum value for node 'light_source' parameter
diff --git a/builtin/game/deprecated.lua b/builtin/game/deprecated.lua
index 73e105eb8..20f0482eb 100644
--- a/builtin/game/deprecated.lua
+++ b/builtin/game/deprecated.lua
@@ -70,3 +70,19 @@ core.setting_get = setting_proxy("get")
core.setting_setbool = setting_proxy("set_bool")
core.setting_getbool = setting_proxy("get_bool")
core.setting_save = setting_proxy("write")
+
+--
+-- core.register_on_auth_fail
+--
+
+function core.register_on_auth_fail(func)
+ core.log("deprecated", "core.register_on_auth_fail " ..
+ "is obsolete and should be replaced by " ..
+ "core.register_on_authplayer instead.")
+
+ core.register_on_authplayer(function (player_name, ip, is_success)
+ if not is_success then
+ func(player_name, ip)
+ end
+ end)
+end
diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua
index ea02e3694..714506a5f 100644
--- a/builtin/game/falling.lua
+++ b/builtin/game/falling.lua
@@ -30,6 +30,8 @@ local facedir_to_euler = {
{y = math.pi/2, x = math.pi, z = 0}
}
+local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
+
--
-- Falling stuff
--
@@ -41,12 +43,13 @@ core.register_entity(":__builtin:falling_node", {
textures = {},
physical = true,
is_visible = false,
- collide_with_objects = false,
+ collide_with_objects = true,
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
},
node = {},
meta = {},
+ floats = false,
set_node = function(self, node, meta)
self.node = node
@@ -71,6 +74,11 @@ core.register_entity(":__builtin:falling_node", {
return
end
self.meta = meta
+
+ -- Cache whether we're supposed to float on water
+ self.floats = core.get_item_group(node.name, "float") ~= 0
+
+ -- Set entity visuals
if def.drawtype == "torchlike" or def.drawtype == "signlike" then
local textures
if def.tiles and def.tiles[1] then
@@ -101,6 +109,7 @@ core.register_entity(":__builtin:falling_node", {
if core.is_colored_paramtype(def.paramtype2) then
itemstring = core.itemstring_with_palette(itemstring, node.param2)
end
+ -- FIXME: solution needed for paramtype2 == "leveled"
local vsize
if def.visual_scale then
local s = def.visual_scale * SCALE
@@ -113,6 +122,25 @@ core.register_entity(":__builtin:falling_node", {
glow = def.light_source,
})
end
+
+ -- 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
+ 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
+ end
+ if box then
+ if def.paramtype2 == "leveled" and (self.node.level or 0) > 0 then
+ box[5] = -0.5 + self.node.level / 64
+ end
+ self.object:set_properties({
+ collisionbox = box
+ })
+ end
+ end
+
-- Rotate entity
if def.drawtype == "torchlike" then
self.object:set_yaw(math.pi*0.25)
@@ -172,6 +200,7 @@ core.register_entity(":__builtin:falling_node", {
on_activate = function(self, staticdata)
self.object:set_armor_groups({immortal = 1})
+ self.object:set_acceleration({x = 0, y = -gravity, z = 0})
local ds = core.deserialize(staticdata)
if ds and ds.node then
@@ -183,85 +212,159 @@ core.register_entity(":__builtin:falling_node", {
end
end,
- on_step = function(self, dtime)
- -- Set gravity
- local acceleration = self.object:get_acceleration()
- if not vector.equals(acceleration, {x = 0, y = -10, z = 0}) then
- self.object:set_acceleration({x = 0, y = -10, z = 0})
+ try_place = function(self, bcp, bcn)
+ local bcd = core.registered_nodes[bcn.name]
+ -- Add levels if dropped on same leveled node
+ if bcd and bcd.paramtype2 == "leveled" and
+ bcn.name == self.node.name then
+ local addlevel = self.node.level
+ if (addlevel or 0) <= 0 then
+ addlevel = bcd.leveled
+ end
+ if core.add_node_level(bcp, addlevel) < addlevel then
+ return true
+ elseif bcd.buildable_to then
+ -- Node level has already reached max, don't place anything
+ return true
+ end
end
- -- Turn to actual node when colliding with ground, or continue to move
- local pos = self.object:get_pos()
- -- Position of bottom center point
- local bcp = {x = pos.x, y = pos.y - 0.7, z = pos.z}
- -- 'bcn' is nil for unloaded nodes
- local bcn = core.get_node_or_nil(bcp)
- -- Delete on contact with ignore at world edges
- if bcn and bcn.name == "ignore" then
- self.object:remove()
- return
+
+ -- Decide if we're replacing the node or placing on top
+ local np = vector.new(bcp)
+ if bcd and bcd.buildable_to and
+ (not self.floats or bcd.liquidtype == "none") then
+ core.remove_node(bcp)
+ else
+ np.y = np.y + 1
end
- local bcd = bcn and core.registered_nodes[bcn.name]
- if bcn and
- (not bcd or bcd.walkable or
- (core.get_item_group(self.node.name, "float") ~= 0 and
- bcd.liquidtype ~= "none")) then
- if bcd and bcd.leveled and
- bcn.name == self.node.name then
- local addlevel = self.node.level
- if not addlevel or addlevel <= 0 then
- addlevel = bcd.leveled
+
+ -- Check what's here
+ local n2 = core.get_node(np)
+ local nd = core.registered_nodes[n2.name]
+ -- If it's not air or liquid, remove node and replace it with
+ -- it's drops
+ if n2.name ~= "air" and (not nd or nd.liquidtype == "none") then
+ if nd and nd.buildable_to == false then
+ nd.on_dig(np, n2, nil)
+ -- If it's still there, it might be protected
+ if core.get_node(np).name == n2.name then
+ return false
end
- if core.add_node_level(bcp, addlevel) == 0 then
+ else
+ core.remove_node(np)
+ end
+ end
+
+ -- Create node
+ local def = core.registered_nodes[self.node.name]
+ if def then
+ core.add_node(np, self.node)
+ if self.meta then
+ core.get_meta(np):from_table(self.meta)
+ end
+ if def.sounds and def.sounds.place then
+ core.sound_play(def.sounds.place, {pos = np}, true)
+ end
+ end
+ core.check_for_falling(np)
+ return true
+ end,
+
+ on_step = function(self, dtime, moveresult)
+ -- Fallback code since collision detection can't tell us
+ -- about liquids (which do not collide)
+ if self.floats then
+ local pos = self.object:get_pos()
+
+ local bcp = vector.round({x = pos.x, y = pos.y - 0.7, z = pos.z})
+ local bcn = core.get_node(bcp)
+
+ local bcd = core.registered_nodes[bcn.name]
+ if bcd and bcd.liquidtype ~= "none" then
+ if self:try_place(bcp, bcn) then
self.object:remove()
return
end
- elseif bcd and bcd.buildable_to and
- (core.get_item_group(self.node.name, "float") == 0 or
- bcd.liquidtype == "none") then
- core.remove_node(bcp)
- return
end
- local np = {x = bcp.x, y = bcp.y + 1, z = bcp.z}
- -- Check what's here
- local n2 = core.get_node(np)
- local nd = core.registered_nodes[n2.name]
- -- If it's not air or liquid, remove node and replace it with
- -- it's drops
- if n2.name ~= "air" and (not nd or nd.liquidtype == "none") then
- core.remove_node(np)
- if nd and nd.buildable_to == false then
- -- Add dropped items
- local drops = core.get_node_drops(n2, "")
- for _, dropped_item in pairs(drops) do
- core.add_item(np, dropped_item)
+ end
+
+ assert(moveresult)
+ if not moveresult.collides then
+ return -- Nothing to do :)
+ end
+
+ local bcp, bcn
+ local player_collision
+ if moveresult.touching_ground then
+ for _, info in ipairs(moveresult.collisions) do
+ if info.type == "object" then
+ if info.axis == "y" and info.object:is_player() then
+ player_collision = info
end
- end
- -- Run script hook
- for _, callback in pairs(core.registered_on_dignodes) do
- callback(np, n2)
+ elseif info.axis == "y" then
+ bcp = info.node_pos
+ bcn = core.get_node(bcp)
+ break
end
end
- -- Create node and remove entity
- local def = core.registered_nodes[self.node.name]
- if def then
- core.add_node(np, self.node)
- if self.meta then
- local meta = core.get_meta(np)
- meta:from_table(self.meta)
- end
- if def.sounds and def.sounds.place then
- core.sound_play(def.sounds.place, {pos = np}, true)
- end
+ end
+
+ if not bcp then
+ -- We're colliding with something, but not the ground. Irrelevant to us.
+ if player_collision then
+ -- Continue falling through players by moving a little into
+ -- their collision box
+ -- TODO: this hack could be avoided in the future if objects
+ -- could choose who to collide with
+ local vel = self.object:get_velocity()
+ self.object:set_velocity({
+ x = vel.x,
+ y = player_collision.old_velocity.y,
+ z = vel.z
+ })
+ self.object:set_pos(vector.add(self.object:get_pos(),
+ {x = 0, y = -0.5, z = 0}))
end
+ return
+ elseif bcn.name == "ignore" then
+ -- Delete on contact with ignore at world edges
self.object:remove()
- core.check_for_falling(np)
return
end
- local vel = self.object:get_velocity()
- if vector.equals(vel, {x = 0, y = 0, z = 0}) then
- local npos = self.object:get_pos()
- self.object:set_pos(vector.round(npos))
+
+ local failure = false
+
+ local pos = self.object:get_pos()
+ local distance = vector.apply(vector.subtract(pos, bcp), math.abs)
+ if distance.x >= 1 or distance.z >= 1 then
+ -- We're colliding with some part of a node that's sticking out
+ -- Since we don't want to visually teleport, drop as item
+ failure = true
+ elseif distance.y >= 2 then
+ -- Doors consist of a hidden top node and a bottom node that is
+ -- the actual door. Despite the top node being solid, the moveresult
+ -- almost always indicates collision with the bottom node.
+ -- Compensate for this by checking the top node
+ bcp.y = bcp.y + 1
+ bcn = core.get_node(bcp)
+ local def = core.registered_nodes[bcn.name]
+ if not (def and def.walkable) then
+ failure = true -- This is unexpected, fail
+ end
+ end
+
+ -- Try to actually place ourselves
+ if not failure then
+ failure = not self:try_place(bcp, bcn)
+ end
+
+ if failure then
+ local drops = core.get_node_drops(self.node, "")
+ for _, item in pairs(drops) do
+ core.add_item(pos, item)
+ end
end
+ self.object:remove()
end
})
@@ -270,6 +373,7 @@ local function convert_to_falling_node(pos, node)
if not obj then
return false
end
+ -- remember node level, the entities' set_node() uses this
node.level = core.get_node_level(pos)
local meta = core.get_meta(pos)
local metatable = meta and meta:to_table() or {}
@@ -355,18 +459,23 @@ function core.check_single_for_falling(p)
-- Only spawn falling node if node below is loaded
local n_bottom = core.get_node_or_nil(p_bottom)
local d_bottom = n_bottom and core.registered_nodes[n_bottom.name]
- if d_bottom and
-
- (core.get_item_group(n.name, "float") == 0 or
- d_bottom.liquidtype == "none") and
-
- (n.name ~= n_bottom.name or (d_bottom.leveled and
- core.get_node_level(p_bottom) <
- core.get_node_max_level(p_bottom))) and
-
- (not d_bottom.walkable or d_bottom.buildable_to) then
- convert_to_falling_node(p, n)
- return true
+ if d_bottom then
+ local same = n.name == n_bottom.name
+ -- Let leveled nodes fall if it can merge with the bottom node
+ if same and d_bottom.paramtype2 == "leveled" and
+ core.get_node_level(p_bottom) <
+ core.get_node_max_level(p_bottom) then
+ convert_to_falling_node(p, n)
+ return true
+ end
+ -- Otherwise only if the bottom node is considered "fall through"
+ if not same and
+ (not d_bottom.walkable or d_bottom.buildable_to) and
+ (core.get_item_group(n.name, "float") == 0 or
+ d_bottom.liquidtype == "none") then
+ convert_to_falling_node(p, n)
+ return true
+ end
end
end
diff --git a/builtin/game/features.lua b/builtin/game/features.lua
index 623f8183b..a15475333 100644
--- a/builtin/game/features.lua
+++ b/builtin/game/features.lua
@@ -16,6 +16,7 @@ core.features = {
formspec_version_element = true,
area_store_persistent_ids = true,
pathfinder_works = true,
+ object_step_has_moveresult = true,
}
function core.has_feature(arg)
diff --git a/builtin/game/item.lua b/builtin/game/item.lua
index 513c3a5e1..f680ce0d4 100644
--- a/builtin/game/item.lua
+++ b/builtin/game/item.lua
@@ -582,7 +582,7 @@ function core.node_dig(pos, node, digger)
wielded = wdef.after_use(wielded, digger, node, dp) or wielded
else
-- Wear out tool
- if not core.settings:get_bool("creative_mode") then
+ if not core.is_creative_enabled(diggername) then
wielded:add_wear(dp.wear)
if wielded:get_count() == 0 and wdef.sound and wdef.sound.breaks then
core.sound_play(wdef.sound.breaks, {
@@ -675,6 +675,8 @@ end
-- Item definition defaults
--
+local default_stack_max = tonumber(minetest.settings:get("default_stack_max")) or 99
+
core.nodedef_default = {
-- Item properties
type="node",
@@ -684,7 +686,7 @@ core.nodedef_default = {
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
- stack_max = 99,
+ stack_max = default_stack_max,
usable = false,
liquids_pointable = false,
tool_capabilities = nil,
@@ -748,7 +750,7 @@ core.craftitemdef_default = {
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
- stack_max = 99,
+ stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
@@ -786,7 +788,7 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
- stack_max = 99,
+ stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua
index 968daac97..20dd18044 100644
--- a/builtin/game/item_entity.lua
+++ b/builtin/game/item_entity.lua
@@ -27,14 +27,11 @@ core.register_entity(":__builtin:item", {
visual = "wielditem",
visual_size = {x = 0.4, y = 0.4},
textures = {""},
- spritediv = {x = 1, y = 1},
- initial_sprite_basepos = {x = 0, y = 0},
is_visible = false,
},
itemstring = "",
moving_state = true,
- slippery_state = false,
physical_state = true,
-- Item expiry
age = 0,
@@ -57,7 +54,6 @@ 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 coll_height = size * 0.75
local def = core.registered_nodes[itemname]
local glow = def and math.floor(def.light_source / 2 + 0.5)
@@ -66,9 +62,7 @@ core.register_entity(":__builtin:item", {
visual = "wielditem",
textures = {itemname},
visual_size = {x = size, y = size},
- collisionbox = {-size, -coll_height, -size,
- size, coll_height, size},
- selectionbox = {-size, -size, -size, size, size, size},
+ collisionbox = {-size, -size, -size, size, size, size},
automatic_rotate = math.pi * 0.5 * 0.2 / size,
wield_item = self.itemstring,
glow = glow,
@@ -157,7 +151,7 @@ core.register_entity(":__builtin:item", {
end
end,
- on_step = function(self, dtime)
+ on_step = function(self, dtime, moveresult)
self.age = self.age + dtime
if time_to_live > 0 and self.age > time_to_live then
self.itemstring = ""
@@ -178,6 +172,38 @@ core.register_entity(":__builtin:item", {
return
end
+ if self.force_out then
+ -- This code runs after the entity got a push from the is_stuck code.
+ -- It makes sure the entity is entirely outside the solid node
+ local c = self.object:get_properties().collisionbox
+ local s = self.force_out_start
+ local f = self.force_out
+ local ok = (f.x > 0 and pos.x + c[1] > s.x + 0.5) or
+ (f.y > 0 and pos.y + c[2] > s.y + 0.5) or
+ (f.z > 0 and pos.z + c[3] > s.z + 0.5) or
+ (f.x < 0 and pos.x + c[4] < s.x - 0.5) or
+ (f.z < 0 and pos.z + c[6] < s.z - 0.5)
+ if ok then
+ -- Item was successfully forced out
+ self.force_out = nil
+ self:enable_physics()
+ return
+ end
+ end
+
+ if not self.physical_state then
+ return -- Don't do anything
+ end
+
+ assert(moveresult,
+ "Collision info missing, this is caused by an out-of-date/buggy mod or game")
+
+ if not moveresult.collides then
+ -- future TODO: items should probably decelerate in air
+ return
+ end
+
+ -- Push item out when stuck inside solid node
local is_stuck = false
local snode = core.get_node_or_nil(pos)
if snode then
@@ -187,7 +213,6 @@ core.register_entity(":__builtin:item", {
and (sdef.node_box == nil or sdef.node_box.type == "regular")
end
- -- Push item out when stuck inside solid node
if is_stuck then
local shootdir
local order = {
@@ -223,69 +248,49 @@ core.register_entity(":__builtin:item", {
self.force_out_start = vector.round(pos)
return
end
- elseif self.force_out then
- -- This code runs after the entity got a push from the above code.
- -- It makes sure the entity is entirely outside the solid node
- local c = self.object:get_properties().collisionbox
- local s = self.force_out_start
- local f = self.force_out
- local ok = (f.x > 0 and pos.x + c[1] > s.x + 0.5) or
- (f.y > 0 and pos.y + c[2] > s.y + 0.5) or
- (f.z > 0 and pos.z + c[3] > s.z + 0.5) or
- (f.x < 0 and pos.x + c[4] < s.x - 0.5) or
- (f.z < 0 and pos.z + c[6] < s.z - 0.5)
- if ok then
- -- Item was successfully forced out
- self.force_out = nil
- self:enable_physics()
- end
end
- if not self.physical_state then
- return -- Don't do anything
+ node = nil -- ground node we're colliding with
+ if moveresult.touching_ground then
+ for _, info in ipairs(moveresult.collisions) do
+ if info.axis == "y" then
+ node = core.get_node(info.node_pos)
+ break
+ end
+ end
end
-- Slide on slippery nodes
- local vel = self.object:get_velocity()
local def = node and core.registered_nodes[node.name]
- local is_moving = (def and not def.walkable) or
- vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
- local is_slippery = false
+ local keep_movement = false
- if def and def.walkable then
+ if def then
local slippery = core.get_item_group(node.name, "slippery")
- is_slippery = slippery ~= 0
- if is_slippery and (math.abs(vel.x) > 0.2 or math.abs(vel.z) > 0.2) then
+ local vel = self.object:get_velocity()
+ if slippery ~= 0 and (math.abs(vel.x) > 0.1 or math.abs(vel.z) > 0.1) then
-- Horizontal deceleration
- local slip_factor = 4.0 / (slippery + 4)
- self.object:set_acceleration({
- x = -vel.x * slip_factor,
+ local factor = math.min(4 / (slippery + 4) * dtime, 1)
+ self.object:set_velocity({
+ x = vel.x * (1 - factor),
y = 0,
- z = -vel.z * slip_factor
+ z = vel.z * (1 - factor)
})
- elseif vel.y == 0 then
- is_moving = false
+ keep_movement = true
end
end
- if self.moving_state == is_moving and
- self.slippery_state == is_slippery then
- -- Do not update anything until the moving state changes
- return
+ if not keep_movement then
+ self.object:set_velocity({x=0, y=0, z=0})
end
- self.moving_state = is_moving
- self.slippery_state = is_slippery
-
- if is_moving then
- self.object:set_acceleration({x = 0, y = -gravity, z = 0})
- else
- self.object:set_acceleration({x = 0, y = 0, z = 0})
- self.object:set_velocity({x = 0, y = 0, z = 0})
+ if self.moving_state == keep_movement then
+ -- Do not update anything until the moving state changes
+ return
end
+ self.moving_state = keep_movement
- --Only collect items if not moving
- if is_moving then
+ -- Only collect items if not moving
+ if self.moving_state then
return
end
-- Collect the items around to merge with
diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua
index 0ed11ada0..341e613c2 100644
--- a/builtin/game/misc.lua
+++ b/builtin/game/misc.lua
@@ -164,6 +164,12 @@ function core.record_protection_violation(pos, name)
end
end
+-- To be overridden by Creative mods
+
+local creative_mode_cache = core.settings:get_bool("creative_mode")
+function core.is_creative_enabled(name)
+ return creative_mode_cache
+end
-- Checks if specified volume intersects a protected volume
diff --git a/builtin/game/register.lua b/builtin/game/register.lua
index eb6c2897c..1034d4f2b 100644
--- a/builtin/game/register.lua
+++ b/builtin/game/register.lua
@@ -607,9 +607,9 @@ core.registered_on_item_eats, core.register_on_item_eat = make_registration()
core.registered_on_punchplayers, core.register_on_punchplayer = make_registration()
core.registered_on_priv_grant, core.register_on_priv_grant = make_registration()
core.registered_on_priv_revoke, core.register_on_priv_revoke = make_registration()
+core.registered_on_authplayers, core.register_on_authplayer = make_registration()
core.registered_can_bypass_userlimit, core.register_can_bypass_userlimit = make_registration()
core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration()
-core.registered_on_auth_fail, core.register_on_auth_fail = 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()
diff --git a/builtin/game/statbars.lua b/builtin/game/statbars.lua
index 46c947b60..d192029c5 100644
--- a/builtin/game/statbars.lua
+++ b/builtin/game/statbars.lua
@@ -3,22 +3,26 @@ local enable_damage = core.settings:get_bool("enable_damage")
local health_bar_definition = {
hud_elem_type = "statbar",
- position = { x=0.5, y=1 },
+ position = {x = 0.5, y = 1},
text = "heart.png",
+ text2 = "heart_gone.png",
number = core.PLAYER_MAX_HP_DEFAULT,
+ item = core.PLAYER_MAX_HP_DEFAULT,
direction = 0,
- size = { x=24, y=24 },
- offset = { x=(-10*24)-25, y=-(48+24+16)},
+ size = {x = 24, y = 24},
+ offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)},
}
local breath_bar_definition = {
hud_elem_type = "statbar",
- position = { x=0.5, y=1 },
+ position = {x = 0.5, y = 1},
text = "bubble.png",
+ text2 = "bubble_gone.png",
number = core.PLAYER_MAX_BREATH_DEFAULT,
+ item = core.PLAYER_MAX_BREATH_DEFAULT * 2,
direction = 0,
- size = { x=24, y=24 },
- offset = {x=25,y=-(48+24+16)},
+ size = {x = 24, y = 24},
+ offset = {x = 25, y= -(48 + 24 + 16)},
}
local hud_ids = {}
@@ -26,7 +30,7 @@ local hud_ids = {}
local function scaleToDefault(player, field)
-- Scale "hp" or "breath" to the default dimensions
local current = player["get_" .. field](player)
- local nominal = core["PLAYER_MAX_".. field:upper() .. "_DEFAULT"]
+ local nominal = core["PLAYER_MAX_" .. field:upper() .. "_DEFAULT"]
local max_display = math.max(nominal,
math.max(player:get_properties()[field .. "_max"], current))
return current / max_display * nominal
@@ -49,6 +53,7 @@ local function update_builtin_statbars(player)
local hud = hud_ids[name]
local immortal = player:get_armor_groups().immortal == 1
+
if flags.healthbar and enable_damage and not immortal then
local number = scaleToDefault(player, "hp")
if hud.id_healthbar == nil then
@@ -63,19 +68,28 @@ local function update_builtin_statbars(player)
hud.id_healthbar = nil
end
+ local show_breathbar = flags.breathbar and enable_damage and not immortal
+
+ local breath = player:get_breath()
local breath_max = player:get_properties().breath_max
- if flags.breathbar and enable_damage and not immortal and
- player:get_breath() < breath_max then
+ if show_breathbar and breath <= breath_max then
local number = 2 * scaleToDefault(player, "breath")
- if hud.id_breathbar == nil then
+ if not hud.id_breathbar and breath < breath_max then
local hud_def = table.copy(breath_bar_definition)
hud_def.number = number
hud.id_breathbar = player:hud_add(hud_def)
- else
+ elseif hud.id_breathbar then
player:hud_change(hud.id_breathbar, "number", number)
end
- elseif hud.id_breathbar then
- player:hud_remove(hud.id_breathbar)
+ 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)
+ if player then
+ player:hud_remove(breath_bar)
+ end
+ end, name, hud.id_breathbar)
hud.id_breathbar = nil
end
end
diff --git a/builtin/init.lua b/builtin/init.lua
index f76174be7..75bb3db85 100644
--- a/builtin/init.lua
+++ b/builtin/init.lua
@@ -36,6 +36,7 @@ dofile(commonpath .. "misc_helpers.lua")
if INIT == "game" then
dofile(gamepath .. "init.lua")
+ assert(not core.get_http_api)
elseif INIT == "mainmenu" then
local mm_script = core.settings:get("main_menu_script")
if mm_script and mm_script ~= "" then
diff --git a/builtin/common/async_event.lua b/builtin/mainmenu/async_event.lua
index 988af79b9..04bfb78d6 100644
--- a/builtin/common/async_event.lua
+++ b/builtin/mainmenu/async_event.lua
@@ -8,15 +8,7 @@ local function handle_job(jobid, serialized_retval)
core.async_jobs[jobid] = nil
end
-if core.register_globalstep then
- core.register_globalstep(function(dtime)
- for i, job in ipairs(core.get_finished_jobs()) do
- handle_job(job.jobid, job.retval)
- end
- end)
-else
- core.async_event_handler = handle_job
-end
+core.async_event_handler = handle_job
function core.handle_async(func, parameter, callback)
-- Serialize function
diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua
index 97218df9c..2cf70c9c9 100644
--- a/builtin/mainmenu/dlg_config_world.lua
+++ b/builtin/mainmenu/dlg_config_world.lua
@@ -23,7 +23,49 @@ local function modname_valid(name)
return not name:find("[^a-z0-9_]")
end
+local function init_data(data)
+ data.list = filterlist.create(
+ pkgmgr.preparemodlist,
+ pkgmgr.comparemod,
+ function(element, uid)
+ if element.name == uid then
+ return true
+ end
+ end,
+ function(element, criteria)
+ if criteria.hide_game and
+ element.is_game_content then
+ return false
+ end
+
+ if criteria.hide_modpackcontents and
+ element.modpack ~= nil then
+ return false
+ end
+ return true
+ end,
+ {
+ worldpath = data.worldspec.path,
+ gameid = data.worldspec.gameid
+ })
+
+ if data.selected_mod > data.list:size() then
+ data.selected_mod = 0
+ end
+
+ data.list:set_filtercriteria({
+ hide_game = data.hide_gamemods,
+ hide_modpackcontents = data.hide_modpackcontents
+ })
+ data.list:add_sort_mechanism("alphabetic", sort_mod_list)
+ data.list:set_sortmode("alphabetic")
+end
+
local function get_formspec(data)
+ if not data.list then
+ init_data(data)
+ end
+
local mod = data.list:get_list()[data.selected_mod] or {name = ""}
local retval =
@@ -85,11 +127,14 @@ local function get_formspec(data)
end
end
end
+
retval = retval ..
"button[3.25,7;2.5,0.5;btn_config_world_save;" ..
fgettext("Save") .. "]" ..
"button[5.75,7;2.5,0.5;btn_config_world_cancel;" ..
- fgettext("Cancel") .. "]"
+ fgettext("Cancel") .. "]" ..
+ "button[9,7;2.5,0.5;btn_config_world_cdb;" ..
+ fgettext("Find More Mods") .. "]"
if mod.name ~= "" and not mod.is_game_content then
if mod.is_modpack then
@@ -198,6 +243,16 @@ local function handle_buttons(this, fields)
return true
end
+ if fields.btn_config_world_cdb then
+ this.data.list = nil
+
+ local dlg = create_store_dlg("mod")
+ dlg:set_parent(this)
+ this:hide()
+ dlg:show()
+ return true
+ end
+
if fields.btn_enable_all_mods then
local list = this.data.list:get_raw_list()
@@ -247,43 +302,5 @@ function create_configure_world_dlg(worldidx)
return
end
- dlg.data.list = filterlist.create(
- pkgmgr.preparemodlist,
- pkgmgr.comparemod,
- function(element, uid)
- if element.name == uid then
- return true
- end
- end,
- function(element, criteria)
- if criteria.hide_game and
- element.is_game_content then
- return false
- end
-
- if criteria.hide_modpackcontents and
- element.modpack ~= nil then
- return false
- end
- return true
- end,
- {
- worldpath = dlg.data.worldspec.path,
- gameid = dlg.data.worldspec.gameid
- }
- )
-
-
- if dlg.data.selected_mod > dlg.data.list:size() then
- dlg.data.selected_mod = 0
- end
-
- dlg.data.list:set_filtercriteria({
- hide_game = dlg.data.hide_gamemods,
- hide_modpackcontents = dlg.data.hide_modpackcontents
- })
- dlg.data.list:add_sort_mechanism("alphabetic", sort_mod_list)
- dlg.data.list:set_sortmode("alphabetic")
-
return dlg
end
diff --git a/builtin/mainmenu/dlg_contentstore.lua b/builtin/mainmenu/dlg_contentstore.lua
index 3bc5f60bb..01c42be0b 100644
--- a/builtin/mainmenu/dlg_contentstore.lua
+++ b/builtin/mainmenu/dlg_contentstore.lua
@@ -1,5 +1,5 @@
--Minetest
---Copyright (C) 2018 rubenwardy
+--Copyright (C) 2018-20 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
@@ -15,8 +15,17 @@
--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
+ function create_store_dlg()
+ return messagebox("store",
+ fgettext("ContentDB is not available when Minetest was compiled without cURL"))
+ end
+ return
+end
+
local store = { packages = {}, packages_full = {} }
-local package_dialog = {}
+
+local http = minetest.get_http_api()
-- Screenshot
local screenshot_dir = core.get_cache_path() .. DIR_DELIM .. "cdb"
@@ -44,19 +53,15 @@ local filter_types_type = {
}
-
-
local function download_package(param)
if core.download_file(param.package.url, param.filename) then
return {
- package = param.package,
filename = param.filename,
successful = true,
}
else
core.log("error", "downloading " .. dump(param.package.url) .. " failed")
return {
- package = param.package,
successful = false,
}
end
@@ -70,9 +75,9 @@ local function start_install(calling_dialog, package)
local function callback(result)
if result.successful then
- local path, msg = pkgmgr.install(result.package.type,
- result.filename, result.package.name,
- result.package.path)
+ local path, msg = pkgmgr.install(package.type,
+ result.filename, package.name,
+ package.path)
if not path then
gamedata.errormessage = msg
else
@@ -80,33 +85,33 @@ local function start_install(calling_dialog, package)
local conf_path
local name_is_title = false
- if result.package.type == "mod" then
+ if package.type == "mod" then
local actual_type = pkgmgr.get_folder_type(path)
if actual_type.type == "modpack" then
conf_path = path .. DIR_DELIM .. "modpack.conf"
else
conf_path = path .. DIR_DELIM .. "mod.conf"
end
- elseif result.package.type == "game" then
+ elseif package.type == "game" then
conf_path = path .. DIR_DELIM .. "game.conf"
name_is_title = true
- elseif result.package.type == "txp" then
+ elseif package.type == "txp" then
conf_path = path .. DIR_DELIM .. "texture_pack.conf"
end
if conf_path then
local conf = Settings(conf_path)
if name_is_title then
- conf:set("name", result.package.title)
+ conf:set("name", package.title)
else
- conf:set("title", result.package.title)
- conf:set("name", result.package.name)
+ conf:set("title", package.title)
+ conf:set("name", package.name)
end
if not conf:get("description") then
- conf:set("description", result.package.short_description)
+ conf:set("description", package.short_description)
end
- conf:set("author", result.package.author)
- conf:set("release", result.package.release)
+ conf:set("author", package.author)
+ conf:set("release", package.release)
conf:write()
end
end
@@ -115,37 +120,22 @@ local function start_install(calling_dialog, package)
gamedata.errormessage = fgettext("Failed to download $1", package.name)
end
- if gamedata.errormessage == nil then
- core.button_handler({btn_hidden_close_download=result})
- else
- core.button_handler({btn_hidden_close_download={successful=false}})
- end
+ package.downloading = false
+ ui.update()
end
+ package.downloading = true
+
if not core.handle_async(download_package, params, callback) then
core.log("error", "ERROR: async event failed")
gamedata.errormessage = fgettext("Failed to download $1", package.name)
+ return
end
+end
- local new_dlg = dialog_create("store_downloading",
- function(data)
- return "size[7,2]label[0.25,0.75;" ..
- fgettext("Downloading and installing $1, please wait...", data.title) .. "]"
- end,
- function(this,fields)
- if fields["btn_hidden_close_download"] ~= nil then
- this:delete()
- return true
- end
-
- return false
- end,
- nil)
-
- new_dlg:set_parent(calling_dialog)
- new_dlg.data.title = package.title
- calling_dialog:hide()
- new_dlg:show()
+local function get_file_extension(path)
+ local parts = path:split(".")
+ return parts[#parts]
end
local function get_screenshot(package)
@@ -156,8 +146,9 @@ local function get_screenshot(package)
end
-- Get tmp screenshot path
+ local ext = get_file_extension(package.thumbnail)
local filepath = screenshot_dir .. DIR_DELIM ..
- package.type .. "-" .. package.author .. "-" .. package.name .. ".png"
+ ("%s-%s-%s.%s"):format(package.type, package.author, package.name, ext)
-- Return if already downloaded
local file = io.open(filepath, "r")
@@ -195,84 +186,12 @@ local function get_screenshot(package)
return defaulttexturedir .. "loading_screenshot.png"
end
-
-
-function package_dialog.get_formspec()
- local package = package_dialog.package
-
- store.update_paths()
-
- local formspec = {
- "size[9,4;true]",
- "image[0,1;4.5,3;", core.formspec_escape(get_screenshot(package)), ']',
- "label[3.8,1;",
- minetest.colorize(mt_color_green, core.formspec_escape(package.title)), "\n",
- minetest.colorize('#BFBFBF', "by " .. core.formspec_escape(package.author)), "]",
- "textarea[4,2;5.3,2;;;", core.formspec_escape(package.short_description), "]",
- "button[0,0;2,1;back;", fgettext("Back"), "]",
- }
-
- if not package.path then
- formspec[#formspec + 1] = "button[7,0;2,1;install;"
- formspec[#formspec + 1] = fgettext("Install")
- formspec[#formspec + 1] = "]"
- elseif package.installed_release < package.release then
- -- The install_ action also handles updating
- formspec[#formspec + 1] = "button[7,0;2,1;install;"
- formspec[#formspec + 1] = fgettext("Update")
- formspec[#formspec + 1] = "]"
- formspec[#formspec + 1] = "button[5,0;2,1;uninstall;"
- formspec[#formspec + 1] = fgettext("Uninstall")
- formspec[#formspec + 1] = "]"
- else
- formspec[#formspec + 1] = "button[7,0;2,1;uninstall;"
- formspec[#formspec + 1] = fgettext("Uninstall")
- formspec[#formspec + 1] = "]"
- end
-
- return table.concat(formspec, "")
-end
-
-function package_dialog.handle_submit(this, fields)
- if fields.back then
- this:delete()
- return true
- end
-
- if fields.install then
- start_install(this, package_dialog.package)
- return true
- end
-
- if fields.uninstall then
- local dlg_delmod = create_delete_content_dlg(package_dialog.package)
- dlg_delmod:set_parent(this)
- this:hide()
- dlg_delmod:show()
- return true
- end
-
- return false
-end
-
-function package_dialog.create(package)
- package_dialog.package = package
- return dialog_create("package_view",
- package_dialog.get_formspec,
- package_dialog.handle_submit,
- nil)
-end
-
function store.load()
- local tmpdir = os.tempfolder()
- local target = tmpdir .. DIR_DELIM .. "packages.json"
-
- assert(core.create_dir(tmpdir))
-
- local base_url = core.settings:get("contentdb_url")
+ local version = core.get_version()
+ local base_url = core.settings:get("contentdb_url")
local url = base_url ..
"/api/packages/?type=mod&type=game&type=txp&protocol_version=" ..
- core.get_max_supp_proto()
+ core.get_max_supp_proto() .. "&engine_version=" .. version.string
for _, item in pairs(core.settings:get("contentdb_flag_blacklist"):split(",")) do
item = item:trim()
@@ -281,31 +200,29 @@ function store.load()
end
end
- core.download_file(url, target)
+ local timeout = tonumber(minetest.settings:get("curl_file_download_timeout"))
+ local response = http.fetch_sync({ url = url, timeout = timeout })
+ if not response.succeeded then
+ return
+ end
- local file = io.open(target, "r")
- if file then
- store.packages_full = core.parse_json(file:read("*all")) or {}
- file:close()
+ store.packages_full = core.parse_json(response.data) or {}
- for _, package in pairs(store.packages_full) do
- package.url = base_url .. "/packages/" ..
+ for _, package in pairs(store.packages_full) do
+ package.url = base_url .. "/packages/" ..
package.author .. "/" .. package.name ..
"/releases/" .. package.release .. "/download/"
- local name_len = #package.name
- if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then
- package.id = package.author:lower() .. "/" .. package.name:sub(1, name_len - 5)
- else
- package.id = package.author:lower() .. "/" .. package.name
- end
+ local name_len = #package.name
+ if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then
+ package.id = package.author:lower() .. "/" .. package.name:sub(1, name_len - 5)
+ else
+ package.id = package.author:lower() .. "/" .. package.name
end
-
- store.packages = store.packages_full
- store.loaded = true
end
- core.delete_dir(tmpdir)
+ store.packages = store.packages_full
+ store.loaded = true
end
function store.update_paths()
@@ -395,34 +312,35 @@ function store.get_formspec(dlgdata)
cur_page = 1
end
+ local W = 15.75
+ local H = 9.5
+
local formspec
if #store.packages_full > 0 then
formspec = {
- "size[12,7;true]",
+ "formspec_version[3]",
+ "size[15.75,9.5]",
"position[0.5,0.55]",
- "field[0.2,0.1;7.8,1;search_string;;",
- core.formspec_escape(search_string), "]",
+ "container[0.375,0.375]",
+ "field[0,0;10.225,0.8;search_string;;", core.formspec_escape(search_string), "]",
"field_close_on_enter[search_string;false]",
- "button[7.7,-0.2;2,1;search;",
- fgettext("Search"), "]",
- "dropdown[9.7,-0.1;2.4;type;",
- table.concat(filter_types_titles, ","),
- ";", filter_type, "]",
- -- "textlist[0,1;2.4,5.6;a;",
- -- table.concat(taglist, ","), "]",
+ "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, "]",
+ "container_end[]",
-- Page nav buttons
- "container[0,",
- num_per_page + 1.5, "]",
- "button[-0.1,0;3,1;back;",
- fgettext("Back to Main Menu"), "]",
- "button[7.1,0;1,1;pstart;<<]",
- "button[8.1,0;1,1;pback;<]",
- "label[9.2,0.2;",
- tonumber(cur_page), " / ",
- tonumber(dlgdata.pagemax), "]",
- "button[10.1,0;1,1;pnext;>]",
- "button[11.1,0;1,1;pend;>>]",
+ "container[0,", H - 0.8 - 0.375, "]",
+ "button[0.375,0;4,0.8;back;", fgettext("Back to Main Menu"), "]",
+
+ "container[", W - 0.375 - 0.8*4 - 2, ",0]",
+ "image_button[0,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]",
+ "image_button[0.8,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]",
+ "style[pagenum;border=false]",
+ "button[1.6,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]",
+ "image_button[3.6,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]",
+ "image_button[4.4,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]",
+ "container_end[]",
+
"container_end[]",
}
@@ -433,73 +351,84 @@ function store.get_formspec(dlgdata)
end
else
formspec = {
- "size[12,7;true]",
+ "size[12,7]",
"position[0.5,0.55]",
"label[4,3;", fgettext("No packages could be retrieved"), "]",
- "button[-0.1,",
- num_per_page + 1.5,
- ";3,1;back;",
- fgettext("Back to Main Menu"), "]",
+ "container[0,", H - 0.8 - 0.375, "]",
+ "button[0,0;4,0.8;back;", fgettext("Back to Main Menu"), "]",
+ "container_end[]",
}
end
local start_idx = (cur_page - 1) * num_per_page + 1
for i=start_idx, math.min(#store.packages, start_idx+num_per_page-1) do
local package = store.packages[i]
- formspec[#formspec + 1] = "container[0.5,"
- formspec[#formspec + 1] = (i - start_idx) * 1.1 + 1
+ formspec[#formspec + 1] = "container[0.375,"
+ formspec[#formspec + 1] = (i - start_idx) * 1.375 + (2*0.375 + 0.8)
formspec[#formspec + 1] = "]"
-- image
- formspec[#formspec + 1] = "image[-0.4,0;1.5,1;"
+ formspec[#formspec + 1] = "image[0,0;1.5,1;"
formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package))
formspec[#formspec + 1] = "]"
-- title
- formspec[#formspec + 1] = "label[1,-0.1;"
+ 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))
formspec[#formspec + 1] = "]"
- -- description
- if package.path and package.installed_release < package.release then
- formspec[#formspec + 1] = "textarea[1.25,0.3;7.5,1;;;"
- else
- formspec[#formspec + 1] = "textarea[1.25,0.3;9,1;;;"
- end
- formspec[#formspec + 1] = core.formspec_escape(package.short_description)
- formspec[#formspec + 1] = "]"
-
-- buttons
- if not package.path then
- formspec[#formspec + 1] = "button[9.9,0;1.5,1;install_"
+ local description_width = W - 0.375*5 - 1 - 2*1.5
+ 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] = "]"
+ 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] = "]"
else
if package.installed_release < package.release then
+ description_width = description_width - 1.5
+
-- The install_ action also handles updating
- formspec[#formspec + 1] = "button[8.4,0;1.5,1;install_"
+ 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
- formspec[#formspec + 1] = "button[9.9,0;1.5,1;uninstall_"
+ 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] = "]"
end
- --formspec[#formspec + 1] = "button[9.9,0;1.5,1;view_"
- --formspec[#formspec + 1] = tostring(i)
- --formspec[#formspec + 1] = ";"
- --formspec[#formspec + 1] = fgettext("View")
- --formspec[#formspec + 1] = "]"
+ 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] = "]"
+ formspec[#formspec + 1] = "container_end[]"
+
+ -- description
+ formspec[#formspec + 1] = "textarea[1.855,0.3;"
+ formspec[#formspec + 1] = tostring(description_width)
+ formspec[#formspec + 1] = ",0.8;;;"
+ formspec[#formspec + 1] = core.formspec_escape(package.short_description)
+ formspec[#formspec + 1] = "]"
formspec[#formspec + 1] = "container_end[]"
end
@@ -576,10 +505,9 @@ function store.handle_submit(this, fields)
end
if fields["view_" .. i] then
- local dlg = package_dialog.create(package)
- dlg:set_parent(this)
- this:hide()
- dlg:show()
+ local url = ("%s/packages/%s?protocol_version=%d"):format(
+ core.settings:get("contentdb_url"), package.id, core.get_max_supp_proto())
+ core.open_url(url)
return true
end
end
@@ -594,6 +522,17 @@ function create_store_dlg(type)
search_string = ""
cur_page = 1
+
+ if type then
+ -- table.indexof does not work on tables that contain `nil`
+ for i, v in pairs(filter_types_type) do
+ if v == type then
+ filter_type = i
+ break
+ end
+ end
+ end
+
store.filter_packages(search_string)
return dialog_create("store",
diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua
index 31d41d693..36df23cce 100644
--- a/builtin/mainmenu/dlg_create_world.lua
+++ b/builtin/mainmenu/dlg_create_world.lua
@@ -17,13 +17,110 @@
local worldname = ""
+local function table_to_flags(ftable)
+ -- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves"
+ local str = {}
+ for flag, is_set in pairs(ftable) do
+ str[#str + 1] = is_set and flag or ("no" .. flag)
+ end
+ return table.concat(str, ",")
+end
+
+-- Same as check_flag but returns a string
+local function strflag(flags, flag)
+ return (flags[flag] == true) and "true" or "false"
+end
+
+local cb_caverns = { "caverns", fgettext("Caverns"), "caverns",
+ fgettext("Very large caverns deep in the underground") }
+local tt_sea_rivers = fgettext("Sea level rivers")
+
+local flag_checkboxes = {
+ v5 = {
+ cb_caverns,
+ },
+ v7 = {
+ cb_caverns,
+ { "ridges", fgettext("Rivers"), "ridges", tt_sea_rivers },
+ { "mountains", fgettext("Mountains"), "mountains" },
+ { "floatlands", fgettext("Floatlands (experimental)"), "floatlands",
+ fgettext("Floating landmasses in the sky") },
+ },
+ carpathian = {
+ cb_caverns,
+ { "rivers", fgettext("Rivers"), "rivers", tt_sea_rivers },
+ },
+ valleys = {
+ { "altitude-chill", fgettext("Altitude chill"), "altitude_chill",
+ fgettext("Reduces heat with altitude") },
+ { "altitude-dry", fgettext("Altitude dry"), "altitude_dry",
+ fgettext("Reduces humidity with altitude") },
+ { "humid-rivers", fgettext("Humid rivers"), "humid_rivers",
+ fgettext("Increases humidity around rivers") },
+ { "vary-river-depth", fgettext("Vary river depth"), "vary_river_depth",
+ fgettext("Low humidity and high heat causes shallow or dry rivers") },
+ },
+ flat = {
+ { "hills", fgettext("Hills"), "hills" },
+ { "lakes", fgettext("Lakes"), "lakes" },
+ },
+ fractal = {
+ { "terrain", fgettext("Additional terrain"), "terrain",
+ fgettext("Generate non-fractal terrain: Oceans and underground") },
+ },
+ v6 = {
+ { "trees", fgettext("Trees and jungle grass"), "trees" },
+ { "flat", fgettext("Flat terrain"), "flat" },
+ { "mudflow", fgettext("Mud flow"), "mudflow",
+ fgettext("Terrain surface erosion") },
+ -- Biome settings are in mgv6_biomes below
+ },
+}
+
+local mgv6_biomes = {
+ {
+ fgettext("Temperate, Desert, Jungle, Tundra, Taiga"),
+ {jungles = true, snowbiomes = true}
+ },
+ {
+ fgettext("Temperate, Desert, Jungle"),
+ {jungles = true, snowbiomes = false}
+ },
+ {
+ fgettext("Temperate, Desert"),
+ {jungles = false, snowbiomes = false}
+ },
+}
+
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]" ..
+ "textarea[0.3,0;11.7,2;;;"..
+ fgettext("You have no games installed.") .. "\n" ..
+ fgettext("Download one from minetest.net") .. "]" ..
+ "button[4.75,2.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
+ end
+
local mapgens = core.get_mapgen_names()
local current_seed = core.settings:get("fixed_map_seed") or ""
local current_mg = core.settings:get("mg_name")
local gameid = core.settings:get("menu_last_game")
+ local flags = {
+ main = core.settings:get_flags("mg_flags"),
+ v5 = core.settings:get_flags("mgv5_spflags"),
+ v6 = core.settings:get_flags("mgv6_spflags"),
+ v7 = core.settings:get_flags("mgv7_spflags"),
+ fractal = core.settings:get_flags("mgfractal_spflags"),
+ carpathian = core.settings:get_flags("mgcarpathian_spflags"),
+ valleys = core.settings:get_flags("mgvalleys_spflags"),
+ flat = core.settings:get_flags("mgflat_spflags"),
+ }
+
local gameidx = 0
if gameid ~= nil then
local _
@@ -35,15 +132,29 @@ local function create_world_formspec(dialogdata)
end
local game_by_gameidx = core.get_game(gameidx)
+ local disallowed_mapgen_settings = {}
if game_by_gameidx ~= nil then
local gamepath = game_by_gameidx.path
local gameconfig = Settings(gamepath.."/game.conf")
+ local allowed_mapgens = (gameconfig:get("allowed_mapgens") or ""):split()
+ for key, value in pairs(allowed_mapgens) do
+ allowed_mapgens[key] = value:trim()
+ end
+
local disallowed_mapgens = (gameconfig:get("disallowed_mapgens") or ""):split()
for key, value in pairs(disallowed_mapgens) do
disallowed_mapgens[key] = value:trim()
end
+ if #allowed_mapgens > 0 then
+ for i = #mapgens, 1, -1 do
+ if table.indexof(allowed_mapgens, mapgens[i]) == -1 then
+ table.remove(mapgens, i)
+ end
+ end
+ end
+
if disallowed_mapgens then
for i = #mapgens, 1, -1 do
if table.indexof(disallowed_mapgens, mapgens[i]) > 0 then
@@ -51,49 +162,193 @@ local function create_world_formspec(dialogdata)
end
end
end
+
+ local ds = (gameconfig:get("disallowed_mapgen_settings") or ""):split()
+ for _, value in pairs(ds) do
+ disallowed_mapgen_settings[value:trim()] = true
+ end
end
local mglist = ""
- local selindex = 1
+ local selindex
local i = 1
+ local first_mg
for k,v in pairs(mapgens) do
+ if not first_mg then
+ first_mg = v
+ end
if current_mg == v then
selindex = i
end
i = i + 1
mglist = mglist .. v .. ","
end
+ if not selindex then
+ selindex = 1
+ current_mg = first_mg
+ end
mglist = mglist:sub(1, -2)
- current_seed = core.formspec_escape(current_seed)
- local retval =
- "size[11.5,6.5,true]" ..
- "label[2,0;" .. fgettext("World name") .. "]"..
- "field[4.5,0.4;6,0.5;te_world_name;;" .. minetest.formspec_escape(worldname) .. "]" ..
+ local mg_main_flags = function(mapgen, y)
+ if mapgen == "singlenode" then
+ return "", y
+ end
+ if disallowed_mapgen_settings["mg_flags"] then
+ return "", y
+ end
- "label[2,1;" .. fgettext("Seed") .. "]"..
- "field[4.5,1.4;6,0.5;te_seed;;".. current_seed .. "]" ..
+ local form = "checkbox[0," .. y .. ";flag_mg_caves;" ..
+ fgettext("Caves") .. ";"..strflag(flags.main, "caves").."]"
+ y = y + 0.5
- "label[2,2;" .. fgettext("Mapgen") .. "]"..
- "dropdown[4.2,2;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" ..
+ form = form .. "checkbox[0,"..y..";flag_mg_dungeons;" ..
+ fgettext("Dungeons") .. ";"..strflag(flags.main, "dungeons").."]"
+ y = y + 0.5
- "label[2,3;" .. fgettext("Game") .. "]"..
- "textlist[4.2,3;5.8,2.3;games;" .. pkgmgr.gamelist() ..
- ";" .. gameidx .. ";true]" ..
+ local d_name = fgettext("Decorations")
+ local d_tt
+ if mapgen == "v6" then
+ d_tt = fgettext("Structures appearing on the terrain (no effect on trees and jungle grass created by v6)")
+ else
+ d_tt = fgettext("Structures appearing on the terrain, typically trees and plants")
+ end
+ form = form .. "checkbox[0,"..y..";flag_mg_decorations;" ..
+ d_name .. ";" ..
+ strflag(flags.main, "decorations").."]" ..
+ "tooltip[flag_mg_decorations;" ..
+ d_tt ..
+ "]"
+ y = y + 0.5
- "button[3.25,6;2.5,0.5;world_create_confirm;" .. fgettext("Create") .. "]" ..
- "button[5.75,6;2.5,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
+ form = form .. "tooltip[flag_mg_caves;" ..
+ fgettext("Network of tunnels and caves")
+ .. "]"
+ return form, y
+ end
- if #pkgmgr.games == 0 then
- retval = retval .. "box[2,4;8,1;#ff8800]label[2.25,4;" ..
- fgettext("You have no games installed.") .. "]label[2.25,4.4;" ..
- fgettext("Download one from minetest.net") .. "]"
- elseif #pkgmgr.games == 1 and pkgmgr.games[1].id == "minimal" then
- retval = retval .. "box[1.75,4;8.7,1;#ff8800]label[2,4;" ..
- fgettext("Warning: The minimal development test is meant for developers.") .. "]label[2,4.4;" ..
+ local mg_specific_flags = function(mapgen, y)
+ if not flag_checkboxes[mapgen] then
+ return "", y
+ end
+ if disallowed_mapgen_settings["mg"..mapgen.."_spflags"] then
+ return "", y
+ end
+ local form = ""
+ for _,tab in pairs(flag_checkboxes[mapgen]) do
+ local id = "flag_mg"..mapgen.."_"..tab[1]
+ form = form .. ("checkbox[0,%f;%s;%s;%s]"):
+ format(y, id, tab[2], strflag(flags[mapgen], tab[3]))
+
+ if tab[4] then
+ form = form .. "tooltip["..id..";"..tab[4].."]"
+ end
+ y = y + 0.5
+ end
+
+ if mapgen ~= "v6" then
+ -- No special treatment
+ return form, y
+ end
+ -- Special treatment for v6 (add biome widgets)
+
+ -- Biome type (jungles, snowbiomes)
+ local biometype
+ if flags.v6.snowbiomes == true then
+ biometype = 1
+ elseif flags.v6.jungles == true then
+ biometype = 2
+ else
+ biometype = 3
+ end
+ y = y + 0.3
+
+ form = form .. "label[0,"..(y+0.1)..";" .. fgettext("Biomes") .. "]"
+ y = y + 0.6
+
+ form = form .. "dropdown[0,"..y..";6.3;mgv6_biomes;"
+ for b=1, #mgv6_biomes do
+ form = form .. mgv6_biomes[b][1]
+ if b < #mgv6_biomes then
+ form = form .. ","
+ end
+ end
+ form = form .. ";" .. biometype.. "]"
+
+ -- biomeblend
+ y = y + 0.55
+ form = form .. "checkbox[0,"..y..";flag_mgv6_biomeblend;" ..
+ fgettext("Biome blending") .. ";"..strflag(flags.v6, "biomeblend").."]" ..
+ "tooltip[flag_mgv6_biomeblend;" ..
+ fgettext("Smooth transition between biomes") .. "]"
+
+ return form, y
+ end
+
+ current_seed = core.formspec_escape(current_seed)
+
+ local y_start = 0.0
+ local y = y_start
+ local str_flags, str_spflags
+ local label_flags, label_spflags = "", ""
+ y = y + 0.3
+ str_flags, y = mg_main_flags(current_mg, y)
+ if str_flags ~= "" then
+ label_flags = "label[0,"..y_start..";" .. fgettext("Mapgen flags") .. "]"
+ y_start = y + 0.4
+ else
+ y_start = 0.0
+ end
+ y = y_start + 0.3
+ str_spflags = mg_specific_flags(current_mg, y)
+ if str_spflags ~= "" then
+ label_spflags = "label[0,"..y_start..";" .. fgettext("Mapgen-specific flags") .. "]"
+ end
+
+ -- Warning if only devtest is installed
+ local devtest_only = ""
+ local gamelist_height = 2.3
+ if #pkgmgr.games == 1 and pkgmgr.games[1].id == "devtest" then
+ devtest_only = "box[0,0;5.8,1.7;#ff8800]" ..
+ "textarea[0.3,0;6,1.8;;;"..
+ fgettext("Warning: The Development Test is meant for developers.") .. "\n" ..
fgettext("Download a game, such as Minetest Game, from minetest.net") .. "]"
+ gamelist_height = 0.5
end
+ local retval =
+ "size[12.25,7,true]" ..
+
+ -- Left side
+ "container[0,0]"..
+ "field[0.3,0.6;6,0.5;te_world_name;" ..
+ fgettext("World name") ..
+ ";" .. core.formspec_escape(worldname) .. "]" ..
+
+ "field[0.3,1.7;6,0.5;te_seed;" ..
+ fgettext("Seed") ..
+ ";".. current_seed .. "]" ..
+
+ "label[0,2;" .. fgettext("Mapgen") .. "]"..
+ "dropdown[0,2.5;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" ..
+
+ "label[0,3.35;" .. fgettext("Game") .. "]"..
+ "textlist[0,3.85;5.8,"..gamelist_height..";games;" ..
+ pkgmgr.gamelist() .. ";" .. gameidx .. ";false]" ..
+ "container[0,4.5]" ..
+ devtest_only ..
+ "container_end[]" ..
+ "container_end[]" ..
+
+ -- Right side
+ "container[6.2,0]"..
+ label_flags .. str_flags ..
+ label_spflags .. str_spflags ..
+ "container_end[]"..
+
+ -- Menu buttons
+ "button[3.25,6.5;3,0.5;world_create_confirm;" .. fgettext("Create") .. "]" ..
+ "button[6.25,6.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
+
return retval
end
@@ -150,11 +405,53 @@ local function create_world_buttonhandler(this, fields)
return true
end
+ for k,v in pairs(fields) do
+ local split = string.split(k, "_", nil, 3)
+ if split and split[1] == "flag" then
+ local setting
+ if split[2] == "mg" then
+ setting = "mg_flags"
+ else
+ setting = split[2].."_spflags"
+ end
+ -- We replaced the underscore of flag names with a dash.
+ local flag = string.gsub(split[3], "-", "_")
+ local ftable = core.settings:get_flags(setting)
+ if v == "true" then
+ ftable[flag] = true
+ else
+ ftable[flag] = false
+ end
+ local flags = table_to_flags(ftable)
+ core.settings:set(setting, flags)
+ return true
+ end
+ end
+
if fields["world_create_cancel"] then
this:delete()
return true
end
+ if fields["mgv6_biomes"] then
+ local entry = minetest.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")
+ ftable.jungles = mgv6_biomes[b][2].jungles
+ ftable.snowbiomes = mgv6_biomes[b][2].snowbiomes
+ local flags = table_to_flags(ftable)
+ core.settings:set("mgv6_spflags", flags)
+ return true
+ end
+ end
+ end
+
+ if fields["dd_mapgen"] then
+ core.settings:set("mg_name", fields["dd_mapgen"])
+ return true
+ end
+
return false
end
diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua
index 130c3e73c..c17e79270 100644
--- a/builtin/mainmenu/init.lua
+++ b/builtin/mainmenu/init.lua
@@ -20,20 +20,18 @@ mt_color_blue = "#6389FF"
mt_color_green = "#72FF63"
mt_color_dark_green = "#25C191"
---for all other colors ask sfan5 to complete his work!
-
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
-dofile(basepath .. "common" .. DIR_DELIM .. "async_event.lua")
dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua")
dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua")
dofile(basepath .. "fstk" .. DIR_DELIM .. "tabview.lua")
dofile(basepath .. "fstk" .. DIR_DELIM .. "ui.lua")
+dofile(menupath .. DIR_DELIM .. "async_event.lua")
dofile(menupath .. DIR_DELIM .. "common.lua")
dofile(menupath .. DIR_DELIM .. "pkgmgr.lua")
dofile(menupath .. DIR_DELIM .. "textures.lua")
diff --git a/builtin/mainmenu/tab_credits.lua b/builtin/mainmenu/tab_credits.lua
index 962d2a3b4..c2b7e503a 100644
--- a/builtin/mainmenu/tab_credits.lua
+++ b/builtin/mainmenu/tab_credits.lua
@@ -101,8 +101,8 @@ return {
local logofile = defaulttexturedir .. "logo.png"
local version = core.get_version()
return "image[0.5,1;" .. core.formspec_escape(logofile) .. "]" ..
- "label[0.5,3.2;" .. version.project .. " " .. version.string .. "]" ..
- "label[0.5,3.5;http://minetest.net]" ..
+ "label[0.5,2.8;" .. version.project .. " " .. version.string .. "]" ..
+ "button[0.5,3;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,5 +115,10 @@ return {
"#FFFF00," .. fgettext("Previous Contributors") .. ",," ..
buildCreditList(previous_contributors) .. "," ..
";1]"
- end
+ end,
+ cbf_button_handler = function(this, fields, name, tabdata)
+ if fields.homepage then
+ core.open_url("https://www.minetest.net")
+ end
+ end,
}
diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua
index 0969bccfb..a21cf12b1 100644
--- a/builtin/mainmenu/tab_local.lua
+++ b/builtin/mainmenu/tab_local.lua
@@ -35,6 +35,15 @@ if enable_gamebar then
end
local function game_buttonbar_button_handler(fields)
+ if fields.game_open_cdb then
+ local maintab = ui.find_by_name("maintab")
+ local dlg = create_store_dlg("game")
+ dlg:set_parent(maintab)
+ maintab:hide()
+ dlg:show()
+ return true
+ end
+
for key,value in pairs(fields) do
for j=1,#pkgmgr.games,1 do
if ("game_btnbar_" .. pkgmgr.games[j].id == key) then
@@ -87,6 +96,9 @@ if enable_gamebar then
end
btnbar:add_button(btn_name, text, image, tooltip)
end
+
+ local plus_image = core.formspec_escape(defaulttexturedir .. "plus.png")
+ btnbar:add_button("game_open_cdb", "", plus_image, fgettext("Install games from ContentDB"))
end
else
function current_game()
@@ -207,40 +219,35 @@ local function main_button_handler(this, fields, name, tabdata)
local selected = core.get_textlist_index("sp_worlds")
gamedata.selected_world = menudata.worldlist:get_raw_index(selected)
- if core.settings:get_bool("enable_server") then
- if selected ~= nil and gamedata.selected_world ~= 0 then
- gamedata.playername = fields["te_playername"]
- gamedata.password = fields["te_passwd"]
- gamedata.port = fields["te_serverport"]
- gamedata.address = ""
-
- core.settings:set("port",gamedata.port)
- if fields["te_serveraddr"] ~= nil then
- core.settings:set("bind_address",fields["te_serveraddr"])
- end
+ if selected == nil or gamedata.selected_world == 0 then
+ gamedata.errormessage =
+ fgettext("No world created or selected!")
+ return true
+ end
- --update last game
- local world = menudata.worldlist:get_raw_element(gamedata.selected_world)
- if world then
- local game = pkgmgr.find_by_gameid(world.gameid)
- core.settings:set("menu_last_game", game.id)
- end
+ -- Update last game
+ local world = menudata.worldlist:get_raw_element(gamedata.selected_world)
+ if world then
+ local game = pkgmgr.find_by_gameid(world.gameid)
+ core.settings:set("menu_last_game", game.id)
+ end
- core.start()
- else
- gamedata.errormessage =
- fgettext("No world created or selected!")
+ if core.settings:get_bool("enable_server") then
+ gamedata.playername = fields["te_playername"]
+ gamedata.password = fields["te_passwd"]
+ gamedata.port = fields["te_serverport"]
+ gamedata.address = ""
+
+ core.settings:set("port",gamedata.port)
+ if fields["te_serveraddr"] ~= nil then
+ core.settings:set("bind_address",fields["te_serveraddr"])
end
else
- if selected ~= nil and gamedata.selected_world ~= 0 then
- gamedata.singleplayer = true
- core.start()
- else
- gamedata.errormessage =
- fgettext("No world created or selected!")
- end
- return true
+ gamedata.singleplayer = true
end
+
+ core.start()
+ return true
end
if fields["world_create"] ~= nil then
diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt
index 1f2889b45..c787aea2c 100644
--- a/builtin/settingtypes.txt
+++ b/builtin/settingtypes.txt
@@ -42,10 +42,10 @@
# Flags are always separated by comma without spaces.
# - default possible_flags
# * noise_params_2d:
-# Format is <offset>, <scale>, (<spreadX>, <spreadY>, <spreadZ>), <seed>, <octaves>, <persistance>, <lacunarity>[, <default flags>]
+# Format is <offset>, <scale>, (<spreadX>, <spreadY>, <spreadZ>), <seed>, <octaves>, <persistence>, <lacunarity>[, <default flags>]
# - default
# * noise_params_3d:
-# Format is <offset>, <scale>, (<spreadX>, <spreadY>, <spreadZ>), <seed>, <octaves>, <persistance>, <lacunarity>[, <default flags>]
+# Format is <offset>, <scale>, (<spreadX>, <spreadY>, <spreadZ>), <seed>, <octaves>, <persistence>, <lacunarity>[, <default flags>]
# - default
# * v3f:
# Format is (<X>, <Y>, <Z>)
@@ -252,7 +252,7 @@ keymap_cinematic (Cinematic mode key) key
# Key for toggling display of minimap.
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
-keymap_minimap (Minimap key) key KEY_F9
+keymap_minimap (Minimap key) key KEY_KEY_V
# Key for taking screenshots.
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
@@ -424,7 +424,7 @@ keymap_toggle_profiler (Profiler toggle key) key KEY_F6
# Key for switching between first- and third-person camera.
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
-keymap_camera_mode (Toggle camera mode key) key KEY_F7
+keymap_camera_mode (Toggle camera mode key) key KEY_KEY_C
# Key for increasing the viewing range.
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
@@ -561,9 +561,6 @@ enable_parallax_occlusion (Parallax occlusion) bool false
# 1 = relief mapping (slower, more accurate).
parallax_occlusion_mode (Parallax occlusion mode) int 1 0 1
-# Strength of parallax.
-3d_paralax_strength (Parallax occlusion strength) float 0.025
-
# Number of parallax occlusion iterations.
parallax_occlusion_iterations (Parallax occlusion iterations) int 4
@@ -713,6 +710,9 @@ fall_bobbing_amount (Fall bobbing factor) float 0.03
# Note that the interlaced mode requires shaders to be enabled.
3d_mode (3D mode) enum none none,anaglyph,interlaced,topbottom,sidebyside,crossview,pageflip
+# Strength of 3D mode parallax.
+3d_paralax_strength (3D mode parallax strength) float 0.025
+
# In-game chat console height, between 0.1 (10%) and 1.0 (100%).
console_height (Console height) float 0.6 0.1 1.0
@@ -903,8 +903,13 @@ fallback_font_shadow_alpha (Fallback font shadow alpha) int 128 0 255
# This font will be used for certain languages or if the default font is unavailable.
fallback_font_path (Fallback font path) filepath fonts/DroidSansFallbackFull.ttf
-# Path to save screenshots at.
-screenshot_path (Screenshot folder) path
+# Font size of the recent chat text and chat prompt in point (pt).
+# Value 0 will use the default font size.
+chat_font_size (Chat font size) int 0
+
+# Path to save screenshots at. Can be an absolute or relative path.
+# The folder will be created if it doesn't already exist.
+screenshot_path (Screenshot folder) path screenshots
# Format of screenshots.
screenshot_format (Screenshot format) enum png png,jpg,bmp,pcx,ppm,tga
@@ -954,6 +959,12 @@ address (Server address) string
# Note that the port field in the main menu overrides this setting.
remote_port (Remote port) int 30000 1 65535
+# Prometheus listener address.
+# If minetest is compiled with ENABLE_PROMETHEUS option enabled,
+# enable metrics listener for Prometheus on that address.
+# Metrics can be fetch on http://127.0.0.1:30000/metrics
+prometheus_listener_address (Prometheus listener address) string 127.0.0.1:30000
+
# Save the map received by the client on disk.
enable_local_map_saving (Saving map received from server) bool false
@@ -1078,6 +1089,10 @@ map-dir (Map directory) path
# Setting it to -1 disables the feature.
item_entity_ttl (Item entity TTL) int 900
+# Specifies the default stack size of nodes, items and tools.
+# Note that mods or games may explicitly set a stack for certain (or all) items.
+default_stack_max (Default stack size) int 99
+
# Enable players getting damage and dying.
enable_damage (Damage) bool false
@@ -1149,7 +1164,7 @@ active_object_send_range_blocks (Active object send range) int 4
# 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_range.
+# This should be configured together with active_object_send_range_blocks.
active_block_range (Active block range) int 3
# From how far blocks are sent to clients, stated in mapblocks (16 nodes).
@@ -1372,7 +1387,7 @@ name (Player name) string
# Set the language. Leave empty to use the system language.
# A restart is required after changing this.
-language (Language) enum ,ar,ca,cs,da,de,dv,el,eo,es,et,eu,fil,fr,hu,id,it,ja,ja_KS,jbo,kk,kn,lo,lt,ms,my,nb,nl,nn,pl,pt,pt_BR,ro,ru,sl,sr_Cyrl,sv,sw,th,tr,uk,vi
+language (Language) enum ,ar,ca,cs,da,de,dv,el,en,eo,es,et,eu,fil,fr,hu,id,it,ja,ja_KS,jbo,kk,kn,lo,lt,ms,my,nb,nl,nn,pl,pt,pt_BR,ro,ru,sl,sr_Cyrl,sv,sw,th,tr,uk,vi
# Level of logging to be written to debug.txt:
# - <nothing> (no logging)
@@ -1390,6 +1405,9 @@ debug_log_level (Debug log level) enum action ,none,error,warning,action,info,ve
# debug.txt is only moved if this setting is positive.
debug_log_size_max (Debug log file size threshold) int 50
+# Minimal level of logging to be written to chat.
+chat_log_level (Chat log level) enum error ,none,error,warning,action,info,verbose
+
# Enable IPv6 support (for both client and server).
# Required for IPv6 connections to work at all.
enable_ipv6 (IPv6) bool true
@@ -1593,12 +1611,53 @@ mgv6_np_apple_trees (Apple trees noise) noise_params_2d 0, 1, (100, 100, 100), 3
[*Mapgen V7]
# Map generation attributes specific to Mapgen v7.
-# 'ridges' enables the rivers.
+# 'ridges': Rivers.
+# 'floatlands': Floating land masses in the atmosphere.
+# 'caverns': Giant caves deep underground.
mgv7_spflags (Mapgen V7 specific flags) flags mountains,ridges,nofloatlands,caverns mountains,ridges,floatlands,caverns,nomountains,noridges,nofloatlands,nocaverns
# Y of mountain density gradient zero level. Used to shift mountains vertically.
mgv7_mount_zero_level (Mountain zero level) int 0
+# Lower Y limit of floatlands.
+mgv7_floatland_ymin (Floatland minimum Y) int 1024
+
+# Upper Y limit of floatlands.
+mgv7_floatland_ymax (Floatland maximum Y) int 4096
+
+# Y-distance over which floatlands taper from full density to nothing.
+# Tapering starts at this distance from the Y limit.
+# For a solid floatland layer, this controls the height of hills/mountains.
+# Must be less than or equal to half the distance between the Y limits.
+mgv7_floatland_taper (Floatland tapering distance) int 256
+
+# Exponent of the floatland tapering. Alters the tapering behaviour.
+# Value = 1.0 creates a uniform, linear tapering.
+# Values > 1.0 create a smooth tapering suitable for the default separated
+# floatlands.
+# Values < 1.0 (for example 0.25) create a more defined surface level with
+# flatter lowlands, suitable for a solid floatland layer.
+mgv7_float_taper_exp (Floatland taper exponent) float 2.0
+
+# Adjusts the density of the floatland layer.
+# Increase value to increase density. Can be positive or negative.
+# Value = 0.0: 50% of volume is floatland.
+# Value = 2.0 (can be higher depending on 'mgv7_np_floatland', always test
+# to be sure) creates a solid floatland layer.
+mgv7_floatland_density (Floatland density) float -0.6
+
+# Surface level of optional water placed on a solid floatland layer.
+# Water is disabled by default and will only be placed if this value is set
+# to above 'mgv7_floatland_ymax' - 'mgv7_floatland_taper' (the start of the
+# upper tapering).
+# ***WARNING, POTENTIAL DANGER TO WORLDS AND SERVER PERFORMANCE***:
+# When enabling water placement the floatlands must be configured and tested
+# to be a solid layer by setting 'mgv7_floatland_density' to 2.0 (or other
+# required value depending on 'mgv7_np_floatland'), to avoid
+# server-intensive extreme water flow and to avoid vast flooding of the
+# world surface below.
+mgv7_floatland_ywater (Floatland water level) int -31000
+
# Controls width of tunnels, a smaller value creates wider tunnels.
# Value >= 10.0 completely disables generation of tunnels and avoids the
# intensive noise calculations.
@@ -1668,6 +1727,12 @@ mgv7_np_mountain (Mountain noise) noise_params_3d -0.6, 1, (250, 350, 250), 5333
# 3D noise defining structure of river canyon walls.
mgv7_np_ridge (Ridge noise) noise_params_3d 0, 1, (100, 100, 100), 6467, 4, 0.75, 2.0
+# 3D noise defining structure of floatlands.
+# If altered from the default, the noise 'scale' (0.7 by default) may need
+# to be adjusted, as floatland tapering functions best when this noise has
+# a value range of approximately -2.0 to 2.0.
+mgv7_np_floatland (Floatland noise) noise_params_3d 0, 0.7, (384, 96, 384), 1009, 4, 0.75, 1.618
+
# 3D noise defining giant caverns.
mgv7_np_cavern (Cavern noise) noise_params_3d 0, 1, (384, 128, 384), 723, 5, 0.63, 2.0
@@ -2098,20 +2163,17 @@ 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 emerge queues) int 512
+emergequeue_limit_total (Absolute limit of queued blocks to emerge) int 512
# Maximum number of blocks to be queued that are to be loaded from file.
-# Set to blank for an appropriate amount to be chosen automatically.
-emergequeue_limit_diskonly (Limit of emerge queues on disk) int 64
+# This limit is enforced per player.
+emergequeue_limit_diskonly (Per-player limit of queued blocks load from disk) int 64
# Maximum number of blocks to be queued that are to be generated.
-# Set to blank for an appropriate amount to be chosen automatically.
-emergequeue_limit_generate (Limit of emerge queues to generate) int 64
+# This limit is enforced per player.
+emergequeue_limit_generate (Per-player limit of queued blocks to generate) int 64
# Number of emerge threads to use.
-# WARNING: Currently there are multiple bugs that may cause crashes when
-# 'num_emerge_threads' is larger than 1. Until this warning is removed it is
-# strongly recommended this value is set to the default '1'.
# Value 0:
# - Automatic selection. The number of emerge threads will be
# - 'number of processors - 2', with a lower limit of 1.