diff options
Diffstat (limited to 'builtin')
28 files changed, 973 insertions, 320 deletions
diff --git a/builtin/async/init.lua b/builtin/async/init.lua index 1b2549685..3803994d6 100644 --- a/builtin/async/init.lua +++ b/builtin/async/init.lua @@ -1,16 +1,10 @@ core.log("info", "Initializing Asynchronous environment") -function core.job_processor(serialized_func, serialized_param) - local func = loadstring(serialized_func) +function core.job_processor(func, serialized_param) local param = core.deserialize(serialized_param) - local retval = nil - if type(func) == "function" then - retval = core.serialize(func(param)) - else - core.log("error", "ASYNC WORKER: Unable to deserialize function") - end + local retval = core.serialize(func(param)) return retval or core.serialize(nil) end diff --git a/builtin/client/death_formspec.lua b/builtin/client/death_formspec.lua index 6d6fd6fd9..ff484b32f 100644 --- a/builtin/client/death_formspec.lua +++ b/builtin/client/death_formspec.lua @@ -1,8 +1,8 @@ local death_formspec = "" .. "size[11,5.5]" .. "bgcolor[#320000b4;true]" - .. "label[4.85,1.35;" .. "You died" .. "]" - .. "button_exit[2,3;3,0.5;btn_respawn;" .. "Respawn" .. "]" + .. "label[4.85,1.35;" .. fgettext("You died") .. "]" + .. "button_exit[2,3;3,0.5;btn_respawn;" .. fgettext("Respawn") .. "]" .. "button_exit[6,3;3,0.5;btn_ghost_mode;" .. "Ghost Mode" .. "]" .. "set_focus[btn_respawn;true]" diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index 324d83b07..e4bc056d9 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -209,14 +209,7 @@ end -------------------------------------------------------------------------------- function math.hypot(x, y) - local t - x = math.abs(x) - y = math.abs(y) - t = math.min(x, y) - x = math.max(x, y) - if x == 0 then return 0 end - t = t / x - return x * math.sqrt(1 + t * t) + return math.sqrt(x * x + y * y) end -------------------------------------------------------------------------------- @@ -432,21 +425,19 @@ function core.string_to_pos(value) return nil end - local p = {} - p.x, p.y, p.z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") - if p.x and p.y and p.z then - p.x = tonumber(p.x) - p.y = tonumber(p.y) - p.z = tonumber(p.z) - return p - end - p = {} - p.x, p.y, p.z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$") - if p.x and p.y and p.z then - p.x = tonumber(p.x) - p.y = tonumber(p.y) - p.z = tonumber(p.z) - return p + local x, y, z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") + if x and y and z then + x = tonumber(x) + y = tonumber(y) + z = tonumber(z) + return vector.new(x, y, z) + end + x, y, z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$") + if x and y and z then + x = tonumber(x) + y = tonumber(y) + z = tonumber(z) + return vector.new(x, y, z) end return nil end diff --git a/builtin/common/tests/misc_helpers_spec.lua b/builtin/common/tests/misc_helpers_spec.lua index bb9d13e7f..b16987f0b 100644 --- a/builtin/common/tests/misc_helpers_spec.lua +++ b/builtin/common/tests/misc_helpers_spec.lua @@ -1,4 +1,5 @@ _G.core = {} +dofile("builtin/common/vector.lua") dofile("builtin/common/misc_helpers.lua") describe("string", function() @@ -55,8 +56,8 @@ end) describe("pos", function() it("from string", function() - assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("10.0, 5.1, -2")) - assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("( 10.0, 5.1, -2)")) + assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("10.0, 5.1, -2")) + assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("( 10.0, 5.1, -2)")) assert.is_nil(core.string_to_pos("asd, 5, -2)")) end) diff --git a/builtin/common/tests/serialize_spec.lua b/builtin/common/tests/serialize_spec.lua index 17c6a60f7..e46b7dcc5 100644 --- a/builtin/common/tests/serialize_spec.lua +++ b/builtin/common/tests/serialize_spec.lua @@ -3,6 +3,7 @@ _G.core = {} _G.setfenv = require 'busted.compatibility'.setfenv dofile("builtin/common/serialize.lua") +dofile("builtin/common/vector.lua") describe("serialize", function() it("works", function() @@ -53,4 +54,16 @@ describe("serialize", function() assert.is_nil(test_out.func) assert.equals(test_out.foo, "bar") end) + + it("vectors work", function() + local v = vector.new(1, 2, 3) + assert.same({{x = 1, y = 2, z = 3}}, core.deserialize(core.serialize({v}))) + assert.same({x = 1, y = 2, z = 3}, core.deserialize(core.serialize(v))) + + -- abuse + v = vector.new(1, 2, 3) + v.a = "bla" + assert.same({x = 1, y = 2, z = 3, a = "bla"}, + core.deserialize(core.serialize(v))) + end) end) diff --git a/builtin/common/tests/vector_spec.lua b/builtin/common/tests/vector_spec.lua index 104c656e9..2a50e2889 100644 --- a/builtin/common/tests/vector_spec.lua +++ b/builtin/common/tests/vector_spec.lua @@ -4,14 +4,20 @@ dofile("builtin/common/vector.lua") describe("vector", function() describe("new()", function() it("constructs", function() - assert.same({ x = 0, y = 0, z = 0 }, vector.new()) - assert.same({ x = 1, y = 2, z = 3 }, vector.new(1, 2, 3)) - assert.same({ x = 3, y = 2, z = 1 }, vector.new({ x = 3, y = 2, z = 1 })) + assert.same({x = 0, y = 0, z = 0}, vector.new()) + assert.same({x = 1, y = 2, z = 3}, vector.new(1, 2, 3)) + assert.same({x = 3, y = 2, z = 1}, vector.new({x = 3, y = 2, z = 1})) + + assert.is_true(vector.check(vector.new())) + assert.is_true(vector.check(vector.new(1, 2, 3))) + assert.is_true(vector.check(vector.new({x = 3, y = 2, z = 1}))) local input = vector.new({ x = 3, y = 2, z = 1 }) local output = vector.new(input) assert.same(input, output) - assert.are_not.equal(input, output) + assert.equal(input, output) + assert.is_false(rawequal(input, output)) + assert.equal(input, input:new()) end) it("throws on invalid input", function() @@ -25,32 +31,271 @@ describe("vector", function() end) end) - it("equal()", function() - local function assertE(a, b) - assert.is_true(vector.equals(a, b)) - end - local function assertNE(a, b) - assert.is_false(vector.equals(a, b)) - end + it("zero()", function() + assert.same({x = 0, y = 0, z = 0}, vector.zero()) + assert.same(vector.new(), vector.zero()) + assert.equal(vector.new(), vector.zero()) + assert.is_true(vector.check(vector.zero())) + end) - assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0}) - assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1}) - local a = { x = 2, y = 4, z = -10 } - assertE(a, a) - assertNE({x = -1, y = 0, z = 1}, a) + it("copy()", function() + local v = vector.new(1, 2, 3) + assert.same(v, vector.copy(v)) + assert.same(vector.new(v), vector.copy(v)) + assert.equal(vector.new(v), vector.copy(v)) + assert.is_true(vector.check(vector.copy(v))) end) - it("add()", function() - assert.same({ x = 2, y = 4, z = 6 }, vector.add(vector.new(1, 2, 3), { x = 1, y = 2, z = 3 })) + it("indexes", function() + local some_vector = vector.new(24, 42, 13) + assert.equal(24, some_vector[1]) + assert.equal(24, some_vector.x) + assert.equal(42, some_vector[2]) + assert.equal(42, some_vector.y) + assert.equal(13, some_vector[3]) + assert.equal(13, some_vector.z) + + some_vector[1] = 100 + assert.equal(100, some_vector.x) + some_vector.x = 101 + assert.equal(101, some_vector[1]) + + some_vector[2] = 100 + assert.equal(100, some_vector.y) + some_vector.y = 102 + assert.equal(102, some_vector[2]) + + some_vector[3] = 100 + assert.equal(100, some_vector.z) + some_vector.z = 103 + assert.equal(103, some_vector[3]) + end) + + it("direction()", function() + local a = vector.new(1, 0, 0) + local b = vector.new(1, 42, 0) + assert.equal(vector.new(0, 1, 0), vector.direction(a, b)) + assert.equal(vector.new(0, 1, 0), a:direction(b)) + end) + + it("distance()", function() + local a = vector.new(1, 0, 0) + local b = vector.new(3, 42, 9) + assert.is_true(math.abs(43 - vector.distance(a, b)) < 1.0e-12) + assert.is_true(math.abs(43 - a:distance(b)) < 1.0e-12) + assert.equal(0, vector.distance(a, a)) + assert.equal(0, b:distance(b)) + end) + + it("length()", function() + local a = vector.new(0, 0, -23) + assert.equal(0, vector.length(vector.new())) + assert.equal(23, vector.length(a)) + assert.equal(23, a:length()) + end) + + it("normalize()", function() + local a = vector.new(0, 0, -23) + assert.equal(vector.new(0, 0, -1), vector.normalize(a)) + assert.equal(vector.new(0, 0, -1), a:normalize()) + assert.equal(vector.new(), vector.normalize(vector.new())) + end) + + it("floor()", function() + local a = vector.new(0.1, 0.9, -0.5) + assert.equal(vector.new(0, 0, -1), vector.floor(a)) + assert.equal(vector.new(0, 0, -1), a:floor()) + end) + + it("round()", function() + local a = vector.new(0.1, 0.9, -0.5) + assert.equal(vector.new(0, 1, -1), vector.round(a)) + assert.equal(vector.new(0, 1, -1), a:round()) + end) + + it("apply()", function() + local i = 0 + local f = function(x) + i = i + 1 + return x + i + end + local a = vector.new(0.1, 0.9, -0.5) + assert.equal(vector.new(1, 1, 0), vector.apply(a, math.ceil)) + assert.equal(vector.new(1, 1, 0), a:apply(math.ceil)) + assert.equal(vector.new(0.1, 0.9, 0.5), vector.apply(a, math.abs)) + assert.equal(vector.new(0.1, 0.9, 0.5), a:apply(math.abs)) + assert.equal(vector.new(1.1, 2.9, 2.5), vector.apply(a, f)) + assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f)) + end) + + it("equals()", function() + local function assertE(a, b) + assert.is_true(vector.equals(a, b)) + end + local function assertNE(a, b) + assert.is_false(vector.equals(a, b)) + end + + assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0}) + assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1}) + assertE({x = -1, y = 0, z = 1}, vector.new(-1, 0, 1)) + local a = {x = 2, y = 4, z = -10} + assertE(a, a) + assertNE({x = -1, y = 0, z = 1}, a) + + assert.equal(vector.new(1, 2, 3), vector.new(1, 2, 3)) + assert.is_true(vector.new(1, 2, 3):equals(vector.new(1, 2, 3))) + assert.not_equal(vector.new(1, 2, 3), vector.new(1, 2, 4)) + assert.is_true(vector.new(1, 2, 3) == vector.new(1, 2, 3)) + assert.is_false(vector.new(1, 2, 3) == vector.new(1, 3, 3)) + end) + + it("metatable is same", function() + local a = vector.new() + local b = vector.new(1, 2, 3) + + assert.equal(true, vector.check(a)) + assert.equal(true, vector.check(b)) + + assert.equal(vector.metatable, getmetatable(a)) + assert.equal(vector.metatable, getmetatable(b)) + assert.equal(vector.metatable, a.metatable) + end) + + it("sort()", function() + local a = vector.new(1, 2, 3) + local b = vector.new(0.5, 232, -2) + local sorted = {vector.new(0.5, 2, -2), vector.new(1, 232, 3)} + assert.same(sorted, {vector.sort(a, b)}) + assert.same(sorted, {a:sort(b)}) + end) + + it("angle()", function() + assert.equal(math.pi, vector.angle(vector.new(-1, -2, -3), vector.new(1, 2, 3))) + assert.equal(math.pi/2, vector.new(0, 1, 0):angle(vector.new(1, 0, 0))) + end) + + it("dot()", function() + assert.equal(-14, vector.dot(vector.new(-1, -2, -3), vector.new(1, 2, 3))) + assert.equal(0, vector.new():dot(vector.new(1, 2, 3))) + end) + + it("cross()", function() + local a = vector.new(-1, -2, 0) + local b = vector.new(1, 2, 3) + assert.equal(vector.new(-6, 3, 0), vector.cross(a, b)) + assert.equal(vector.new(-6, 3, 0), a:cross(b)) end) it("offset()", function() - assert.same({ x = 41, y = 52, z = 63 }, vector.offset(vector.new(1, 2, 3), 40, 50, 60)) + assert.same({x = 41, y = 52, z = 63}, vector.offset(vector.new(1, 2, 3), 40, 50, 60)) + assert.equal(vector.new(41, 52, 63), vector.offset(vector.new(1, 2, 3), 40, 50, 60)) + assert.equal(vector.new(41, 52, 63), vector.new(1, 2, 3):offset(40, 50, 60)) + end) + + it("is()", function() + local some_table1 = {foo = 13, [42] = 1, "bar", 2} + local some_table2 = {1, 2, 3} + local some_table3 = {x = 1, 2, 3} + local some_table4 = {1, 2, z = 3} + local old = {x = 1, y = 2, z = 3} + local real = vector.new(1, 2, 3) + + assert.is_false(vector.check(nil)) + assert.is_false(vector.check(1)) + assert.is_false(vector.check(true)) + assert.is_false(vector.check("foo")) + assert.is_false(vector.check(some_table1)) + assert.is_false(vector.check(some_table2)) + assert.is_false(vector.check(some_table3)) + assert.is_false(vector.check(some_table4)) + assert.is_false(vector.check(old)) + assert.is_true(vector.check(real)) + assert.is_true(real:check()) + end) + + it("global pairs", function() + local out = {} + local vec = vector.new(10, 20, 30) + for k, v in pairs(vec) do + out[k] = v + end + assert.same({x = 10, y = 20, z = 30}, out) + end) + + it("abusing works", function() + local v = vector.new(1, 2, 3) + v.a = 1 + assert.equal(1, v.a) + + local a_is_there = false + for key, value in pairs(v) do + if key == "a" then + a_is_there = true + assert.equal(value, 1) + break + end + end + assert.is_true(a_is_there) + end) + + it("add()", function() + local a = vector.new(1, 2, 3) + local b = vector.new(1, 4, 3) + local c = vector.new(2, 6, 6) + assert.equal(c, vector.add(a, {x = 1, y = 4, z = 3})) + assert.equal(c, vector.add(a, b)) + assert.equal(c, a:add(b)) + assert.equal(c, a + b) + assert.equal(c, b + a) + end) + + it("subtract()", function() + local a = vector.new(1, 2, 3) + local b = vector.new(2, 4, 3) + local c = vector.new(-1, -2, 0) + assert.equal(c, vector.subtract(a, {x = 2, y = 4, z = 3})) + assert.equal(c, vector.subtract(a, b)) + assert.equal(c, a:subtract(b)) + assert.equal(c, a - b) + assert.equal(c, -b + a) + end) + + it("multiply()", function() + local a = vector.new(1, 2, 3) + local b = vector.new(2, 4, 3) + local c = vector.new(2, 8, 9) + local s = 2 + local d = vector.new(2, 4, 6) + assert.equal(c, vector.multiply(a, {x = 2, y = 4, z = 3})) + assert.equal(c, vector.multiply(a, b)) + assert.equal(d, vector.multiply(a, s)) + assert.equal(d, a:multiply(s)) + assert.equal(d, a * s) + assert.equal(d, s * a) + assert.equal(-a, -1 * a) + end) + + it("divide()", function() + local a = vector.new(1, 2, 3) + local b = vector.new(2, 4, 3) + local c = vector.new(0.5, 0.5, 1) + local s = 2 + local d = vector.new(0.5, 1, 1.5) + assert.equal(c, vector.divide(a, {x = 2, y = 4, z = 3})) + assert.equal(c, vector.divide(a, b)) + assert.equal(d, vector.divide(a, s)) + assert.equal(d, a:divide(s)) + assert.equal(d, a / s) + assert.equal(d, 1/s * a) + assert.equal(-a, a / -1) end) it("to_string()", function() local v = vector.new(1, 2, 3.14) assert.same("(1, 2, 3.14)", vector.to_string(v)) + assert.same("(1, 2, 3.14)", v:to_string()) + assert.same("(1, 2, 3.14)", tostring(v)) end) it("from_string()", function() diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua index 2ef8fc617..02fc1bdee 100644 --- a/builtin/common/vector.lua +++ b/builtin/common/vector.lua @@ -1,15 +1,55 @@ +--[[ +Vector helpers +Note: The vector.*-functions must be able to accept old vectors that had no metatables +]] + +-- localize functions +local setmetatable = setmetatable vector = {} +local metatable = {} +vector.metatable = metatable + +local xyz = {"x", "y", "z"} + +-- only called when rawget(v, key) returns nil +function metatable.__index(v, key) + return rawget(v, xyz[key]) or vector[key] +end + +-- only called when rawget(v, key) returns nil +function metatable.__newindex(v, key, value) + rawset(v, xyz[key] or key, value) +end + +-- constructors + +local function fast_new(x, y, z) + return setmetatable({x = x, y = y, z = z}, metatable) +end + function vector.new(a, b, c) + if a and b and c then + return fast_new(a, b, c) + end + + -- deprecated, use vector.copy and vector.zero directly if type(a) == "table" then - assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()") - return {x=a.x, y=a.y, z=a.z} - elseif a then - assert(b and c, "Invalid arguments for vector.new()") - return {x=a, y=b, z=c} + return vector.copy(a) + else + assert(not a, "Invalid arguments for vector.new()") + return vector.zero() end - return {x=0, y=0, z=0} +end + +function vector.zero() + return fast_new(0, 0, 0) +end + +function vector.copy(v) + assert(v.x and v.y and v.z, "Invalid vector passed to vector.copy()") + return fast_new(v.x, v.y, v.z) end function vector.from_string(s, init) @@ -27,63 +67,60 @@ end function vector.to_string(v) return string.format("(%g, %g, %g)", v.x, v.y, v.z) end +metatable.__tostring = vector.to_string function vector.equals(a, b) return a.x == b.x and a.y == b.y and a.z == b.z end +metatable.__eq = vector.equals + +-- unary operations function vector.length(v) - return math.hypot(v.x, math.hypot(v.y, v.z)) + return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) end +-- Note: we can not use __len because it is already used for primitive table length function vector.normalize(v) local len = vector.length(v) if len == 0 then - return {x=0, y=0, z=0} + return fast_new(0, 0, 0) else return vector.divide(v, len) end end function vector.floor(v) - return { - x = math.floor(v.x), - y = math.floor(v.y), - z = math.floor(v.z) - } + return vector.apply(v, math.floor) end function vector.round(v) - return { - x = math.round(v.x), - y = math.round(v.y), - z = math.round(v.z) - } + return fast_new( + math.round(v.x), + math.round(v.y), + math.round(v.z) + ) end function vector.apply(v, func) - return { - x = func(v.x), - y = func(v.y), - z = func(v.z) - } + return fast_new( + func(v.x), + func(v.y), + func(v.z) + ) end function vector.distance(a, b) local x = a.x - b.x local y = a.y - b.y local z = a.z - b.z - return math.hypot(x, math.hypot(y, z)) + return math.sqrt(x * x + y * y + z * z) end function vector.direction(pos1, pos2) - return vector.normalize({ - x = pos2.x - pos1.x, - y = pos2.y - pos1.y, - z = pos2.z - pos1.z - }) + return vector.subtract(pos2, pos1):normalize() end function vector.angle(a, b) @@ -98,70 +135,137 @@ function vector.dot(a, b) end function vector.cross(a, b) - return { - x = a.y * b.z - a.z * b.y, - y = a.z * b.x - a.x * b.z, - z = a.x * b.y - a.y * b.x - } + return fast_new( + a.y * b.z - a.z * b.y, + a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x + ) +end + +function metatable.__unm(v) + return fast_new(-v.x, -v.y, -v.z) end +-- add, sub, mul, div operations + function vector.add(a, b) if type(b) == "table" then - return {x = a.x + b.x, - y = a.y + b.y, - z = a.z + b.z} + return fast_new( + a.x + b.x, + a.y + b.y, + a.z + b.z + ) else - return {x = a.x + b, - y = a.y + b, - z = a.z + b} + return fast_new( + a.x + b, + a.y + b, + a.z + b + ) end end +function metatable.__add(a, b) + return fast_new( + a.x + b.x, + a.y + b.y, + a.z + b.z + ) +end function vector.subtract(a, b) if type(b) == "table" then - return {x = a.x - b.x, - y = a.y - b.y, - z = a.z - b.z} + return fast_new( + a.x - b.x, + a.y - b.y, + a.z - b.z + ) else - return {x = a.x - b, - y = a.y - b, - z = a.z - b} + return fast_new( + a.x - b, + a.y - b, + a.z - b + ) end end +function metatable.__sub(a, b) + return fast_new( + a.x - b.x, + a.y - b.y, + a.z - b.z + ) +end function vector.multiply(a, b) if type(b) == "table" then - return {x = a.x * b.x, - y = a.y * b.y, - z = a.z * b.z} + return fast_new( + a.x * b.x, + a.y * b.y, + a.z * b.z + ) else - return {x = a.x * b, - y = a.y * b, - z = a.z * b} + return fast_new( + a.x * b, + a.y * b, + a.z * b + ) + end +end +function metatable.__mul(a, b) + if type(a) == "table" then + return fast_new( + a.x * b, + a.y * b, + a.z * b + ) + else + return fast_new( + a * b.x, + a * b.y, + a * b.z + ) end end function vector.divide(a, b) if type(b) == "table" then - return {x = a.x / b.x, - y = a.y / b.y, - z = a.z / b.z} + return fast_new( + a.x / b.x, + a.y / b.y, + a.z / b.z + ) else - return {x = a.x / b, - y = a.y / b, - z = a.z / b} + return fast_new( + a.x / b, + a.y / b, + a.z / b + ) end end +function metatable.__div(a, b) + -- scalar/vector makes no sense + return fast_new( + a.x / b, + a.y / b, + a.z / b + ) +end + +-- misc stuff function vector.offset(v, x, y, z) - return {x = v.x + x, - y = v.y + y, - z = v.z + z} + return fast_new( + v.x + x, + v.y + y, + v.z + z + ) end function vector.sort(a, b) - return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)}, - {x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)} + return fast_new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z)), + fast_new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z)) +end + +function vector.check(v) + return getmetatable(v) == metatable end local function sin(x) @@ -229,7 +333,7 @@ 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} + local rot = vector.new(math.asin(forward.y), -math.atan2(forward.x, forward.z), 0) if not up then return rot end @@ -237,7 +341,7 @@ function vector.dir_to_rotation(forward, up) "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) + local forwup = vector.rotate(vector.new(0, 1, 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) diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua index fc061666c..e7d502bb3 100644 --- a/builtin/game/auth.lua +++ b/builtin/game/auth.lua @@ -87,6 +87,10 @@ core.builtin_auth_handler = { core.settings:get("default_password"))) end + auth_entry.privileges = privileges + + core_auth.save(auth_entry) + -- Run grant callbacks for priv, _ in pairs(privileges) do if not auth_entry.privileges[priv] then @@ -100,9 +104,6 @@ core.builtin_auth_handler = { core.run_priv_callbacks(name, priv, nil, "revoke") end end - - auth_entry.privileges = privileges - core_auth.save(auth_entry) core.notify_authentication_modified(name) end, reload = function() diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index 0bd12c25f..99296f782 100644 --- a/builtin/game/chat.lua +++ b/builtin/game/chat.lua @@ -212,9 +212,14 @@ core.register_chatcommand("haspriv", { table.insert(players_with_priv, player_name) end end - return true, S("Players online with the \"@1\" privilege: @2", - param, - table.concat(players_with_priv, ", ")) + if #players_with_priv == 0 then + return true, S("No online player has the \"@1\" privilege.", + param) + else + return true, S("Players online with the \"@1\" privilege: @2", + param, + table.concat(players_with_priv, ", ")) + end end }) @@ -250,11 +255,11 @@ local function handle_grant_command(caller, grantname, grantprivstr) if privs_unknown ~= "" then return false, privs_unknown end + core.set_player_privs(grantname, privs) for priv, _ in pairs(grantprivs) do -- call the on_grant callbacks core.run_priv_callbacks(grantname, priv, caller, "grant") end - core.set_player_privs(grantname, privs) core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname) if grantname ~= caller then core.chat_send_player(grantname, @@ -354,13 +359,13 @@ local function handle_revoke_command(caller, revokename, revokeprivstr) end local revokecount = 0 + + core.set_player_privs(revokename, privs) for priv, _ in pairs(revokeprivs) do -- call the on_revoke callbacks core.run_priv_callbacks(revokename, priv, caller, "revoke") revokecount = revokecount + 1 end - - core.set_player_privs(revokename, privs) local new_privs = core.get_player_privs(revokename) if revokecount == 0 then @@ -499,10 +504,10 @@ core.register_chatcommand("remove_player", { -- pos may be a non-integer position local function find_free_position_near(pos) local tries = { - {x=1, y=0, z=0}, - {x=-1, y=0, z=0}, - {x=0, y=0, z=1}, - {x=0, y=0, z=-1}, + vector.new( 1, 0, 0), + vector.new(-1, 0, 0), + vector.new( 0, 0, 1), + vector.new( 0, 0, -1), } for _, d in ipairs(tries) do local p = vector.add(pos, d) @@ -737,7 +742,12 @@ core.register_chatcommand("mods", { description = S("List mods installed on the server"), privs = {}, func = function(name, param) - return true, table.concat(core.get_modnames(), ", ") + local mods = core.get_modnames() + if #mods == 0 then + return true, S("No mods installed.") + else + return true, table.concat(core.get_modnames(), ", ") + end end, }) @@ -1053,24 +1063,58 @@ core.register_chatcommand("days", { end }) +local function parse_shutdown_param(param) + local delay, reconnect, message + local one, two, three + one, two, three = param:match("^(%S+) +(%-r) +(.*)") + if one and two and three then + -- 3 arguments: delay, reconnect and message + return one, two, three + end + -- 2 arguments + one, two = param:match("^(%S+) +(.*)") + if one and two then + if tonumber(one) then + delay = one + if two == "-r" then + reconnect = two + else + message = two + end + elseif one == "-r" then + reconnect, message = one, two + end + return delay, reconnect, message + end + -- 1 argument + one = param:match("(.*)") + if tonumber(one) then + delay = one + elseif one == "-r" then + reconnect = one + else + message = one + end + return delay, reconnect, message +end + core.register_chatcommand("shutdown", { - params = S("[<delay_in_seconds> | -1] [reconnect] [<message>]"), - description = S("Shutdown server (-1 cancels a delayed shutdown)"), + params = S("[<delay_in_seconds> | -1] [-r] [<message>]"), + description = S("Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)"), privs = {server=true}, func = function(name, param) - local delay, reconnect, message - delay, param = param:match("^%s*(%S+)(.*)") - if param then - reconnect, param = param:match("^%s*(%S+)(.*)") + local delay, reconnect, message = parse_shutdown_param(param) + local bool_reconnect = reconnect == "-r" + if not message then + message = "" end - message = param and param:match("^%s*(.+)") or "" delay = tonumber(delay) or 0 if delay == 0 then core.log("action", name .. " shuts down server") core.chat_send_all("*** "..S("Server shutting down (operator request).")) end - core.request_shutdown(message:trim(), core.is_yes(reconnect), delay) + core.request_shutdown(message:trim(), bool_reconnect, delay) return true end, }) diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index 2cc0d8fac..29cb56aae 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -39,7 +39,7 @@ local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81 core.register_entity(":__builtin:falling_node", { initial_properties = { visual = "item", - visual_size = {x = SCALE, y = SCALE, z = SCALE}, + visual_size = vector.new(SCALE, SCALE, SCALE), textures = {}, physical = true, is_visible = false, @@ -96,7 +96,7 @@ core.register_entity(":__builtin:falling_node", { local vsize if def.visual_scale then local s = def.visual_scale - vsize = {x = s, y = s, z = s} + vsize = vector.new(s, s, s) end self.object:set_properties({ is_visible = true, @@ -111,15 +111,21 @@ core.register_entity(":__builtin:falling_node", { 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 - vsize = {x = s, y = s, z = s} + -- Calculate size of falling node + local s = {} + s.x = (def.visual_scale or 1) * SCALE + s.y = s.x + s.z = s.x + -- Compensate for wield_scale + if def.wield_scale then + s.x = s.x / def.wield_scale.x + s.y = s.y / def.wield_scale.y + s.z = s.z / def.wield_scale.z end self.object:set_properties({ is_visible = true, wield_item = itemstring, - visual_size = vsize, + visual_size = s, glow = def.light_source, }) end @@ -158,7 +164,8 @@ core.register_entity(":__builtin:falling_node", { if euler then self.object:set_rotation(euler) end - elseif (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike") then + elseif (def.drawtype ~= "plantlike" and def.drawtype ~= "plantlike_rooted" and + (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike")) then local rot = node.param2 % 8 if (def.drawtype == "signlike" and def.paramtype2 ~= "wallmounted" and def.paramtype2 ~= "colorwallmounted") then -- Change rotation to "floor" by default for non-wallmounted paramtype2 @@ -227,7 +234,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}) + self.object:set_acceleration(vector.new(0, -gravity, 0)) local ds = core.deserialize(staticdata) if ds and ds.node then @@ -303,7 +310,7 @@ core.register_entity(":__builtin:falling_node", { 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 bcp = pos:offset(0, -0.7, 0):round() local bcn = core.get_node(bcp) local bcd = core.registered_nodes[bcn.name] @@ -344,13 +351,12 @@ core.register_entity(":__builtin:falling_node", { -- 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})) + self.object:set_velocity(vector.new( + vel.x, + player_collision.old_velocity.y, + vel.z + )) + self.object:set_pos(self.object:get_pos():offset(0, -0.5, 0)) end return elseif bcn.name == "ignore" then @@ -430,7 +436,7 @@ local function drop_attached_node(p) if def and def.preserve_metadata then local oldmeta = core.get_meta(p):to_table().fields -- Copy pos and node because the callback can modify them. - local pos_copy = {x=p.x, y=p.y, z=p.z} + local pos_copy = vector.new(p) local node_copy = {name=n.name, param1=n.param1, param2=n.param2} local drop_stacks = {} for k, v in pairs(drops) do @@ -455,14 +461,14 @@ end function builtin_shared.check_attached_node(p, n) local def = core.registered_nodes[n.name] - local d = {x = 0, y = 0, z = 0} + local d = vector.new() if def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" then -- The fallback vector here is in case 'wallmounted to dir' is nil due -- to voxelmanip placing a wallmounted node without resetting a -- pre-existing param2 value that is out-of-range for wallmounted. -- The fallback vector corresponds to param2 = 0. - d = core.wallmounted_to_dir(n.param2) or {x = 0, y = 1, z = 0} + d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0) else d.y = -1 end @@ -482,7 +488,7 @@ end function core.check_single_for_falling(p) local n = core.get_node(p) if core.get_item_group(n.name, "falling_node") ~= 0 then - local p_bottom = {x = p.x, y = p.y - 1, z = p.z} + local p_bottom = vector.offset(p, 0, -1, 0) -- 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] @@ -521,17 +527,17 @@ end -- Down first as likely case, but always before self. The same with sides. -- Up must come last, so that things above self will also fall all at once. local check_for_falling_neighbors = { - {x = -1, y = -1, z = 0}, - {x = 1, y = -1, z = 0}, - {x = 0, y = -1, z = -1}, - {x = 0, y = -1, z = 1}, - {x = 0, y = -1, z = 0}, - {x = -1, y = 0, z = 0}, - {x = 1, y = 0, z = 0}, - {x = 0, y = 0, z = 1}, - {x = 0, y = 0, z = -1}, - {x = 0, y = 0, z = 0}, - {x = 0, y = 1, z = 0}, + vector.new(-1, -1, 0), + vector.new( 1, -1, 0), + vector.new( 0, -1, -1), + vector.new( 0, -1, 1), + vector.new( 0, -1, 0), + vector.new(-1, 0, 0), + vector.new( 1, 0, 0), + vector.new( 0, 0, 1), + vector.new( 0, 0, -1), + vector.new( 0, 0, 0), + vector.new( 0, 1, 0), } function core.check_for_falling(p) diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 8f0604448..583ef5092 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -20,6 +20,8 @@ core.features = { direct_velocity_on_players = true, use_texture_alpha_string_modes = true, degrotate_240_steps = true, + abm_min_max_y = true, + dynamic_add_media_table = true, } function core.has_feature(arg) diff --git a/builtin/game/forceloading.lua b/builtin/game/forceloading.lua index e1e00920c..8043e5dea 100644 --- a/builtin/game/forceloading.lua +++ b/builtin/game/forceloading.lua @@ -86,12 +86,6 @@ local function read_file(filename) return core.deserialize(t) or {} end -local function write_file(filename, table) - local f = io.open(filename, "w") - f:write(core.serialize(table)) - f:close() -end - blocks_forceloaded = read_file(wpath.."/force_loaded.txt") for _, __ in pairs(blocks_forceloaded) do total_forceloaded = total_forceloaded + 1 @@ -106,7 +100,8 @@ end) -- persists the currently forceloaded blocks to disk local function persist_forceloaded_blocks() - write_file(wpath.."/force_loaded.txt", blocks_forceloaded) + local data = core.serialize(blocks_forceloaded) + core.safe_file_write(wpath.."/force_loaded.txt", data) end -- periodical forceload persistence diff --git a/builtin/game/init.lua b/builtin/game/init.lua index 1d62be019..bb007fabd 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -7,8 +7,6 @@ local gamepath = scriptpath .. "game".. DIR_DELIM -- not exposed to outer context local builtin_shared = {} -dofile(commonpath .. "vector.lua") - dofile(gamepath .. "constants.lua") assert(loadfile(gamepath .. "item.lua"))(builtin_shared) dofile(gamepath .. "register.lua") diff --git a/builtin/game/item.lua b/builtin/game/item.lua index c4d93abd6..92818b177 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -70,12 +70,12 @@ end -- Table of possible dirs local facedir_to_dir = { - {x= 0, y=0, z= 1}, - {x= 1, y=0, z= 0}, - {x= 0, y=0, z=-1}, - {x=-1, y=0, z= 0}, - {x= 0, y=-1, z= 0}, - {x= 0, y=1, z= 0}, + vector.new( 0, 0, 1), + vector.new( 1, 0, 0), + vector.new( 0, 0, -1), + vector.new(-1, 0, 0), + vector.new( 0, -1, 0), + vector.new( 0, 1, 0), } -- Mapping from facedir value to index in facedir_to_dir. local facedir_to_dir_map = { @@ -114,12 +114,12 @@ end -- table of dirs in wallmounted order local wallmounted_to_dir = { - [0] = {x = 0, y = 1, z = 0}, - {x = 0, y = -1, z = 0}, - {x = 1, y = 0, z = 0}, - {x = -1, y = 0, z = 0}, - {x = 0, y = 0, z = 1}, - {x = 0, y = 0, z = -1}, + [0] = vector.new( 0, 1, 0), + vector.new( 0, -1, 0), + vector.new( 1, 0, 0), + vector.new(-1, 0, 0), + vector.new( 0, 0, 1), + vector.new( 0, 0, -1), } function core.wallmounted_to_dir(wallmounted) return wallmounted_to_dir[wallmounted % 8] @@ -130,7 +130,7 @@ function core.dir_to_yaw(dir) end function core.yaw_to_dir(yaw) - return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)} + return vector.new(-math.sin(yaw), 0, math.cos(yaw)) end function core.is_colored_paramtype(ptype) @@ -153,6 +153,18 @@ function core.strip_param2_color(param2, paramtype2) return param2 end +local function has_all_groups(tbl, required_groups) + if type(required_groups) == "string" then + return (tbl[required_groups] or 0) ~= 0 + end + for _, group in ipairs(required_groups) do + if (tbl[group] or 0) == 0 then + return false + end + end + return true +end + function core.get_node_drops(node, toolname) -- Compatibility, if node is string local nodename = node @@ -192,7 +204,7 @@ function core.get_node_drops(node, toolname) if item.rarity ~= nil then good_rarity = item.rarity < 1 or math.random(item.rarity) == 1 end - if item.tools ~= nil then + if item.tools ~= nil or item.tool_groups ~= nil then good_tool = false end if item.tools ~= nil and toolname then @@ -207,6 +219,27 @@ function core.get_node_drops(node, toolname) end end end + if item.tool_groups ~= nil and toolname then + local tooldef = core.registered_items[toolname] + if tooldef ~= nil and type(tooldef.groups) == "table" then + if type(item.tool_groups) == "string" then + -- tool_groups can be a string which specifies the required group + good_tool = core.get_item_group(toolname, item.tool_groups) ~= 0 + else + -- tool_groups can be a list of sufficient requirements. + -- i.e. if any item in the list can be satisfied then the tool is good + assert(type(item.tool_groups) == "table") + for _, required_groups in ipairs(item.tool_groups) do + -- required_groups can be either a string (a single group), + -- or an array of strings where all must be in tooldef.groups + good_tool = has_all_groups(tooldef.groups, required_groups) + if good_tool then + break + end + end + end + end + end if good_rarity and good_tool then got_count = got_count + 1 for _, add_item in ipairs(item.items) do @@ -268,12 +301,12 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2, end -- Place above pointed node - local place_to = {x = above.x, y = above.y, z = above.z} + local place_to = vector.new(above) -- If node under is buildable_to, place into it instead (eg. snow) if olddef_under.buildable_to then log("info", "node under is buildable to") - place_to = {x = under.x, y = under.y, z = under.z} + place_to = vector.new(under) end if core.is_protected(place_to, playername) then @@ -293,22 +326,14 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2, newnode.param2 = def.place_param2 elseif (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted") and not param2 then - local dir = { - x = under.x - above.x, - y = under.y - above.y, - z = under.z - above.z - } + local dir = vector.subtract(under, above) newnode.param2 = core.dir_to_wallmounted(dir) -- Calculate the direction for furnaces and chests and stuff elseif (def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir") and not param2 then local placer_pos = placer and placer:get_pos() if placer_pos then - local dir = { - x = above.x - placer_pos.x, - y = above.y - placer_pos.y, - z = above.z - placer_pos.z - } + local dir = vector.subtract(above, placer_pos) newnode.param2 = core.dir_to_facedir(dir) log("info", "facedir: " .. newnode.param2) end @@ -362,7 +387,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2, -- Run callback if def.after_place_node and not prevent_after_place then -- Deepcopy place_to and pointed_thing because callback can modify it - local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} + local place_to_copy = vector.new(place_to) local pointed_thing_copy = copy_pointed_thing(pointed_thing) if def.after_place_node(place_to_copy, placer, itemstack, pointed_thing_copy) then @@ -373,7 +398,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2, -- Run script hook for _, callback in ipairs(core.registered_on_placenodes) do -- Deepcopy pos, node and pointed_thing because callback can modify them - local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} + local place_to_copy = vector.new(place_to) local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2} local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2} local pointed_thing_copy = copy_pointed_thing(pointed_thing) @@ -519,11 +544,11 @@ function core.handle_node_drops(pos, drops, digger) for _, dropped_item in pairs(drops) do local left = give_item(dropped_item) if not left:is_empty() then - local p = { - x = pos.x + math.random()/2-0.25, - y = pos.y + math.random()/2-0.25, - z = pos.z + math.random()/2-0.25, - } + local p = vector.offset(pos, + math.random()/2-0.25, + math.random()/2-0.25, + math.random()/2-0.25 + ) core.add_item(p, left) end end @@ -582,7 +607,7 @@ function core.node_dig(pos, node, digger) if def and def.preserve_metadata then local oldmeta = core.get_meta(pos):to_table().fields -- Copy pos and node because the callback can modify them. - local pos_copy = {x=pos.x, y=pos.y, z=pos.z} + local pos_copy = vector.new(pos) local node_copy = {name=node.name, param1=node.param1, param2=node.param2} local drop_stacks = {} for k, v in pairs(drops) do @@ -614,7 +639,7 @@ function core.node_dig(pos, node, digger) -- Run callback if def and def.after_dig_node then -- Copy pos and node because callback can modify them - local pos_copy = {x=pos.x, y=pos.y, z=pos.z} + local pos_copy = vector.new(pos) local node_copy = {name=node.name, param1=node.param1, param2=node.param2} def.after_dig_node(pos_copy, node_copy, oldmetadata, digger) end @@ -627,7 +652,7 @@ function core.node_dig(pos, node, digger) end -- Copy pos and node because callback can modify them - local pos_copy = {x=pos.x, y=pos.y, z=pos.z} + local pos_copy = vector.new(pos) local node_copy = {name=node.name, param1=node.param1, param2=node.param2} callback(pos_copy, node_copy, digger) end @@ -670,7 +695,7 @@ core.nodedef_default = { groups = {}, inventory_image = "", wield_image = "", - wield_scale = {x=1,y=1,z=1}, + wield_scale = vector.new(1, 1, 1), stack_max = default_stack_max, usable = false, liquids_pointable = false, @@ -729,7 +754,7 @@ core.craftitemdef_default = { groups = {}, inventory_image = "", wield_image = "", - wield_scale = {x=1,y=1,z=1}, + wield_scale = vector.new(1, 1, 1), stack_max = default_stack_max, liquids_pointable = false, tool_capabilities = nil, @@ -748,7 +773,7 @@ core.tooldef_default = { groups = {}, inventory_image = "", wield_image = "", - wield_scale = {x=1,y=1,z=1}, + wield_scale = vector.new(1, 1, 1), stack_max = 1, liquids_pointable = false, tool_capabilities = nil, @@ -767,7 +792,7 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items groups = {}, inventory_image = "", wield_image = "", - wield_scale = {x=1,y=1,z=1}, + wield_scale = vector.new(1, 1, 1), stack_max = default_stack_max, liquids_pointable = false, tool_capabilities = nil, diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index fcb86146d..63d64817c 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -119,13 +119,12 @@ end function core.get_position_from_hash(hash) - local pos = {} - pos.x = (hash % 65536) - 32768 + local x = (hash % 65536) - 32768 hash = math.floor(hash / 65536) - pos.y = (hash % 65536) - 32768 + local y = (hash % 65536) - 32768 hash = math.floor(hash / 65536) - pos.z = (hash % 65536) - 32768 - return pos + local z = (hash % 65536) - 32768 + return vector.new(x, y, z) end @@ -215,7 +214,7 @@ function core.is_area_protected(minp, maxp, player_name, interval) local y = math.floor(yf + 0.5) for xf = minp.x, maxp.x, d.x do local x = math.floor(xf + 0.5) - local pos = {x = x, y = y, z = z} + local pos = vector.new(x, y, z) if core.is_protected(pos, player_name) then return pos end @@ -270,24 +269,44 @@ function core.cancel_shutdown_requests() end --- Callback handling for dynamic_add_media +-- Used for callback handling with dynamic_add_media +core.dynamic_media_callbacks = {} -local dynamic_add_media_raw = core.dynamic_add_media_raw -core.dynamic_add_media_raw = nil -function core.dynamic_add_media(filepath, callback) - local ret = dynamic_add_media_raw(filepath) - if ret == false then - return ret + +-- PNG encoder safety wrapper + +local o_encode_png = core.encode_png +function core.encode_png(width, height, data, compression) + if type(width) ~= "number" then + error("Incorrect type for 'width', expected number, got " .. type(width)) end - if callback == nil then - core.log("deprecated", "Calling minetest.dynamic_add_media without ".. - "a callback is deprecated and will stop working in future versions.") - else - -- At the moment async loading is not actually implemented, so we - -- immediately call the callback ourselves - for _, name in ipairs(ret) do - callback(name) + if type(height) ~= "number" then + error("Incorrect type for 'height', expected number, got " .. type(height)) + end + + local expected_byte_count = width * height * 4 + + if type(data) ~= "table" and type(data) ~= "string" then + error("Incorrect type for 'height', expected table or string, got " .. type(height)) + end + + local data_length = type(data) == "table" and #data * 4 or string.len(data) + + if data_length ~= expected_byte_count then + error(string.format( + "Incorrect length of 'data', width and height imply %d bytes but %d were provided", + expected_byte_count, + data_length + )) + end + + if type(data) == "table" then + local dataBuf = {} + for i = 1, #data do + dataBuf[i] = core.colorspec_to_bytes(data[i]) end + data = table.concat(dataBuf) end - return true + + return o_encode_png(width, height, data, compression or 6) end diff --git a/builtin/game/privileges.lua b/builtin/game/privileges.lua index 1d3efb525..97681655e 100644 --- a/builtin/game/privileges.lua +++ b/builtin/game/privileges.lua @@ -97,10 +97,13 @@ core.register_privilege("rollback", { description = S("Can use the rollback functionality"), give_to_singleplayer = false, }) +core.register_privilege("basic_debug", { + description = S("Can view more debug info that might give a gameplay advantage"), + give_to_singleplayer = false, +}) core.register_privilege("debug", { - description = S("Allows enabling various debug options that may affect gameplay"), + description = S("Can enable wireframe"), give_to_singleplayer = false, - give_to_admin = true, }) core.register_can_bypass_userlimit(function(name, ip) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index c07535855..56e40c75c 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -610,6 +610,7 @@ core.registered_on_modchannel_message, core.register_on_modchannel_message = mak core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration() core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration() core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_registration() +core.registered_on_liquid_transformed, core.register_on_liquid_transformed = make_registration() -- -- Compatibility for on_mapgen_init() diff --git a/builtin/game/voxelarea.lua b/builtin/game/voxelarea.lua index 724761414..64436bf1a 100644 --- a/builtin/game/voxelarea.lua +++ b/builtin/game/voxelarea.lua @@ -1,6 +1,6 @@ VoxelArea = { - MinEdge = {x=1, y=1, z=1}, - MaxEdge = {x=0, y=0, z=0}, + MinEdge = vector.new(1, 1, 1), + MaxEdge = vector.new(0, 0, 0), ystride = 0, zstride = 0, } @@ -19,11 +19,11 @@ end function VoxelArea:getExtent() local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge - return { - x = MaxEdge.x - MinEdge.x + 1, - y = MaxEdge.y - MinEdge.y + 1, - z = MaxEdge.z - MinEdge.z + 1, - } + return vector.new( + MaxEdge.x - MinEdge.x + 1, + MaxEdge.y - MinEdge.y + 1, + MaxEdge.z - MinEdge.z + 1 + ) end function VoxelArea:getVolume() diff --git a/builtin/init.lua b/builtin/init.lua index 171742ae1..8bb69a33d 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -30,6 +30,7 @@ local clientpath = scriptdir .. "client" .. DIR_DELIM local commonpath = scriptdir .. "common" .. DIR_DELIM local asyncpath = scriptdir .. "async" .. DIR_DELIM +dofile(commonpath .. "vector.lua") dofile(commonpath .. "strict.lua") dofile(commonpath .. "serialize.lua") dofile(commonpath .. "misc_helpers.lua") diff --git a/builtin/locale/__builtin.de.tr b/builtin/locale/__builtin.de.tr index e8bc1fd84..aa40ffc8d 100644 --- a/builtin/locale/__builtin.de.tr +++ b/builtin/locale/__builtin.de.tr @@ -21,6 +21,7 @@ Player @1 does not exist.=Spieler @1 existiert nicht. Return list of all online players with privilege=Liste aller Spieler mit einem Privileg ausgeben Invalid parameters (see /help haspriv).=Ungültige Parameter (siehe „/help haspriv“). Unknown privilege!=Unbekanntes Privileg! +No online player has the "@1" privilege.=Kein online spielender Spieler hat das „@1“-Privileg. Players online with the "@1" privilege: @2=Derzeit online spielende Spieler mit dem „@1“-Privileg: @2 Your privileges are insufficient.=Ihre Privilegien sind unzureichend. Your privileges are insufficient. '@1' only allows you to grant: @2=Ihre Privilegien sind unzureichend. Mit „@1“ können Sie nur folgendes gewähren: @2 @@ -87,6 +88,7 @@ Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in Successfully reset light in the area ranging from @1 to @2.=Das Licht im Gebiet zwischen @1 und @2 wurde erfolgreich zurückgesetzt. Failed to load one or more blocks in area.=Fehlgeschlagen: Ein oder mehrere Kartenblöcke im Gebiet konnten nicht geladen werden. List mods installed on the server=Installierte Mods auf dem Server auflisten +No mods installed.=Es sind keine Mods installiert. Cannot give an empty item.=Ein leerer Gegenstand kann nicht gegeben werden. Cannot give an unknown item.=Ein unbekannter Gegenstand kann nicht gegeben werden. Giving 'ignore' is not allowed.=„ignore“ darf nicht gegeben werden. @@ -143,8 +145,8 @@ Invalid hour (must be between 0 and 23 inclusive).=Ungültige Stunde (muss zwisc Invalid minute (must be between 0 and 59 inclusive).=Ungültige Minute (muss zwischen 0 und 59 inklusive liegen). Show day count since world creation=Anzahl Tage seit der Erschaffung der Welt anzeigen Current day is @1.=Aktueller Tag ist @1. -[<delay_in_seconds> | -1] [reconnect] [<message>]=[<Verzögerung_in_Sekunden> | -1] [reconnect] [<Nachricht>] -Shutdown server (-1 cancels a delayed shutdown)=Server herunterfahren (-1 bricht einen verzögerten Abschaltvorgang ab) +[<delay_in_seconds> | -1] [-r] [<message>]=[<Verzögerung_in_Sekunden> | -1] [-r] [<Nachricht>] +Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=Server herunterfahren (-1 bricht einen verzögerten Abschaltvorgang ab, -r erlaubt Spielern, sich wiederzuverbinden) Server shutting down (operator request).=Server wird heruntergefahren (Betreiberanfrage). Ban the IP of a player or show the ban list=Die IP eines Spielers verbannen oder die Bannliste anzeigen The ban list is empty.=Die Bannliste ist leer. @@ -191,6 +193,7 @@ Available commands:=Verfügbare Befehle: Command not available: @1=Befehl nicht verfügbar: @1 [all | privs | <cmd>]=[all | privs | <Befehl>] Get help for commands or list privileges=Hilfe für Befehle erhalten oder Privilegien auflisten +Available privileges:=Verfügbare Privilegien: Command=Befehl Parameters=Parameter For more information, click on any entry in the list.=Für mehr Informationen klicken Sie auf einen beliebigen Eintrag in der Liste. @@ -200,7 +203,6 @@ Available commands: (see also: /help <cmd>)=Verfügbare Befehle: (siehe auch: /h Close=Schließen Privilege=Privileg Description=Beschreibung -Available privileges:=Verfügbare Privilegien: print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<Filter>] | dump [<Filter>] | save [<Format> [<Filter>]] Handle the profiler and profiling data=Den Profiler und Profilingdaten verwalten Statistics written to action log.=Statistiken zum Aktionsprotokoll geschrieben. diff --git a/builtin/locale/__builtin.it.tr b/builtin/locale/__builtin.it.tr index 8bce1d0d2..449c2b85e 100644 --- a/builtin/locale/__builtin.it.tr +++ b/builtin/locale/__builtin.it.tr @@ -21,6 +21,7 @@ Player @1 does not exist.=Il giocatore @1 non esiste. Return list of all online players with privilege=Ritorna una lista di tutti i giocatori connessi col tale privilegio Invalid parameters (see /help haspriv).=Parametri non validi (vedi /help haspriv). Unknown privilege!=Privilegio sconosciuto! +No online player has the "@1" privilege.= Players online with the "@1" privilege: @2=Giocatori connessi con il privilegio "@1": @2 Your privileges are insufficient.=I tuoi privilegi sono insufficienti. Your privileges are insufficient. '@1' only allows you to grant: @2= @@ -87,6 +88,7 @@ Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in Successfully reset light in the area ranging from @1 to @2.=Luce nell'area tra @1 e @2 reimpostata con successo. Failed to load one or more blocks in area.=Errore nel caricare uno o più blocchi mappa nell'area. List mods installed on the server=Elenca le mod installate nel server +No mods installed.= Cannot give an empty item.=Impossibile dare un oggetto vuoto. Cannot give an unknown item.=Impossibile dare un oggetto sconosciuto. Giving 'ignore' is not allowed.=Non è permesso dare 'ignore'. @@ -143,8 +145,8 @@ Invalid hour (must be between 0 and 23 inclusive).=Ora non valida (deve essere t Invalid minute (must be between 0 and 59 inclusive).=Minuto non valido (deve essere tra 0 e 59 inclusi) Show day count since world creation=Mostra il conteggio dei giorni da quando il mondo è stato creato Current day is @1.=Giorno attuale: @1. -[<delay_in_seconds> | -1] [reconnect] [<message>]=[<ritardo_in_secondi> | -1] [reconnect] [<messaggio>] -Shutdown server (-1 cancels a delayed shutdown)=Arresta il server (-1 annulla un arresto programmato) +[<delay_in_seconds> | -1] [-r] [<message>]= +Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)= Server shutting down (operator request).=Arresto del server in corso (per richiesta dell'operatore) Ban the IP of a player or show the ban list=Bandisce l'IP del giocatore o mostra la lista di quelli banditi The ban list is empty.=La lista banditi è vuota. @@ -191,6 +193,7 @@ Available commands:=Comandi disponibili: Command not available: @1=Comando non disponibile: @1 [all | privs | <cmd>]=[all | privs | <comando>] Get help for commands or list privileges=Richiama la finestra d'aiuto dei comandi o dei privilegi +Available privileges:=Privilegi disponibili: Command=Comando Parameters=Parametri For more information, click on any entry in the list.=Per più informazioni, clicca su una qualsiasi voce dell'elenco. @@ -200,7 +203,6 @@ Available commands: (see also: /help <cmd>)=Comandi disponibili: (vedi anche /he Close=Chiudi Privilege=Privilegio Description=Descrizione -Available privileges:=Privilegi disponibili: print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<filtro>] | dump [<filtro>] | save [<formato> [<filtro>]] | reset Handle the profiler and profiling data=Gestisce il profiler e i dati da esso elaborati Statistics written to action log.=Statistiche scritte nel log delle azioni. @@ -242,6 +244,8 @@ Profile saved to @1= ##### not used anymore ##### +[<delay_in_seconds> | -1] [reconnect] [<message>]=[<ritardo_in_secondi> | -1] [reconnect] [<messaggio>] +Shutdown server (-1 cancels a delayed shutdown)=Arresta il server (-1 annulla un arresto programmato) <name> (<privilege> | all)=<nome> (<privilegio> | all) <privilege> | all=<privilegio> | all Can modify 'shout' and 'interact' privileges=Si possono modificare i privilegi 'shout' e 'interact' diff --git a/builtin/locale/template.txt b/builtin/locale/template.txt index db0ee07b8..7049dde36 100644 --- a/builtin/locale/template.txt +++ b/builtin/locale/template.txt @@ -21,6 +21,7 @@ Player @1 does not exist.= Return list of all online players with privilege= Invalid parameters (see /help haspriv).= Unknown privilege!= +No online player has the "@1" privilege.= Players online with the "@1" privilege: @2= Your privileges are insufficient.= Your privileges are insufficient. '@1' only allows you to grant: @2= @@ -87,6 +88,7 @@ Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in Successfully reset light in the area ranging from @1 to @2.= Failed to load one or more blocks in area.= List mods installed on the server= +No mods installed.= Cannot give an empty item.= Cannot give an unknown item.= Giving 'ignore' is not allowed.= @@ -143,8 +145,8 @@ Invalid hour (must be between 0 and 23 inclusive).= Invalid minute (must be between 0 and 59 inclusive).= Show day count since world creation= Current day is @1.= -[<delay_in_seconds> | -1] [reconnect] [<message>]= -Shutdown server (-1 cancels a delayed shutdown)= +[<delay_in_seconds> | -1] [-r] [<message>]= +Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)= Server shutting down (operator request).= Ban the IP of a player or show the ban list= The ban list is empty.= @@ -191,6 +193,7 @@ Available commands:= Command not available: @1= [all | privs | <cmd>]= Get help for commands or list privileges= +Available privileges:= Command= Parameters= For more information, click on any entry in the list.= @@ -200,7 +203,6 @@ Available commands: (see also: /help <cmd>)= Close= Privilege= Description= -Available privileges:= print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset= Handle the profiler and profiling data= Statistics written to action log.= diff --git a/builtin/mainmenu/dlg_contentstore.lua b/builtin/mainmenu/dlg_contentstore.lua index 20a446d5d..790da03ba 100644 --- a/builtin/mainmenu/dlg_contentstore.lua +++ b/builtin/mainmenu/dlg_contentstore.lua @@ -57,24 +57,39 @@ local filter_types_type = { "txp", } +local REASON_NEW = "new" +local REASON_UPDATE = "update" +local REASON_DEPENDENCY = "dependency" + + +local function get_download_url(package, reason) + local base_url = core.settings:get("contentdb_url") + local ret = base_url .. ("/packages/%s/%s/releases/%d/download/"):format(package.author, package.name, package.release) + if reason then + ret = ret .. "?reason=" .. reason + end + return ret +end + local function download_package(param) - if core.download_file(param.package.url, param.filename) then + if core.download_file(param.url, param.filename) then return { filename = param.filename, successful = true, } else - core.log("error", "downloading " .. dump(param.package.url) .. " failed") + core.log("error", "downloading " .. dump(param.url) .. " failed") return { successful = false, } end end -local function start_install(package) +local function start_install(package, reason) local params = { package = package, + url = get_download_url(package, reason), filename = os.tempfolder() .. "_MODNAME_" .. package.name .. ".zip", } @@ -135,7 +150,7 @@ local function start_install(package) if next then table.remove(download_queue, 1) - start_install(next) + start_install(next.package, next.reason) end ui.update() @@ -151,12 +166,12 @@ local function start_install(package) end end -local function queue_download(package) +local function queue_download(package, reason) local max_concurrent_downloads = tonumber(core.settings:get("contentdb_max_concurrent_downloads")) if number_downloading < max_concurrent_downloads then - start_install(package) + start_install(package, reason) else - table.insert(download_queue, package) + table.insert(download_queue, { package = package, reason = reason }) package.queued = true end end @@ -407,12 +422,12 @@ function install_dialog.handle_submit(this, fields) end if fields.install_all then - queue_download(install_dialog.package) + queue_download(install_dialog.package, REASON_NEW) if install_dialog.will_install_deps then for _, dep in pairs(install_dialog.dependencies) do if not dep.is_optional and not dep.installed and dep.package then - queue_download(dep.package) + queue_download(dep.package, REASON_DEPENDENCY) end end end @@ -560,18 +575,25 @@ function store.load() end store.packages_full = core.parse_json(response.data) or {} + store.aliases = {} 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 + + if package.aliases then + for _, alias in ipairs(package.aliases) do + -- We currently don't support name changing + local suffix = "/" .. package.name + if alias:sub(-#suffix) == suffix then + store.aliases[alias:lower()] = package.id + end + end + end end store.packages_full_unordered = store.packages_full @@ -584,7 +606,8 @@ function store.update_paths() pkgmgr.refresh_globals() for _, mod in pairs(pkgmgr.clientmods:get_list()) do if mod.author and mod.release > 0 then - mod_hash[mod.author:lower() .. "/" .. mod.name] = mod + local id = mod.author:lower() .. "/" .. mod.name + mod_hash[store.aliases[id] or id] = mod end end @@ -592,14 +615,16 @@ function store.update_paths() pkgmgr.update_gamelist() for _, game in pairs(pkgmgr.games) do if game.author ~= "" and game.release > 0 then - game_hash[game.author:lower() .. "/" .. game.id] = game + local id = game.author:lower() .. "/" .. game.id + game_hash[store.aliases[id] or id] = game end end local txp_hash = {} for _, txp in pairs(pkgmgr.get_texture_packs()) do if txp.author and txp.release > 0 then - txp_hash[txp.author:lower() .. "/" .. txp.name] = txp + local id = txp.author:lower() .. "/" .. txp.name + txp_hash[store.aliases[id] or id] = txp end end @@ -915,7 +940,7 @@ function store.handle_submit(this, fields) local package = store.packages_full[i] if package.path and package.installed_release < package.release and not (package.downloading or package.queued) then - queue_download(package) + queue_download(package, REASON_UPDATE) end end return true @@ -948,7 +973,7 @@ function store.handle_submit(this, fields) this:hide() dlg:show() else - queue_download(package) + queue_download(package, package.path and REASON_UPDATE or REASON_NEW) end end diff --git a/builtin/mainmenu/dlg_settings_advanced.lua b/builtin/mainmenu/dlg_settings_advanced.lua index 26f4fa4a7..38a658969 100644 --- a/builtin/mainmenu/dlg_settings_advanced.lua +++ b/builtin/mainmenu/dlg_settings_advanced.lua @@ -31,6 +31,10 @@ end -- returns error message, or nil local function parse_setting_line(settings, line, read_all, base_level, allow_secure) + + -- strip carriage returns (CR, /r) + line = line:gsub("\r", "") + -- comment local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$") if comment then @@ -650,7 +654,7 @@ local function create_change_setting_formspec(dialogdata) -- Third row add_field(0.3, "te_octaves", fgettext("Octaves"), t[7]) - add_field(3.6, "te_persist", fgettext("Persistance"), t[8]) + add_field(3.6, "te_persist", fgettext("Persistence"), t[8]) add_field(6.9, "te_lacun", fgettext("Lacunarity"), t[9]) height = height + 1.1 diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua index 0e06c3bef..be5f905ac 100644 --- a/builtin/mainmenu/tab_local.lua +++ b/builtin/mainmenu/tab_local.lua @@ -18,8 +18,14 @@ local enable_gamebar = PLATFORM ~= "Android" local current_game, singleplayer_refresh_gamebar +local valid_disabled_settings = { + ["enable_damage"]=true, + ["creative_mode"]=true, + ["enable_server"]=true, +} if enable_gamebar then + -- Currently chosen game in gamebar for theming and filtering function current_game() local last_game_id = core.settings:get("menu_last_game") local game = pkgmgr.find_by_gameid(last_game_id) @@ -102,37 +108,87 @@ if enable_gamebar then btnbar:add_button("game_open_cdb", "", plus_image, fgettext("Install games from ContentDB")) end else + -- Currently chosen game in gamebar: no gamebar -> no "current" game function current_game() return nil end end +local function get_disabled_settings(game) + if not game then + return {} + end + + local gameconfig = Settings(game.path .. "/game.conf") + local disabled_settings = {} + if gameconfig then + local disabled_settings_str = (gameconfig:get("disabled_settings") or ""):split() + for _, value in pairs(disabled_settings_str) do + local state = false + value = value:trim() + if string.sub(value, 1, 1) == "!" then + state = true + value = string.sub(value, 2) + end + if valid_disabled_settings[value] then + disabled_settings[value] = state + else + core.log("error", "Invalid disabled setting in game.conf: "..tostring(value)) + end + end + end + return disabled_settings +end + local function get_formspec(tabview, name, tabdata) local retval = "" local index = filterlist.get_current_index(menudata.worldlist, - tonumber(core.settings:get("mainmenu_last_selected_world")) - ) + tonumber(core.settings:get("mainmenu_last_selected_world"))) + local list = menudata.worldlist:get_list() + local world = list and index and list[index] + local gameid = world and world.gameid + local game = gameid and pkgmgr.find_by_gameid(gameid) + local disabled_settings = get_disabled_settings(game) + + local creative, damage, host = "", "", "" + + -- Y offsets for game settings checkboxes + local y = -0.2 + local yo = 0.45 + + if disabled_settings["creative_mode"] == nil then + creative = "checkbox[0,"..y..";cb_creative_mode;".. fgettext("Creative Mode") .. ";" .. + dump(core.settings:get_bool("creative_mode")) .. "]" + y = y + yo + end + if disabled_settings["enable_damage"] == nil then + damage = "checkbox[0,"..y..";cb_enable_damage;".. fgettext("Enable Damage") .. ";" .. + dump(core.settings:get_bool("enable_damage")) .. "]" + y = y + yo + end + if disabled_settings["enable_server"] == nil then + host = "checkbox[0,"..y..";cb_server;".. fgettext("Host Server") ..";" .. + dump(core.settings:get_bool("enable_server")) .. "]" + y = y + yo + end retval = retval .. "button[3.9,3.8;2.8,1;world_delete;".. fgettext("Delete") .. "]" .. "button[6.55,3.8;2.8,1;world_configure;".. fgettext("Select Mods") .. "]" .. "button[9.2,3.8;2.8,1;world_create;".. fgettext("New") .. "]" .. "label[3.9,-0.05;".. fgettext("Select World:") .. "]".. - "checkbox[0,-0.20;cb_creative_mode;".. fgettext("Creative Mode") .. ";" .. - dump(core.settings:get_bool("creative_mode")) .. "]".. - "checkbox[0,0.25;cb_enable_damage;".. fgettext("Enable Damage") .. ";" .. - dump(core.settings:get_bool("enable_damage")) .. "]".. - "checkbox[0,0.7;cb_server;".. fgettext("Host Server") ..";" .. - dump(core.settings:get_bool("enable_server")) .. "]" .. + creative .. + damage .. + host .. "textlist[3.9,0.4;7.9,3.45;sp_worlds;" .. menu_render_worldlist() .. ";" .. index .. "]" - if core.settings:get_bool("enable_server") then + if core.settings:get_bool("enable_server") and disabled_settings["enable_server"] == nil then retval = retval .. "button[7.9,4.75;4.1,1;play;".. fgettext("Host Game") .. "]" .. - "checkbox[0,1.15;cb_server_announce;" .. fgettext("Announce Server") .. ";" .. + "checkbox[0,"..y..";cb_server_announce;" .. fgettext("Announce Server") .. ";" .. dump(core.settings:get_bool("server_announce")) .. "]" .. "field[0.3,2.85;3.8,0.5;te_playername;" .. fgettext("Name") .. ";" .. core.formspec_escape(core.settings:get("name")) .. "]" .. @@ -227,9 +283,21 @@ local function main_button_handler(this, fields, name, tabdata) -- Update last game local world = menudata.worldlist:get_raw_element(gamedata.selected_world) + local game_obj if world then - local game = pkgmgr.find_by_gameid(world.gameid) - core.settings:set("menu_last_game", game.id) + game_obj = pkgmgr.find_by_gameid(world.gameid) + core.settings:set("menu_last_game", game_obj.id) + end + + local disabled_settings = get_disabled_settings(game_obj) + for k, _ in pairs(valid_disabled_settings) do + local v = disabled_settings[k] + if v ~= nil then + if k == "enable_server" and v == true then + error("Setting 'enable_server' cannot be force-enabled! The game.conf needs to be fixed.") + end + core.settings:set_bool(k, disabled_settings[k]) + end end if core.settings:get_bool("enable_server") then diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index 29744048a..f06e35872 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -43,6 +43,14 @@ local labels = { fgettext("2x"), fgettext("4x"), fgettext("8x") + }, + shadow_levels = { + fgettext("Disabled"), + fgettext("Very Low"), + fgettext("Low"), + fgettext("Medium"), + fgettext("High"), + fgettext("Ultra High") } } @@ -66,6 +74,10 @@ local dd_options = { antialiasing = { table.concat(labels.antialiasing, ","), {"0", "2", "4", "8"} + }, + shadow_levels = { + table.concat(labels.shadow_levels, ","), + { "0", "1", "2", "3", "4", "5" } } } @@ -110,6 +122,15 @@ local getSettingIndex = { end end return 1 + end, + ShadowMapping = function() + local shadow_setting = core.settings:get("shadow_levels") + for i = 1, #dd_options.shadow_levels[2] do + if shadow_setting == dd_options.shadow_levels[2][i] then + return i + end + end + return 1 end } @@ -197,7 +218,10 @@ local function formspec(tabview, name, tabdata) "checkbox[8.25,1.5;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" .. dump(core.settings:get_bool("enable_waving_leaves")) .. "]" .. "checkbox[8.25,2;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" - .. dump(core.settings:get_bool("enable_waving_plants")) .. "]" + .. dump(core.settings:get_bool("enable_waving_plants")) .. "]".. + "label[8.25,3.0;" .. fgettext("Dynamic shadows: ") .. "]" .. + "dropdown[8.25,3.5;3.5;dd_shadows;" .. dd_options.shadow_levels[1] .. ";" + .. getSettingIndex.ShadowMapping() .. "]" else tab_string = tab_string .. "label[8.38,0.7;" .. core.colorize("#888888", @@ -207,7 +231,9 @@ local function formspec(tabview, name, tabdata) "label[8.38,1.7;" .. core.colorize("#888888", fgettext("Waving Leaves")) .. "]" .. "label[8.38,2.2;" .. core.colorize("#888888", - fgettext("Waving Plants")) .. "]" + fgettext("Waving Plants")) .. "]".. + "label[8.38,2.7;" .. core.colorize("#888888", + fgettext("Dynamic shadows")) .. "]" end return tab_string @@ -333,6 +359,34 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) ddhandled = true end + for i = 1, #labels.shadow_levels do + if fields["dd_shadows"] == labels.shadow_levels[i] then + core.settings:set("shadow_levels", dd_options.shadow_levels[2][i]) + ddhandled = true + end + end + + if fields["dd_shadows"] == labels.shadow_levels[1] then + core.settings:set("enable_dynamic_shadows", "false") + else + local shadow_presets = { + [2] = { 80, 512, "true", 0, "false" }, + [3] = { 120, 1024, "true", 1, "false" }, + [4] = { 350, 2048, "true", 1, "false" }, + [5] = { 350, 2048, "true", 2, "true" }, + [6] = { 450, 4096, "true", 2, "true" }, + } + local s = shadow_presets[table.indexof(labels.shadow_levels, fields["dd_shadows"])] + if s then + core.settings:set("enable_dynamic_shadows", "true") + core.settings:set("shadow_map_max_distance", s[1]) + core.settings:set("shadow_map_texture_size", s[2]) + core.settings:set("shadow_map_texture_32bit", s[3]) + core.settings:set("shadow_filters", s[4]) + core.settings:set("shadow_map_color", s[5]) + end + end + return ddhandled end diff --git a/builtin/mainmenu/tests/serverlistmgr_spec.lua b/builtin/mainmenu/tests/serverlistmgr_spec.lua index 148e9b794..a091959fb 100644 --- a/builtin/mainmenu/tests/serverlistmgr_spec.lua +++ b/builtin/mainmenu/tests/serverlistmgr_spec.lua @@ -2,6 +2,7 @@ _G.core = {} _G.unpack = table.unpack _G.serverlistmgr = {} +dofile("builtin/common/vector.lua") dofile("builtin/common/misc_helpers.lua") dofile("builtin/mainmenu/serverlistmgr.lua") diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index ab3ceb9e7..8726012ff 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -525,7 +525,7 @@ texture_clean_transparent (Clean transparent textures) bool false # can be blurred, so automatically upscale them with nearest-neighbor # interpolation to preserve crisp pixels. This sets the minimum texture size # for the upscaled textures; higher values look sharper, but require more -# memory. Powers of 2 are recommended. This setting is ONLY applies if +# memory. Powers of 2 are recommended. This setting is ONLY applied if # bilinear/trilinear/anisotropic filtering is enabled. # This is also used as the base node texture size for world-aligned # texture autoscaling. @@ -594,6 +594,58 @@ enable_waving_leaves (Waving leaves) bool false # Requires shaders to be enabled. enable_waving_plants (Waving plants) bool false +[***Dynamic shadows] + +# Set to true to enable Shadow Mapping. +# Requires shaders to be enabled. +enable_dynamic_shadows (Dynamic shadows) bool false + +# Set the shadow strength. +# Lower value means lighter shadows, higher value means darker shadows. +shadow_strength (Shadow strength) float 0.2 0.05 1.0 + +# Maximum distance to render shadows. +shadow_map_max_distance (Shadow map max distance in nodes to render shadows) float 120.0 10.0 1000.0 + +# Texture size to render the shadow map on. +# This must be a power of two. +# Bigger numbers create better shadows but it is also more expensive. +shadow_map_texture_size (Shadow map texture size) int 1024 128 8192 + +# Sets shadow texture quality to 32 bits. +# On false, 16 bits texture will be used. +# This can cause much more artifacts in the shadow. +shadow_map_texture_32bit (Shadow map texture in 32 bits) bool true + +# Enable Poisson disk filtering. +# On true uses Poisson disk to make "soft shadows". Otherwise uses PCF filtering. +shadow_poisson_filter (Poisson filtering) bool true + +# Define shadow filtering quality +# This simulates the soft shadows effect by applying a PCF or Poisson disk +# but also uses more resources. +shadow_filters (Shadow filter quality) enum 1 0,1,2 + +# Enable colored shadows. +# On true translucent nodes cast colored shadows. This is expensive. +shadow_map_color (Colored shadows) bool false + +# Spread a complete update of shadow map over given amount of frames. +# Higher values might make shadows laggy, lower values +# will consume more resources. +# Minimum value: 1; maximum value: 16 +shadow_update_frames (Map shadows update frames) int 8 1 16 + +# Set the soft shadow radius size. +# Lower values mean sharper shadows, bigger values mean softer shadows. +# Minimum value: 1.0; maxiumum value: 10.0 +shadow_soft_radius (Soft shadow radius) float 1.0 1.0 10.0 + +# Set the tilt of Sun/Moon orbit in degrees +# Value of 0 means no tilt / vertical orbit. +# Minimum value: 0.0; maximum value: 60.0 +shadow_sky_body_orbit_tilt (Sky Body Orbit Tilt) float 0.0 0.0 60.0 + [**Advanced] # Arm inertia, gives a more realistic movement of @@ -620,10 +672,10 @@ viewing_range (Viewing range) int 190 20 4000 # 0.1 = Default, 0.25 = Good value for weaker tablets. near_plane (Near plane) float 0.1 0 0.25 -# Width component of the initial window size. +# Width component of the initial window size. Ignored in fullscreen mode. screen_w (Screen width) int 1024 1 -# Height component of the initial window size. +# Height component of the initial window size. Ignored in fullscreen mode. screen_h (Screen height) int 600 1 # Save window size automatically when modified. @@ -632,9 +684,6 @@ autosave_screensize (Autosave screen size) bool true # Fullscreen mode. fullscreen (Full screen) bool false -# Bits per pixel (aka color depth) in fullscreen mode. -fullscreen_bpp (Full screen BPP) int 24 - # Vertical screen synchronization. vsync (VSync) bool false @@ -678,7 +727,7 @@ texture_path (Texture path) path # Note: On Android, stick with OGLES1 if unsure! App may fail to start otherwise. # On other platforms, OpenGL is recommended. # Shaders are supported by OpenGL (desktop only) and OGLES2 (experimental) -video_driver (Video driver) enum opengl null,software,burningsvideo,direct3d8,direct3d9,opengl,ogles1,ogles2 +video_driver (Video driver) enum opengl opengl,ogles1,ogles2 # Radius of cloud area stated in number of 64 node cloud squares. # Values larger than 26 will start to produce sharp cutoffs at cloud area corners. @@ -739,7 +788,7 @@ selectionbox_width (Selection box width) int 2 1 5 crosshair_color (Crosshair color) string (255,255,255) # Crosshair alpha (opaqueness, between 0 and 255). -# Also controls the object crosshair color +# This also applies to the object crosshair. crosshair_alpha (Crosshair alpha) int 255 0 255 # Maximum number of recent chat messages to show @@ -936,6 +985,12 @@ mute_sound (Mute sound) bool false [Client] +# Clickable weblinks (middle-click or ctrl-left-click) enabled in chat console output. +clickable_chat_weblinks (Chat weblinks) bool false + +# Optional override for chat weblink color. +chat_weblink_color (Weblink color) string + [*Network] # Address to connect to. @@ -948,9 +1003,9 @@ address (Server address) string remote_port (Remote port) int 30000 1 65535 # Prometheus listener address. -# If minetest is compiled with ENABLE_PROMETHEUS option enabled, +# 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 +# Metrics can be fetched 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. @@ -1057,11 +1112,10 @@ full_block_send_enable_min_time_from_building (Delay in sending blocks after bui # client number. max_packets_per_iteration (Max. packets per iteration) int 1024 -# ZLib compression level to use when sending mapblocks to the client. -# -1 - Zlib's default compression level -# 0 - no compresson, fastest +# Compression level to use when sending mapblocks to the client. +# -1 - use default compression level +# 0 - least compresson, fastest # 9 - best compression, slowest -# (levels 1-3 use Zlib's "fast" method, 4-9 use the normal method) map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9 [*Game] @@ -1260,12 +1314,11 @@ max_objects_per_block (Maximum objects per block) int 64 # See https://www.sqlite.org/pragma.html#pragma_synchronous sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2 -# ZLib compression level to use when saving mapblocks to disk. -# -1 - Zlib's default compression level -# 0 - no compresson, fastest +# Compression level to use when saving mapblocks to disk. +# -1 - use default compression level +# 0 - least compresson, fastest # 9 - best compression, slowest -# (levels 1-3 use Zlib's "fast" method, 4-9 use the normal method) -map_compression_level_disk (Map Compression Level for Disk Storage) int 3 -1 9 +map_compression_level_disk (Map Compression Level for Disk Storage) int -1 -1 9 # Length of a server tick and the interval at which objects are generally updated over # network. @@ -1437,9 +1490,6 @@ curl_parallel_limit (cURL parallel limit) int 8 # Maximum time a file download (e.g. a mod download) may take, stated in milliseconds. curl_file_download_timeout (cURL file download timeout) int 300000 -# Makes DirectX work with LuaJIT. Disable if it causes troubles. -high_precision_fpu (High-precision FPU) bool true - # Replaces the default main menu with a custom one. main_menu_script (Main menu script) string @@ -2178,15 +2228,15 @@ chunksize (Chunk size) int 5 enable_mapgen_debug_info (Mapgen debug) bool false # Maximum number of blocks that can be queued for loading. -emergequeue_limit_total (Absolute limit of queued blocks to emerge) int 1024 +emergequeue_limit_total (Absolute limit of queued blocks to emerge) int 1024 1 1000000 # Maximum number of blocks to be queued that are to be loaded from file. # This limit is enforced per player. -emergequeue_limit_diskonly (Per-player limit of queued blocks load from disk) int 128 +emergequeue_limit_diskonly (Per-player limit of queued blocks load from disk) int 128 1 1000000 # Maximum number of blocks to be queued that are to be generated. # This limit is enforced per player. -emergequeue_limit_generate (Per-player limit of queued blocks to generate) int 128 +emergequeue_limit_generate (Per-player limit of queued blocks to generate) int 128 1 1000000 # Number of emerge threads to use. # Value 0: |