]> git.lizzy.rs Git - dragonfireclient.git/commitdiff
Merge branch 'master' of https://github.com/minetest/minetest
authorElias Fleckenstein <eliasfleckenstein@web.de>
Thu, 2 Jun 2022 18:54:02 +0000 (20:54 +0200)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Thu, 2 Jun 2022 18:54:02 +0000 (20:54 +0200)
53 files changed:
.github/workflows/build.yml
.github/workflows/lua.yml
CMakeLists.txt
README.md
builtin/common/information_formspecs.lua
builtin/common/misc_helpers.lua
builtin/common/tests/misc_helpers_spec.lua
builtin/game/chat.lua
builtin/mainmenu/tab_settings.lua
cmake/Modules/MinetestFindIrrlichtHeaders.cmake
doc/lua_api.txt
doc/minetest.6
games/devtest/mods/testitems/init.lua [new file with mode: 0644]
games/devtest/mods/testitems/mod.conf [new file with mode: 0644]
games/devtest/mods/testitems/textures/testitems_overlay_base.png [new file with mode: 0644]
games/devtest/mods/testitems/textures/testitems_overlay_overlay.png [new file with mode: 0644]
games/devtest/mods/testnodes/init.lua
games/devtest/mods/testnodes/overlays.lua [new file with mode: 0644]
games/devtest/mods/testnodes/textures/testnodes_overlay.png [new file with mode: 0644]
games/devtest/mods/testnodes/textures/testnodes_overlayable.png [new file with mode: 0644]
games/devtest/mods/unittests/misc.lua
src/CMakeLists.txt
src/client/client.cpp
src/client/mapblock_mesh.cpp
src/client/renderingengine.cpp
src/client/renderingengine.h
src/client/shadows/dynamicshadows.cpp
src/client/shadows/dynamicshadowsrender.cpp
src/client/shadows/dynamicshadowsrender.h
src/gui/modalMenu.cpp
src/log.cpp
src/log.h
src/map.cpp
src/map.h
src/mapblock.cpp
src/mapnode.cpp
src/nodedef.cpp
src/nodedef.h
src/script/common/c_content.cpp
src/script/cpp_api/s_security.cpp
src/script/lua_api/l_item.cpp
src/script/lua_api/l_nodemeta.cpp
src/script/lua_api/l_nodemeta.h
src/script/lua_api/l_object.cpp
src/script/lua_api/l_object.h
src/script/lua_api/l_settings.cpp
src/server.cpp
src/server.h
src/server/luaentity_sao.cpp
src/server/luaentity_sao.h
src/serverenvironment.cpp
src/serverenvironment.h
util/ci/common.sh

index 70340d82d584b3b417d985f48eb5dd4e6a5ea971..1717a282d4bd6c3475cbdfbd24714bbfc2c2159b 100644 (file)
@@ -132,7 +132,7 @@ jobs:
       - name: Install deps
         run: |
           source ./util/ci/common.sh
-          install_linux_deps --old-irr clang-9
+          install_linux_deps clang-9
 
       - name: Build prometheus-cpp
         run: |
index 3af4a6ee78065fe4123e8572659a229be87d46a5..2a728f2d3502898fe1f708d4a25d1166ded4751a 100644 (file)
@@ -42,9 +42,10 @@ jobs:
     runs-on: ubuntu-20.04
     steps:
     - uses: actions/checkout@v3
-    - name: Install luarocks
-      run: |
-        sudo apt-get update && sudo apt-get install -y luarocks
+    - uses: leafo/gh-actions-lua@v9
+      with:
+        luaVersion: "5.1.5"
+    - uses: leafo/gh-actions-luarocks@v4
 
     - name: Install luarocks tools
       run: |
index 018e233da2b19b23b5ab740a1bae4e46bfa8f0aa..fe1cb47eb38751d491da02983c198f5ecad45fc1 100644 (file)
@@ -78,7 +78,6 @@ if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "")
                # IrrlichtMtConfig.cmake
                message(FATAL_ERROR "Could not find IrrlichtMtConfig.cmake in ${IRRLICHTMT_BUILD_DIR}/cmake.")
        endif()
-# This is done here so that relative search paths are more reasonable
 elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
        message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'")
        if(BUILD_CLIENT)
@@ -108,9 +107,9 @@ else()
 
                include(MinetestFindIrrlichtHeaders)
                if(NOT IRRLICHT_INCLUDE_DIR)
-                       message(FATAL_ERROR "Irrlicht or IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
+                       message(FATAL_ERROR "IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
                endif()
-               message(STATUS "Found Irrlicht headers: ${IRRLICHT_INCLUDE_DIR}")
+               message(STATUS "Found IrrlichtMt headers: ${IRRLICHT_INCLUDE_DIR}")
                add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED)
                # Note that we can't use target_include_directories() since that doesn't work for IMPORTED targets before CMake 3.11
                set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
index 410ca91a01d4eef2b318dc45c60c6c6f8c7ce98f..4fef8f0c04bd3000f106a10ba2e273647625c48c 100644 (file)
--- a/README.md
+++ b/README.md
@@ -223,8 +223,8 @@ Run it:
 - You can disable the client build by specifying `-DBUILD_CLIENT=FALSE`.
 - You can select between Release and Debug build by `-DCMAKE_BUILD_TYPE=<Debug or Release>`.
   - Debug build is slower, but gives much more useful output in a debugger.
-- If you build a bare server you don't need to have the Irrlicht or IrrlichtMt library installed.
-  - In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlicht/include`.
+- If you build a bare server you don't need to compile IrrlichtMt, just the headers suffice.
+  - In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlichtmt/include`.
 
 - Minetest will use the IrrlichtMt package that is found first, given by the following order:
   1. Specified `IRRLICHTMT_BUILD_DIR` CMake variable
index 3405263bf42506489b04a4df1818776f418ac6cf..1445a017cd39ab9060e128412839f21f1ed2ec5e 100644 (file)
@@ -22,7 +22,6 @@ local LIST_FORMSPEC_DESCRIPTION = [[
 
 local F = core.formspec_escape
 local S = core.get_translator("__builtin")
-local check_player_privs = core.check_player_privs
 
 
 -- CHAT COMMANDS FORMSPEC
@@ -58,10 +57,11 @@ local function build_chatcommands_formspec(name, sel, copy)
                .. "any entry in the list.").. "\n" ..
                S("Double-click to copy the entry to the chat history.")
 
+       local privs = core.get_player_privs(name)
        for i, data in ipairs(mod_cmds) do
                rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. ","
                for j, cmds in ipairs(data[2]) do
-                       local has_priv = check_player_privs(name, cmds[2].privs)
+                       local has_priv = privs[cmds[2].privs]
                        rows[#rows + 1] = ("%s,1,%s,%s"):format(
                                has_priv and COLOR_GREEN or COLOR_GRAY,
                                cmds[1], F(cmds[2].params))
index 542b2040d6579df334fe444489b3c0ebb41a566b..f8a905c7bbf6d819e9009022fa9bcb4d2c687245 100644 (file)
@@ -204,7 +204,7 @@ end
 
 --------------------------------------------------------------------------------
 function string:trim()
-       return (self:gsub("^%s*(.-)%s*$", "%1"))
+       return self:match("^%s*(.-)%s*$")
 end
 
 --------------------------------------------------------------------------------
@@ -245,16 +245,16 @@ function math.round(x)
        return math.ceil(x - 0.5)
 end
 
-
+local formspec_escapes = {
+       ["\\"] = "\\\\",
+       ["["] = "\\[",
+       ["]"] = "\\]",
+       [";"] = "\\;",
+       [","] = "\\,"
+}
 function core.formspec_escape(text)
-       if text ~= nil then
-               text = string.gsub(text,"\\","\\\\")
-               text = string.gsub(text,"%]","\\]")
-               text = string.gsub(text,"%[","\\[")
-               text = string.gsub(text,";","\\;")
-               text = string.gsub(text,",","\\,")
-       end
-       return text
+       -- Use explicit character set instead of dot here because it doubles the performance
+       return text and text:gsub("[\\%[%];,]", formspec_escapes)
 end
 
 
@@ -265,18 +265,21 @@ function core.wrap_text(text, max_length, as_table)
                return as_table and {text} or text
        end
 
-       for word in text:gmatch('%S+') do
-               local cur_length = #table.concat(line, ' ')
-               if cur_length > 0 and cur_length + #word + 1 >= max_length then
+       local line_length = 0
+       for word in text:gmatch("%S+") do
+               if line_length > 0 and line_length + #word + 1 >= max_length then
                        -- word wouldn't fit on current line, move to next line
-                       table.insert(result, table.concat(line, ' '))
-                       line = {}
+                       table.insert(result, table.concat(line, " "))
+                       line = {word}
+                       line_length = #word
+               else
+                       table.insert(line, word)
+                       line_length = line_length + 1 + #word
                end
-               table.insert(line, word)
        end
 
-       table.insert(result, table.concat(line, ' '))
-       return as_table and result or table.concat(result, '\n')
+       table.insert(result, table.concat(line, " "))
+       return as_table and result or table.concat(result, "\n")
 end
 
 --------------------------------------------------------------------------------
@@ -425,54 +428,50 @@ function core.string_to_pos(value)
                return nil
        end
 
-       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.-]+) *%)$")
+       value = value:match("^%((.-)%)$") or value -- strip parentheses
+
+       local x, y, z = value:trim():match("^([%d.-]+)[,%s]%s*([%d.-]+)[,%s]%s*([%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
 
 
 --------------------------------------------------------------------------------
-function core.string_to_area(value)
-       local p1, p2 = unpack(value:split(") ("))
-       if p1 == nil or p2 == nil then
-               return nil
-       end
 
-       p1 = core.string_to_pos(p1 .. ")")
-       p2 = core.string_to_pos("(" .. p2)
-       if p1 == nil or p2 == nil then
-               return nil
+do
+       local rel_num_cap = "(~?-?%d*%.?%d*)" -- may be overly permissive as this will be tonumber'ed anyways
+       local num_delim = "[,%s]%s*"
+       local pattern = "^" .. table.concat({rel_num_cap, rel_num_cap, rel_num_cap}, num_delim) .. "$"
+
+       local function parse_area_string(pos, relative_to)
+               local pp = {}
+               pp.x, pp.y, pp.z = pos:trim():match(pattern)
+               return core.parse_coordinates(pp.x, pp.y, pp.z, relative_to)
        end
 
-       return p1, p2
-end
+       function core.string_to_area(value, relative_to)
+               local p1, p2 = value:match("^%((.-)%)%s*%((.-)%)$")
+               if not p1 then
+                       return
+               end
 
-local function test_string_to_area()
-       local p1, p2 = core.string_to_area("(10.0, 5, -2) (  30.2,   4, -12.53)")
-       assert(p1.x == 10.0 and p1.y == 5 and p1.z == -2)
-       assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53)
+               p1 = parse_area_string(p1, relative_to)
+               p2 = parse_area_string(p2, relative_to)
 
-       p1, p2 = core.string_to_area("(10.0, 5, -2  30.2,   4, -12.53")
-       assert(p1 == nil and p2 == nil)
+               if p1 == nil or p2 == nil then
+                       return
+               end
 
-       p1, p2 = core.string_to_area("(10.0, 5,) -2  fgdf2,   4, -12.53")
-       assert(p1 == nil and p2 == nil)
+               return p1, p2
+       end
 end
 
-test_string_to_area()
-
 --------------------------------------------------------------------------------
 function table.copy(t, seen)
        local n = {}
@@ -786,6 +785,74 @@ function core.is_nan(number)
        return number ~= number
 end
 
+--[[ Helper function for parsing an optionally relative number
+of a chat command parameter, using the chat command tilde notation.
+
+Parameters:
+* arg: String snippet containing the number; possible values:
+    * "<number>": return as number
+    * "~<number>": return relative_to + <number>
+    * "~": return relative_to
+    * Anything else will return `nil`
+* relative_to: Number to which the `arg` number might be relative to
+
+Returns:
+A number or `nil`, depending on `arg.
+
+Examples:
+* `core.parse_relative_number("5", 10)` returns 5
+* `core.parse_relative_number("~5", 10)` returns 15
+* `core.parse_relative_number("~", 10)` returns 10
+]]
+function core.parse_relative_number(arg, relative_to)
+       if not arg then
+               return nil
+       elseif arg == "~" then
+               return relative_to
+       elseif string.sub(arg, 1, 1) == "~" then
+               local number = tonumber(string.sub(arg, 2))
+               if not number then
+                       return nil
+               end
+               if core.is_nan(number) or number == math.huge or number == -math.huge then
+                       return nil
+               end
+               return relative_to + number
+       else
+               local number = tonumber(arg)
+               if core.is_nan(number) or number == math.huge or number == -math.huge then
+                       return nil
+               end
+               return number
+       end
+end
+
+--[[ Helper function to parse coordinates that might be relative
+to another position; supports chat command tilde notation.
+Intended to be used in chat command parameter parsing.
+
+Parameters:
+* x, y, z: Parsed x, y, and z coordinates as strings
+* relative_to: Position to which to compare the position
+
+Syntax of x, y and z:
+* "<number>": return as number
+* "~<number>": return <number> + player position on this axis
+* "~": return player position on this axis
+
+Returns: a vector or nil for invalid input or if player does not exist
+]]
+function core.parse_coordinates(x, y, z, relative_to)
+       if not relative_to then
+               x, y, z = tonumber(x), tonumber(y), tonumber(z)
+               return x and y and z and { x = x, y = y, z = z }
+       end
+       local rx = core.parse_relative_number(x, relative_to.x)
+       local ry = core.parse_relative_number(y, relative_to.y)
+       local rz = core.parse_relative_number(z, relative_to.z)
+       return rx and ry and rz and { x = rx, y = ry, z = rz }
+end
+
 function core.inventorycube(img1, img2, img3)
        img2 = img2 or img1
        img3 = img3 or img1
index b112368605886aed85a7de325eecff02f909d534..7d046d5b7d80512f656e36bf0f510f32fcb5e6c7 100644 (file)
@@ -67,9 +67,107 @@ describe("pos", function()
        end)
 end)
 
+describe("area parsing", function()
+       describe("valid inputs", function()
+               it("accepts absolute numbers", function()
+                       local p1, p2 = core.string_to_area("(10.0, 5, -2) (  30.2 4 -12.53)")
+                       assert(p1.x == 10 and p1.y == 5 and p1.z == -2)
+                       assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53)
+               end)
+
+               it("accepts relative numbers", function()
+                       local p1, p2 = core.string_to_area("(1,2,3) (~5,~-5,~)", {x=10,y=10,z=10})
+                       assert(type(p1) == "table" and type(p2) == "table")
+                       assert(p1.x == 1 and p1.y == 2 and p1.z == 3)
+                       assert(p2.x == 15 and p2.y == 5 and p2.z == 10)
+
+                       p1, p2 = core.string_to_area("(1 2 3) (~5 ~-5 ~)", {x=10,y=10,z=10})
+                       assert(type(p1) == "table" and type(p2) == "table")
+                       assert(p1.x == 1 and p1.y == 2 and p1.z == 3)
+                       assert(p2.x == 15 and p2.y == 5 and p2.z == 10)
+               end)
+       end)
+       describe("invalid inputs", function()
+               it("rejects too few numbers", function()
+                       local p1, p2 = core.string_to_area("(1,1) (1,1,1,1)", {x=1,y=1,z=1})
+                       assert(p1 == nil and p2 == nil)
+               end)
+
+               it("rejects too many numbers", function()
+                       local p1, p2 = core.string_to_area("(1,1,1,1) (1,1,1,1)", {x=1,y=1,z=1})
+                       assert(p1 == nil and p2 == nil)
+               end)
+
+               it("rejects nan & inf", function()
+                       local p1, p2 = core.string_to_area("(1,1,1) (1,1,nan)", {x=1,y=1,z=1})
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(1,1,1) (1,1,~nan)", {x=1,y=1,z=1})
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(1,1,1) (1,~nan,1)", {x=1,y=1,z=1})
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(1,1,1) (1,1,inf)", {x=1,y=1,z=1})
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(1,1,1) (1,1,~inf)", {x=1,y=1,z=1})
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(1,1,1) (1,~inf,1)", {x=1,y=1,z=1})
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(nan,nan,nan) (nan,nan,nan)", {x=1,y=1,z=1})
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(nan,nan,nan) (nan,nan,nan)")
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(inf,inf,inf) (-inf,-inf,-inf)", {x=1,y=1,z=1})
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(inf,inf,inf) (-inf,-inf,-inf)")
+                       assert(p1 == nil and p2 == nil)
+               end)
+
+               it("rejects words", function()
+                       local p1, p2 = core.string_to_area("bananas", {x=1,y=1,z=1})
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("bananas", "foobar")
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("bananas")
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(bananas,bananas,bananas)")
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(bananas,bananas,bananas) (bananas,bananas,bananas)")
+                       assert(p1 == nil and p2 == nil)
+               end)
+
+               it("requires parenthesis & valid numbers", function()
+                       local p1, p2 = core.string_to_area("(10.0, 5, -2  30.2,   4, -12.53")
+                       assert(p1 == nil and p2 == nil)
+
+                       p1, p2 = core.string_to_area("(10.0, 5,) -2  fgdf2,   4, -12.53")
+                       assert(p1 == nil and p2 == nil)
+               end)
+       end)
+end)
+
 describe("table", function()
        it("indexof()", function()
                assert.equal(1, table.indexof({"foo", "bar"}, "foo"))
                assert.equal(-1, table.indexof({"foo", "bar"}, "baz"))
        end)
 end)
+
+describe("formspec_escape", function()
+       it("escapes", function()
+               assert.equal(nil, core.formspec_escape(nil))
+               assert.equal("", core.formspec_escape(""))
+               assert.equal("\\[Hello\\\\\\[", core.formspec_escape("[Hello\\["))
+       end)
+end)
index c4fb6314e564e2f90e6e7bd9e7190b4863d5040d..bbcdcf2d060a7a07d680e6b59607cb0aa67743bd 100644 (file)
@@ -130,8 +130,13 @@ local function parse_range_str(player_name, str)
                        return false, S("Unable to get position of player @1.", player_name)
                end
        else
-               p1, p2 = core.string_to_area(str)
-               if p1 == nil then
+               local player = core.get_player_by_name(player_name)
+               local relpos
+               if player then
+                       relpos = player:get_pos()
+               end
+               p1, p2 = core.string_to_area(str, relpos)
+               if p1 == nil or p2 == nil then
                        return false, S("Incorrect area format. "
                                .. "Expected: (x1,y1,z1) (x2,y2,z2)")
                end
@@ -570,10 +575,15 @@ core.register_chatcommand("teleport", {
        description = S("Teleport to position or player"),
        privs = {teleport=true},
        func = function(name, param)
+               local player = core.get_player_by_name(name)
+               local relpos
+               if player then
+                       relpos = player:get_pos()
+               end
                local p = {}
-               p.x, p.y, p.z = param:match("^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
-               p = vector.apply(p, tonumber)
-               if p.x and p.y and p.z then
+               p.x, p.y, p.z = string.match(param, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
+               p = core.parse_coordinates(p.x, p.y, p.z, relpos)
+               if p and p.x and p.y and p.z then
                        return teleport_to_pos(name, p)
                end
 
@@ -587,9 +597,19 @@ core.register_chatcommand("teleport", {
                        "other players (missing privilege: @1).", "bring")
 
                local teleportee_name
+               p = {}
                teleportee_name, p.x, p.y, p.z = param:match(
-                               "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
+                               "^([^ ]+) +([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
+               if teleportee_name then
+                       local teleportee = core.get_player_by_name(teleportee_name)
+                       if not teleportee then
+                               return
+                       end
+                       relpos = teleportee:get_pos()
+                       p = core.parse_coordinates(p.x, p.y, p.z, relpos)
+               end
                p = vector.apply(p, tonumber)
+
                if teleportee_name and p.x and p.y and p.z then
                        if not has_bring_priv then
                                return false, missing_bring_msg
@@ -842,7 +862,7 @@ core.register_chatcommand("spawnentity", {
        description = S("Spawn entity at given (or your) position"),
        privs = {give=true, interact=true},
        func = function(name, param)
-               local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
+               local entityname, pstr = string.match(param, "^([^ ]+) *(.*)$")
                if not entityname then
                        return false, S("EntityName required.")
                end
@@ -856,11 +876,15 @@ core.register_chatcommand("spawnentity", {
                if not core.registered_entities[entityname] then
                        return false, S("Cannot spawn an unknown entity.")
                end
-               if p == "" then
+               local p
+               if pstr == "" then
                        p = player:get_pos()
                else
-                       p = core.string_to_pos(p)
-                       if p == nil then
+                       p = {}
+                       p.x, p.y, p.z = string.match(pstr, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
+                       local relpos = player:get_pos()
+                       p = core.parse_coordinates(p.x, p.y, p.z, relpos)
+                       if not (p and p.x and p.y and p.z) then
                                return false, S("Invalid parameters (@1).", param)
                        end
                end
@@ -1019,6 +1043,13 @@ core.register_chatcommand("status", {
        end,
 })
 
+local function get_time(timeofday)
+       local time = math.floor(timeofday * 1440)
+       local minute = time % 60
+       local hour = (time - minute) / 60
+       return time, hour, minute
+end
+
 core.register_chatcommand("time", {
        params = S("[<0..23>:<0..59> | <0..24000>]"),
        description = S("Show or set time of day"),
@@ -1037,9 +1068,14 @@ core.register_chatcommand("time", {
                        return false, S("You don't have permission to run "
                                .. "this command (missing privilege: @1).", "settime")
                end
-               local hour, minute = param:match("^(%d+):(%d+)$")
-               if not hour then
-                       local new_time = tonumber(param) or -1
+               local relative, negative, hour, minute = param:match("^(~?)(%-?)(%d+):(%d+)$")
+               if not relative then -- checking the first capture against nil suffices
+                       local new_time = core.parse_relative_number(param, core.get_timeofday() * 24000)
+                       if not new_time then
+                               new_time = tonumber(param) or -1
+                       else
+                               new_time = new_time % 24000
+                       end
                        if new_time ~= new_time or new_time < 0 or new_time > 24000 then
                                return false, S("Invalid time (must be between 0 and 24000).")
                        end
@@ -1047,14 +1083,29 @@ core.register_chatcommand("time", {
                        core.log("action", name .. " sets time to " .. new_time)
                        return true, S("Time of day changed.")
                end
+               local new_time
                hour = tonumber(hour)
                minute = tonumber(minute)
-               if hour < 0 or hour > 23 then
-                       return false, S("Invalid hour (must be between 0 and 23 inclusive).")
-               elseif minute < 0 or minute > 59 then
-                       return false, S("Invalid minute (must be between 0 and 59 inclusive).")
+               if relative == "" then
+                       if hour < 0 or hour > 23 then
+                               return false, S("Invalid hour (must be between 0 and 23 inclusive).")
+                       elseif minute < 0 or minute > 59 then
+                               return false, S("Invalid minute (must be between 0 and 59 inclusive).")
+                       end
+                       new_time = (hour * 60 + minute) / 1440
+               else
+                       if minute < 0 or minute > 59 then
+                               return false, S("Invalid minute (must be between 0 and 59 inclusive).")
+                       end
+                       local current_time = core.get_timeofday()
+                       if negative == "-" then -- negative time
+                               hour, minute = -hour, -minute
+                       end
+                       new_time = (current_time + (hour * 60 + minute) / 1440) % 1
+                       local _
+                       _, hour, minute = get_time(new_time)
                end
-               core.set_timeofday((hour * 60 + minute) / 1440)
+               core.set_timeofday(new_time)
                core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
                return true, S("Time of day changed.")
        end,
@@ -1136,6 +1187,9 @@ core.register_chatcommand("ban", {
                                return true, S("Ban list: @1", ban_list)
                        end
                end
+               if core.is_singleplayer() then
+                       return false, S("You cannot ban players in singleplayer!")
+               end
                if not core.get_player_by_name(param) then
                        return false, S("Player is not online.")
                end
index 0e761d324823548a60f8e31bde51e0d3aaad2711..880978800b24e4d4468607f94b93f6985978a2fb 100644 (file)
@@ -50,7 +50,7 @@ local labels = {
                fgettext("Low"),
                fgettext("Medium"),
                fgettext("High"),
-               fgettext("Ultra High")
+               fgettext("Very High")
        }
 }
 
@@ -219,8 +219,9 @@ local function formspec(tabview, name, tabdata)
                                        .. 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")) .. "]"..
-                       "label[8.25,3.0;" .. fgettext("Dynamic shadows: ") .. "]" ..
-                       "dropdown[8.25,3.5;3.5;dd_shadows;" .. dd_options.shadow_levels[1] .. ";"
+                       "label[8.25,2.8;" .. fgettext("Dynamic shadows:") .. "]" ..
+                       "label[8.25,3.2;" .. fgettext("(game support required)") .. "]" ..
+                                       "dropdown[8.25,3.7;3.5;dd_shadows;" .. dd_options.shadow_levels[1] .. ";"
                                        .. getSettingIndex.ShadowMapping() .. "]"
        else
                tab_string = tab_string ..
@@ -364,11 +365,11 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
                core.settings:set("enable_dynamic_shadows", "false")
        else
                local shadow_presets = {
-                       [2] = { 55,  512,  "true", 0, "false" },
-                       [3] = { 82,  1024, "true", 1, "false" },
-                       [4] = { 240, 2048, "true", 1, "false" },
-                       [5] = { 240, 2048, "true", 2,  "true" },
-                       [6] = { 300, 4096, "true", 2,  "true" },
+                       [2] = { 62,  512,  "true", 0, "false" },
+                       [3] = { 93,  1024, "true", 0, "false" },
+                       [4] = { 140, 2048, "true", 1, "false" },
+                       [5] = { 210, 4096, "true", 2,  "true" },
+                       [6] = { 300, 8192, "true", 2,  "true" },
                }
                local s = shadow_presets[table.indexof(labels.shadow_levels, fields["dd_shadows"])]
                if s then
index d33b296d036939b2ec9c38b35689063ec3d53bde..e434b582f183e5ba9be6f61e7148cd4af7d01a16 100644 (file)
@@ -1,21 +1,13 @@
-# Locate Irrlicht or IrrlichtMt headers on system.
+# Locate IrrlichtMt headers on system.
 
-foreach(libname IN ITEMS IrrlichtMt Irrlicht)
-       string(TOLOWER "${libname}" libname2)
-
-       find_path(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h
-               DOC "Path to the directory with IrrlichtMt includes"
-               PATHS
-               /usr/local/include/${libname2}
-               /usr/include/${libname2}
-               /system/develop/headers/${libname2} #Haiku
-               PATH_SUFFIXES "include/${libname2}"
-       )
-
-       if(IRRLICHT_INCLUDE_DIR)
-               break()
-       endif()
-endforeach()
+find_path(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h
+       DOC "Path to the directory with IrrlichtMt includes"
+       PATHS
+       /usr/local/include/irrlichtmt
+       /usr/include/irrlichtmt
+       /system/develop/headers/irrlichtmt #Haiku
+       PATH_SUFFIXES "include/irrlichtmt"
+)
 
 # Handholding for users
 if(IRRLICHT_INCLUDE_DIR AND (NOT IS_DIRECTORY "${IRRLICHT_INCLUDE_DIR}" OR
index df49bd766ac0432afa6389e737b476171c8423ff..d277429b3f32998b85b5e4b67372b247f9e63712 100644 (file)
@@ -2012,7 +2012,7 @@ Example definition of the capabilities of an item
         max_drop_level=1,
         groupcaps={
             crumbly={maxlevel=2, uses=20, times={[1]=1.60, [2]=1.20, [3]=0.80}}
-        }
+        },
         damage_groups = {fleshy=2},
     }
 
@@ -3552,8 +3552,16 @@ Helper functions
 * `minetest.string_to_pos(string)`: returns a position or `nil`
     * Same but in reverse.
     * If the string can't be parsed to a position, nothing is returned.
-* `minetest.string_to_area("(X1, Y1, Z1) (X2, Y2, Z2)")`: returns two positions
+* `minetest.string_to_area("(X1, Y1, Z1) (X2, Y2, Z2)", relative_to)`:
+    * returns two positions
     * Converts a string representing an area box into two positions
+    * X1, Y1, ... Z2 are coordinates
+    * `relative_to`: Optional. If set to a position, each coordinate
+      can use the tilde notation for relative positions
+    * Tilde notation: "~": Relative coordinate
+                      "~<number>": Relative coordinate plus <number>
+    * Example: `minetest.string_to_area("(1,2,3) (~5,~-5,~)", {x=10,y=10,z=10})`
+      returns `{x=1,y=2,z=3}, {x=15,y=5,z=10}`
 * `minetest.formspec_escape(string)`: returns a string
     * escapes the characters "[", "]", "\", "," and ";", which can not be used
       in formspecs.
@@ -7093,6 +7101,8 @@ object you are working with still exists.
         * `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness)
 * `get_lighting()`: returns the current state of lighting for the player.
     * Result is a table with the same fields as `light_definition` in `set_lighting`.
+* `respawn()`: Respawns the player using the same mechanism as the death screen,
+  including calling on_respawnplayer callbacks.
 
 `PcgRandom`
 -----------
@@ -8554,9 +8564,8 @@ See [Decoration types]. Used by `minetest.register_decoration`.
 
         spawn_by = "default:water",
         -- Node (or list of nodes) that the decoration only spawns next to.
-        -- Checks two horizontal planes of 8 neighbouring nodes (including
-        -- diagonal neighbours), one plane level with the 'place_on' node and a
-        -- plane one node above that.
+        -- Checks the 8 neighbouring nodes on the same Y, and also the ones
+        -- at Y+1, excluding both center nodes.
 
         num_spawn_by = 1,
         -- Number of spawn_by nodes that must be surrounding the decoration
index 6a3601f80981d78a26003dde93f7f322c3dab83f..27a3d002426f3fc0a576723156436a77414b1f36 100644 (file)
@@ -92,8 +92,6 @@ Set password from contents of file
 .B \-\-random\-input
 Enable random user input, for testing (client only)
 .TP
-.B \-\-videomodes
-List available video modes (client only)
 .TP
 .B \-\-speedtests
 Run speed tests
diff --git a/games/devtest/mods/testitems/init.lua b/games/devtest/mods/testitems/init.lua
new file mode 100644 (file)
index 0000000..33ebf50
--- /dev/null
@@ -0,0 +1,55 @@
+local S = minetest.get_translator("testitems")
+
+--
+-- Texture overlays for items
+--
+
+-- For the global overlay color test
+local GLOBAL_COLOR_ARG = "orange"
+
+-- Punch handler to set random color with "color" argument in item metadata
+local overlay_on_use = function(itemstack, user, pointed_thing)
+       local meta = itemstack:get_meta()
+       local color = math.random(0x0, 0xFFFFFF)
+       local colorstr = string.format("#%06x", color)
+       meta:set_string("color", colorstr)
+       minetest.log("action", "[testitems] Color of "..itemstack:get_name().." changed to "..colorstr)
+       return itemstack
+end
+-- Place handler to clear item metadata color
+local overlay_on_place = function(itemstack, user, pointed_thing)
+       local meta = itemstack:get_meta()
+       meta:set_string("color", "")
+       return itemstack
+end
+
+minetest.register_craftitem("testitems:overlay_meta", {
+       description = S("Texture Overlay Test Item, Meta Color") .. "\n" ..
+               S("Image must be a square with rainbow cross (inventory and wield)") .. "\n" ..
+               S("Item meta color must only change square color") .. "\n" ..
+               S("Punch: Set random color") .. "\n" ..
+               S("Place: Clear color"),
+       -- Base texture: A grayscale square (can be colorized)
+       inventory_image = "testitems_overlay_base.png",
+       wield_image = "testitems_overlay_base.png",
+       -- Overlay: A rainbow cross (NOT to be colorized!)
+       inventory_overlay = "testitems_overlay_overlay.png",
+       wield_overlay = "testitems_overlay_overlay.png",
+
+       on_use = overlay_on_use,
+       on_place = overlay_on_place,
+       on_secondary_use = overlay_on_place,
+})
+minetest.register_craftitem("testitems:overlay_global", {
+       description = S("Texture Overlay Test Item, Global Color") .. "\n" ..
+               S("Image must be an orange square with rainbow cross (inventory and wield)"),
+       -- Base texture: A grayscale square (to be colorized)
+       inventory_image = "testitems_overlay_base.png",
+       wield_image = "testitems_overlay_base.png",
+       -- Overlay: A rainbow cross (NOT to be colorized!)
+       inventory_overlay = "testitems_overlay_overlay.png",
+       wield_overlay = "testitems_overlay_overlay.png",
+       color = GLOBAL_COLOR_ARG,
+})
+
+
diff --git a/games/devtest/mods/testitems/mod.conf b/games/devtest/mods/testitems/mod.conf
new file mode 100644 (file)
index 0000000..f91febe
--- /dev/null
@@ -0,0 +1,2 @@
+name = testitems
+description = Test mod to test misc. items that are neither tools nor nodes
diff --git a/games/devtest/mods/testitems/textures/testitems_overlay_base.png b/games/devtest/mods/testitems/textures/testitems_overlay_base.png
new file mode 100644 (file)
index 0000000..f473623
Binary files /dev/null and b/games/devtest/mods/testitems/textures/testitems_overlay_base.png differ
diff --git a/games/devtest/mods/testitems/textures/testitems_overlay_overlay.png b/games/devtest/mods/testitems/textures/testitems_overlay_overlay.png
new file mode 100644 (file)
index 0000000..75a7d9f
Binary files /dev/null and b/games/devtest/mods/testitems/textures/testitems_overlay_overlay.png differ
index 92e2c5630de33025e5d9acf520b2e0850ba383a3..d355c4278e5fcef3afaed8275998329005952032 100644 (file)
@@ -8,3 +8,4 @@ dofile(path.."/properties.lua")
 dofile(path.."/liquids.lua")
 dofile(path.."/light.lua")
 dofile(path.."/textures.lua")
+dofile(path.."/overlays.lua")
diff --git a/games/devtest/mods/testnodes/overlays.lua b/games/devtest/mods/testnodes/overlays.lua
new file mode 100644 (file)
index 0000000..294e06a
--- /dev/null
@@ -0,0 +1,93 @@
+local S = minetest.get_translator("testnodes")
+
+minetest.register_node("testnodes:overlay", {
+       description = S("Texture Overlay Test Node") .. "\n" ..
+               S("Uncolorized"),
+       tiles = {{name = "testnodes_overlayable.png"}},
+       overlay_tiles = {{name = "testnodes_overlay.png"}},
+       groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_color_all", {
+       description = S("Texture Overlay Test Node, Colorized") .. "\n" ..
+               S("param2 changes color"),
+       tiles = {{name = "testnodes_overlayable.png"}},
+       overlay_tiles = {{name = "testnodes_overlay.png"}},
+       paramtype2 = "color",
+       palette = "testnodes_palette_full.png",
+
+
+       groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_color_overlay", {
+       description = S("Texture Overlay Test Node, Colorized Overlay") .. "\n" ..
+               S("param2 changes color of overlay"),
+       tiles = {{name = "testnodes_overlayable.png", color="white"}},
+       overlay_tiles = {{name = "testnodes_overlay.png"}},
+       paramtype2 = "color",
+       palette = "testnodes_palette_full.png",
+
+
+       groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_color_overlayed", {
+       description = S("Texture Overlay Test Node, Colorized Base") .. "\n" ..
+               S("param2 changes color of base texture"),
+       tiles = {{name = "testnodes_overlayable.png"}},
+       overlay_tiles = {{name = "testnodes_overlay.png", color="white"}},
+       paramtype2 = "color",
+       palette = "testnodes_palette_full.png",
+
+
+       groups = { dig_immediate = 2 },
+})
+
+local global_overlay_color = "#FF2000"
+minetest.register_node("testnodes:overlay_global", {
+       description = S("Texture Overlay Test Node, Global Color") .. "\n" ..
+               S("Global color = @1", global_overlay_color),
+       tiles = {{name = "testnodes_overlayable.png"}},
+       overlay_tiles = {{name = "testnodes_overlay.png"}},
+       color = global_overlay_color,
+
+
+       groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_global_color_all", {
+       description = S("Texture Overlay Test Node, Global Color + Colorized") .. "\n" ..
+               S("Global color = @1", global_overlay_color) .. "\n" ..
+               S("param2 changes color"),
+       tiles = {{name = "testnodes_overlayable.png"}},
+       overlay_tiles = {{name = "testnodes_overlay.png"}},
+       color = global_overlay_color,
+       paramtype2 = "color",
+       palette = "testnodes_palette_full.png",
+
+
+       groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_global_color_overlay", {
+       description = S("Texture Overlay Test Node, Global Color + Colorized Overlay") .. "\n" ..
+               S("Global color = @1", global_overlay_color) .. "\n" ..
+               S("param2 changes color of overlay"),
+       tiles = {{name = "testnodes_overlayable.png", color=global_overlay_color}},
+       overlay_tiles = {{name = "testnodes_overlay.png"}},
+       color = global_overlay_color,
+       paramtype2 = "color",
+       palette = "testnodes_palette_full.png",
+
+
+       groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_global_color_overlayed", {
+       description = S("Texture Overlay Test Node, Global Color + Colorized Base") .. "\n" ..
+               S("Global color = @1", global_overlay_color) .. "\n" ..
+               S("param2 changes color of base texture"),
+       tiles = {{name = "testnodes_overlayable.png"}},
+       overlay_tiles = {{name = "testnodes_overlay.png", color=global_overlay_color}},
+       color = global_overlay_color,
+       paramtype2 = "color",
+       palette = "testnodes_palette_full.png",
+
+
+       groups = { dig_immediate = 2 },
+})
diff --git a/games/devtest/mods/testnodes/textures/testnodes_overlay.png b/games/devtest/mods/testnodes/textures/testnodes_overlay.png
new file mode 100644 (file)
index 0000000..1c69b5e
Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_overlay.png differ
diff --git a/games/devtest/mods/testnodes/textures/testnodes_overlayable.png b/games/devtest/mods/testnodes/textures/testnodes_overlayable.png
new file mode 100644 (file)
index 0000000..431bc94
Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_overlayable.png differ
index ba980866a12976281320346b7ec599015dbd629b..4b53004b1d47567d677471d04c7438ca889e0c79 100644 (file)
@@ -48,3 +48,23 @@ local function test_v3s16_metatable(player, pos)
        assert(vector.check(found_pos))
 end
 unittests.register("test_v3s16_metatable", test_v3s16_metatable, {map=true})
+
+local function test_clear_meta(_, pos)
+       local ref = core.get_meta(pos)
+
+       for way = 1, 3 do
+               ref:set_string("foo", "bar")
+               assert(ref:contains("foo"))
+
+               if way == 1 then
+                       ref:from_table({})
+               elseif way == 2 then
+                       ref:from_table(nil)
+               else
+                       ref:set_string("foo", "")
+               end
+
+               assert(#core.find_nodes_with_meta(pos, pos) == 0, "clearing failed " .. way)
+       end
+end
+unittests.register("test_clear_meta", test_clear_meta, {map=true})
index 7bba68a643e02dab2391d13bedcedf4a2349b45c..c068be575558a1186f6a5faeaf9efb8f7f9629e8 100644 (file)
@@ -136,6 +136,7 @@ if(ENABLE_POSTGRESQL)
                if(PostgreSQL_INCLUDE_DIR AND PostgreSQL_LIBRARY)
                        set(PostgreSQL_FOUND TRUE)
                        set(PostgreSQL_INCLUDE_DIRS ${PostgreSQL_INCLUDE_DIR})
+                       set(PostgreSQL_LIBRARIES ${PostgreSQL_LIBRARY})
                endif()
        else()
                find_package(PostgreSQL)
index 2d9d226e47e6797e10215428d5331b85205a77f3..5e31387ab02fc0be00b3d3fdebed552a106b94a3 100644 (file)
@@ -533,6 +533,7 @@ void Client::step(float dtime)
        {
                int num_processed_meshes = 0;
                std::vector<v3s16> blocks_to_ack;
+               bool force_update_shadows = false;
                while (!m_mesh_update_thread.m_queue_out.empty())
                {
                        num_processed_meshes++;
@@ -559,9 +560,11 @@ void Client::step(float dtime)
 
                                        if (is_empty)
                                                delete r.mesh;
-                                       else
+                                       else {
                                                // Replace with the new mesh
                                                block->mesh = r.mesh;
+                                               force_update_shadows = true;
+                                       }
                                }
                        } else {
                                delete r.mesh;
@@ -586,6 +589,10 @@ void Client::step(float dtime)
 
                if (num_processed_meshes > 0)
                        g_profiler->graphAdd("num_processed_meshes", num_processed_meshes);
+
+               auto shadow_renderer = RenderingEngine::get_shadow_renderer();
+               if (shadow_renderer && force_update_shadows)
+                       shadow_renderer->setForceUpdateShadowMap();
        }
 
        /*
@@ -799,7 +806,7 @@ void Client::deletingPeer(con::Peer *peer, bool timeout)
        m_access_denied = true;
        if (timeout)
                m_access_denied_reason = gettext("Connection timed out.");
-       else
+       else if (m_access_denied_reason.empty())
                m_access_denied_reason = gettext("Connection aborted (protocol error?).");
 }
 
index 9e82fc3e453ad69d5bb8a451749de79a5bf324d3..263601121d723d114b803b3efaca4d57be40d236 100644 (file)
@@ -1417,30 +1417,22 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
 
                        scene::SMeshBuffer *buf = new scene::SMeshBuffer();
                        buf->Material = material;
-                       switch (p.layer.material_type) {
-                       // list of transparent materials taken from tile.h
-                       case TILE_MATERIAL_ALPHA:
-                       case TILE_MATERIAL_LIQUID_TRANSPARENT:
-                       case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
-                               {
-                                       buf->append(&p.vertices[0], p.vertices.size(),
-                                               &p.indices[0], 0);
-
-                                       MeshTriangle t;
-                                       t.buffer = buf;
-                                       for (u32 i = 0; i < p.indices.size(); i += 3) {
-                                               t.p1 = p.indices[i];
-                                               t.p2 = p.indices[i + 1];
-                                               t.p3 = p.indices[i + 2];
-                                               t.updateAttributes();
-                                               m_transparent_triangles.push_back(t);
-                                       }
+                       if (p.layer.isTransparent()) {
+                               buf->append(&p.vertices[0], p.vertices.size(), nullptr, 0);
+
+                               MeshTriangle t;
+                               t.buffer = buf;
+                               m_transparent_triangles.reserve(p.indices.size() / 3);
+                               for (u32 i = 0; i < p.indices.size(); i += 3) {
+                                       t.p1 = p.indices[i];
+                                       t.p2 = p.indices[i + 1];
+                                       t.p3 = p.indices[i + 2];
+                                       t.updateAttributes();
+                                       m_transparent_triangles.push_back(t);
                                }
-                               break;
-                       default:
+                       } else {
                                buf->append(&p.vertices[0], p.vertices.size(),
                                        &p.indices[0], p.indices.size());
-                               break;
                        }
                        mesh->addMeshBuffer(buf);
                        buf->drop();
index 6ebcc784d151abd0ccb7fecc6ec38fbf07dc096c..224efce3ec4a9879890f62eccc555417dfbfa1a6 100644 (file)
@@ -637,25 +637,10 @@ float RenderingEngine::getDisplayDensity()
 
 #endif
 
-v2u32 RenderingEngine::getDisplaySize()
-{
-       IrrlichtDevice *nulldevice = createDevice(video::EDT_NULL);
-
-       core::dimension2d<u32> deskres =
-                       nulldevice->getVideoModeList()->getDesktopResolution();
-       nulldevice->drop();
-
-       return deskres;
-}
-
 #else // __ANDROID__
 float RenderingEngine::getDisplayDensity()
 {
        return porting::getDisplayDensity();
 }
 
-v2u32 RenderingEngine::getDisplaySize()
-{
-       return porting::getDisplaySize();
-}
 #endif // __ANDROID__
index 6f104bba98e2ee09eb0e8963f0a39082a712752e..38420010f65d1cecb17181bd34936a976d9060f7 100644 (file)
@@ -55,7 +55,6 @@ class RenderingEngine
 
        static const VideoDriverInfo &getVideoDriverInfo(irr::video::E_DRIVER_TYPE type);
        static float getDisplayDensity();
-       static v2u32 getDisplaySize();
 
        bool setupTopLevelWindow(const std::string &name);
        void setupTopLevelXorgWindow(const std::string &name);
index ca2d3ce379954e6d0dce6f96629c838fbcd8e26c..9f26ba94ab8f673227def3d7520c87300071ba16 100644 (file)
@@ -27,10 +27,24 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 using m4f = core::matrix4;
 
+static v3f quantizeDirection(v3f direction, float step)
+{
+
+       float yaw = std::atan2(direction.Z, direction.X);
+       float pitch = std::asin(direction.Y); // assume look is normalized
+
+       yaw = std::floor(yaw / step) * step;
+       pitch = std::floor(pitch / step) * step;
+
+       return v3f(std::cos(yaw)*std::cos(pitch), std::sin(pitch), std::sin(yaw)*std::cos(pitch));
+}
+
 void DirectionalLight::createSplitMatrices(const Camera *cam)
 {
+       const float DISTANCE_STEP = BS * 2.0; // 2 meters
        v3f newCenter;
        v3f look = cam->getDirection();
+       look = quantizeDirection(look, M_PI / 12.0); // 15 degrees
 
        // camera view tangents
        float tanFovY = tanf(cam->getFovY() * 0.5f);
@@ -42,6 +56,10 @@ void DirectionalLight::createSplitMatrices(const Camera *cam)
 
        // adjusted camera positions
        v3f cam_pos_world = cam->getPosition();
+       cam_pos_world = v3f(
+                       floor(cam_pos_world.X / DISTANCE_STEP) * DISTANCE_STEP,
+                       floor(cam_pos_world.Y / DISTANCE_STEP) * DISTANCE_STEP,
+                       floor(cam_pos_world.Z / DISTANCE_STEP) * DISTANCE_STEP);
        v3f cam_pos_scene = v3f(cam_pos_world.X - cam->getOffset().X * BS,
                        cam_pos_world.Y - cam->getOffset().Y * BS,
                        cam_pos_world.Z - cam->getOffset().Z * BS);
@@ -61,7 +79,7 @@ void DirectionalLight::createSplitMatrices(const Camera *cam)
        v3f boundVec = (cam_pos_scene + farCorner * sfFar) - center_scene;
        float radius = boundVec.getLength();
        float length = radius * 3.0f;
-       v3f eye_displacement = direction * length;
+       v3f eye_displacement = quantizeDirection(direction, M_PI / 2880 /*15 seconds*/) * length;
 
        // we must compute the viewmat with the position - the camera offset
        // but the future_frustum position must be the actual world position
index c13cfe252894af9adc2e63ed137d5ad0df399b9e..b8ceeb6239e48eaef04d6d31926a1aced79bc2e3 100644 (file)
@@ -242,7 +242,7 @@ void ShadowRenderer::updateSMTextures()
 
                // detect if SM should be regenerated
                for (DirectionalLight &light : m_light_list) {
-                       if (light.should_update_map_shadow) {
+                       if (light.should_update_map_shadow || m_force_update_shadow_map) {
                                light.should_update_map_shadow = false;
                                m_current_frame = 0;
                                reset_sm_texture = true;
@@ -271,14 +271,14 @@ void ShadowRenderer::updateSMTextures()
                        // should put some gl* fn here
 
 
-                       if (m_current_frame < m_map_shadow_update_frames) {
+                       if (m_current_frame < m_map_shadow_update_frames || m_force_update_shadow_map) {
                                m_driver->setRenderTarget(shadowMapTargetTexture, reset_sm_texture, true,
                                                video::SColor(255, 255, 255, 255));
                                renderShadowMap(shadowMapTargetTexture, light);
 
                                // Render transparent part in one pass.
                                // This is also handled in ClientMap.
-                               if (m_current_frame == m_map_shadow_update_frames - 1) {
+                               if (m_current_frame == m_map_shadow_update_frames - 1 || m_force_update_shadow_map) {
                                        if (m_shadow_map_colored) {
                                                m_driver->setRenderTarget(0, false, false);
                                                m_driver->setRenderTarget(shadowMapTextureColors,
@@ -298,7 +298,7 @@ void ShadowRenderer::updateSMTextures()
                        ++m_current_frame;
 
                // pass finished, swap textures and commit light changes
-               if (m_current_frame == m_map_shadow_update_frames) {
+               if (m_current_frame == m_map_shadow_update_frames || m_force_update_shadow_map) {
                        if (shadowMapClientMapFuture != nullptr)
                                std::swap(shadowMapClientMapFuture, shadowMapClientMap);
 
@@ -306,6 +306,7 @@ void ShadowRenderer::updateSMTextures()
                        for (DirectionalLight &light : m_light_list)
                                light.commitFrustum();
                }
+               m_force_update_shadow_map = false;
        }
 }
 
@@ -432,7 +433,10 @@ void ShadowRenderer::renderShadowMap(video::ITexture *target,
                m_driver->setTransform(video::ETS_WORLD,
                                map_node->getAbsoluteTransformation());
 
-               map_node->renderMapShadows(m_driver, material, pass, m_current_frame, m_map_shadow_update_frames);
+               int frame = m_force_update_shadow_map ? 0 : m_current_frame;
+               int total_frames = m_force_update_shadow_map ? 1 : m_map_shadow_update_frames;
+
+               map_node->renderMapShadows(m_driver, material, pass, frame, total_frames);
                break;
        }
 }
index 0e4ef6b70cd29ee220845531cf2a6644db2c0832..2e3b58f6f9e90d6fbd6e73caa7d6d584cd621c94 100644 (file)
@@ -74,6 +74,7 @@ class ShadowRenderer
        void removeNodeFromShadowList(scene::ISceneNode *node);
 
        void update(video::ITexture *outputTarget = nullptr);
+       void setForceUpdateShadowMap() { m_force_update_shadow_map = true; }
        void drawDebug();
 
        video::ITexture *get_texture()
@@ -131,6 +132,7 @@ class ShadowRenderer
        bool m_shadows_enabled;
        bool m_shadows_supported;
        bool m_shadow_map_colored;
+       bool m_force_update_shadow_map;
        u8 m_map_shadow_update_frames; /* Use this number of frames to update map shaodw */
        u8 m_current_frame{0}; /* Current frame */
        f32 m_perspective_bias_xy;
index d27f63d9404fc9a7ea9cbe6a12d84c7abd1df3ee..b05bed49008bb595c8030ad2349433886e233a56 100644 (file)
@@ -252,13 +252,11 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
                                return retval;
 
                        m_jni_field_name = field_name;
-                       /*~ Imperative, as in "Enter/type in text".
-                       Don't forget the space. */
-                       std::string message = gettext("Enter ");
                        std::string label = wide_to_utf8(getLabelByID(hovered->getID()));
                        if (label.empty())
                                label = "text";
-                       message += strgettext(label) + ":";
+                       /*~ Imperative, as in "Type in text" */
+                       std::string message = fmtgettext("Enter %s:");
 
                        // single line text input
                        int type = 2;
index 51652fe0a059eeaa918b6a313e272c489fdd2498..ef998f16196c38251a98eba5cd8d93905399cd3c 100644 (file)
@@ -152,12 +152,14 @@ void Logger::addOutput(ILogOutput *out)
 
 void Logger::addOutput(ILogOutput *out, LogLevel lev)
 {
+       MutexAutoLock lock(m_mutex);
        m_outputs[lev].push_back(out);
        m_has_outputs[lev] = true;
 }
 
 void Logger::addOutputMasked(ILogOutput *out, LogLevelMask mask)
 {
+       MutexAutoLock lock(m_mutex);
        for (size_t i = 0; i < LL_MAX; i++) {
                if (mask & LOGLEVEL_TO_MASKLEVEL(i)) {
                        m_outputs[i].push_back(out);
@@ -168,6 +170,7 @@ void Logger::addOutputMasked(ILogOutput *out, LogLevelMask mask)
 
 void Logger::addOutputMaxLevel(ILogOutput *out, LogLevel lev)
 {
+       MutexAutoLock lock(m_mutex);
        assert(lev < LL_MAX);
        for (size_t i = 0; i <= lev; i++) {
                m_outputs[i].push_back(out);
@@ -177,6 +180,7 @@ void Logger::addOutputMaxLevel(ILogOutput *out, LogLevel lev)
 
 LogLevelMask Logger::removeOutput(ILogOutput *out)
 {
+       MutexAutoLock lock(m_mutex);
        LogLevelMask ret_mask = 0;
        for (size_t i = 0; i < LL_MAX; i++) {
                std::vector<ILogOutput *>::iterator it;
@@ -386,6 +390,6 @@ void LogOutputBuffer::logRaw(LogLevel lev, const std::string &line)
                default: break;
                }
        }
-
-       m_buffer.push(color.append(line));
+       MutexAutoLock lock(m_buffer_mutex);
+       m_buffer.emplace(color.append(line));
 }
index 5c6ba7d64ff41e559cdddef2e08be9f1975f8db1..0a84332e70b80416fcff76f500556b053a63e6e1 100644 (file)
--- a/src/log.h
+++ b/src/log.h
@@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #if !defined(_WIN32)  // POSIX
        #include <unistd.h>
 #endif
+#include "threading/mutex_auto_lock.h"
 #include "util/basic_macros.h"
 #include "util/stream.h"
 #include "irrlichttypes.h"
@@ -168,24 +169,31 @@ class LogOutputBuffer : public ICombinedLogOutput {
 
        void clear()
        {
+               MutexAutoLock lock(m_buffer_mutex);
                m_buffer = std::queue<std::string>();
        }
 
        bool empty() const
        {
+               MutexAutoLock lock(m_buffer_mutex);
                return m_buffer.empty();
        }
 
        std::string get()
        {
-               if (empty())
+               MutexAutoLock lock(m_buffer_mutex);
+               if (m_buffer.empty())
                        return "";
-               std::string s = m_buffer.front();
+               std::string s = std::move(m_buffer.front());
                m_buffer.pop();
                return s;
        }
 
 private:
+       // g_logger serializes calls to logRaw() with a mutex, but that
+       // doesn't prevent get() / clear() from being called on top of it.
+       // This mutex prevents that.
+       mutable std::mutex m_buffer_mutex;
        std::queue<std::string> m_buffer;
        Logger &m_logger;
 };
index 7bc1334b0fbf9aa2238e7abfe29e2006e5c74628..cb63d258339423c1283547ae854709cee10cad27 100644 (file)
@@ -102,7 +102,7 @@ MapSector * Map::getSectorNoGenerateNoLock(v2s16 p)
                return sector;
        }
 
-       std::map<v2s16, MapSector*>::iterator n = m_sectors.find(p);
+       auto n = m_sectors.find(p);
 
        if (n == m_sectors.end())
                return NULL;
index 248312ebe5436d3f6539fe949ed9931b808583b9..499609534db20d2a9b8578b9e79cbf8678506319 100644 (file)
--- a/src/map.h
+++ b/src/map.h
@@ -268,7 +268,7 @@ class Map /*: public NodeContainer*/
 
        std::set<MapEventReceiver*> m_event_receivers;
 
-       std::map<v2s16, MapSector*> m_sectors;
+       std::unordered_map<v2s16, MapSector*> m_sectors;
 
        // Be sure to set this to NULL when the cached sector is deleted
        MapSector *m_sector_cache = nullptr;
index e3a6caa199616f6e416e1dc2c044dea12e1a0c17..2bbc0ebbf9f76d9ab0750afb7d24f2caf5920a1b 100644 (file)
@@ -221,33 +221,36 @@ void MapBlock::expireDayNightDiff()
 /*
        Serialization
 */
+
 // List relevant id-name pairs for ids in the block using nodedef
-// Renumbers the content IDs (starting at 0 and incrementing
-// use static memory requires about 65535 * sizeof(int) ram in order to be
-// sure we can handle all content ids. But it's absolutely worth it as it's
-// a speedup of 4 for one of the major time consuming functions on storing
-// mapblocks.
-static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
+// Renumbers the content IDs (starting at 0 and incrementing)
 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
        const NodeDefManager *nodedef)
 {
-       memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
-
-       std::set<content_t> unknown_contents;
+       // The static memory requires about 65535 * sizeof(int) RAM in order to be
+       // sure we can handle all content ids. But it's absolutely worth it as it's
+       // a speedup of 4 for one of the major time consuming functions on storing
+       // mapblocks.
+       thread_local std::unique_ptr<content_t[]> mapping;
+       static_assert(sizeof(content_t) == 2, "content_t must be 16-bit");
+       if (!mapping)
+               mapping = std::make_unique<content_t[]>(USHRT_MAX + 1);
+
+       memset(mapping.get(), 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
+
+       std::unordered_set<content_t> unknown_contents;
        content_t id_counter = 0;
        for (u32 i = 0; i < MapBlock::nodecount; i++) {
                content_t global_id = nodes[i].getContent();
                content_t id = CONTENT_IGNORE;
 
                // Try to find an existing mapping
-               if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
-                       id = getBlockNodeIdMapping_mapping[global_id];
-               }
-               else
-               {
+               if (mapping[global_id] != 0xFFFF) {
+                       id = mapping[global_id];
+               } else {
                        // We have to assign a new mapping
                        id = id_counter++;
-                       getBlockNodeIdMapping_mapping[global_id] = id;
+                       mapping[global_id] = id;
 
                        const ContentFeatures &f = nodedef->get(global_id);
                        const std::string &name = f.name;
@@ -265,6 +268,7 @@ static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
                                << "Name for node id " << unknown_content << " not known" << std::endl;
        }
 }
+
 // Correct ids in the block to match nodedef based on names.
 // Unknown ones are added to nodedef.
 // Will not update itself to match id-name pairs in nodedef.
index 73bd620fbadb44c9653e3808a2241e7d5af5d44f..42f020e7106b651eeca3ad4e4d3f7630cac56429 100644 (file)
@@ -266,10 +266,12 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox,
        std::vector<aabb3f> &boxes = *p_boxes;
 
        if (nodebox.type == NODEBOX_FIXED || nodebox.type == NODEBOX_LEVELED) {
-               const std::vector<aabb3f> &fixed = nodebox.fixed;
+               const auto &fixed = nodebox.fixed;
                int facedir = n.getFaceDir(nodemgr, true);
                u8 axisdir = facedir>>2;
                facedir&=0x03;
+
+               boxes.reserve(boxes.size() + fixed.size());
                for (aabb3f box : fixed) {
                        if (nodebox.type == NODEBOX_LEVELED)
                                box.MaxEdge.Y = (-0.5f + n.getLevel(nodemgr) / 64.0f) * BS;
@@ -437,41 +439,43 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox,
        {
                size_t boxes_size = boxes.size();
                boxes_size += nodebox.fixed.size();
+               const auto &c = nodebox.getConnected();
+
                if (neighbors & 1)
-                       boxes_size += nodebox.connect_top.size();
+                       boxes_size += c.connect_top.size();
                else
-                       boxes_size += nodebox.disconnected_top.size();
+                       boxes_size += c.disconnected_top.size();
 
                if (neighbors & 2)
-                       boxes_size += nodebox.connect_bottom.size();
+                       boxes_size += c.connect_bottom.size();
                else
-                       boxes_size += nodebox.disconnected_bottom.size();
+                       boxes_size += c.disconnected_bottom.size();
 
                if (neighbors & 4)
-                       boxes_size += nodebox.connect_front.size();
+                       boxes_size += c.connect_front.size();
                else
-                       boxes_size += nodebox.disconnected_front.size();
+                       boxes_size += c.disconnected_front.size();
 
                if (neighbors & 8)
-                       boxes_size += nodebox.connect_left.size();
+                       boxes_size += c.connect_left.size();
                else
-                       boxes_size += nodebox.disconnected_left.size();
+                       boxes_size += c.disconnected_left.size();
 
                if (neighbors & 16)
-                       boxes_size += nodebox.connect_back.size();
+                       boxes_size += c.connect_back.size();
                else
-                       boxes_size += nodebox.disconnected_back.size();
+                       boxes_size += c.disconnected_back.size();
 
                if (neighbors & 32)
-                       boxes_size += nodebox.connect_right.size();
+                       boxes_size += c.connect_right.size();
                else
-                       boxes_size += nodebox.disconnected_right.size();
+                       boxes_size += c.disconnected_right.size();
 
                if (neighbors == 0)
-                       boxes_size += nodebox.disconnected.size();
+                       boxes_size += c.disconnected.size();
 
                if (neighbors < 4)
-                       boxes_size += nodebox.disconnected_sides.size();
+                       boxes_size += c.disconnected_sides.size();
 
                boxes.reserve(boxes_size);
 
@@ -484,47 +488,47 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox,
                BOXESPUSHBACK(nodebox.fixed);
 
                if (neighbors & 1) {
-                       BOXESPUSHBACK(nodebox.connect_top);
+                       BOXESPUSHBACK(c.connect_top);
                } else {
-                       BOXESPUSHBACK(nodebox.disconnected_top);
+                       BOXESPUSHBACK(c.disconnected_top);
                }
 
                if (neighbors & 2) {
-                       BOXESPUSHBACK(nodebox.connect_bottom);
+                       BOXESPUSHBACK(c.connect_bottom);
                } else {
-                       BOXESPUSHBACK(nodebox.disconnected_bottom);
+                       BOXESPUSHBACK(c.disconnected_bottom);
                }
 
                if (neighbors & 4) {
-                       BOXESPUSHBACK(nodebox.connect_front);
+                       BOXESPUSHBACK(c.connect_front);
                } else {
-                       BOXESPUSHBACK(nodebox.disconnected_front);
+                       BOXESPUSHBACK(c.disconnected_front);
                }
 
                if (neighbors & 8) {
-                       BOXESPUSHBACK(nodebox.connect_left);
+                       BOXESPUSHBACK(c.connect_left);
                } else {
-                       BOXESPUSHBACK(nodebox.disconnected_left);
+                       BOXESPUSHBACK(c.disconnected_left);
                }
 
                if (neighbors & 16) {
-                       BOXESPUSHBACK(nodebox.connect_back);
+                       BOXESPUSHBACK(c.connect_back);
                } else {
-                       BOXESPUSHBACK(nodebox.disconnected_back);
+                       BOXESPUSHBACK(c.disconnected_back);
                }
 
                if (neighbors & 32) {
-                       BOXESPUSHBACK(nodebox.connect_right);
+                       BOXESPUSHBACK(c.connect_right);
                } else {
-                       BOXESPUSHBACK(nodebox.disconnected_right);
+                       BOXESPUSHBACK(c.disconnected_right);
                }
 
                if (neighbors == 0) {
-                       BOXESPUSHBACK(nodebox.disconnected);
+                       BOXESPUSHBACK(c.disconnected);
                }
 
                if (neighbors < 4) {
-                       BOXESPUSHBACK(nodebox.disconnected_sides);
+                       BOXESPUSHBACK(c.disconnected_sides);
                }
 
        }
index 2b8ebd77393c8ace40479fc9afb65d8ff5f70e1f..5954dac1e86200ad7d68e642f9c86542320d1e0c 100644 (file)
@@ -56,20 +56,7 @@ void NodeBox::reset()
        wall_bottom = aabb3f(-BS/2, -BS/2, -BS/2, BS/2, -BS/2+BS/16., BS/2);
        wall_side = aabb3f(-BS/2, -BS/2, -BS/2, -BS/2+BS/16., BS/2, BS/2);
        // no default for other parts
-       connect_top.clear();
-       connect_bottom.clear();
-       connect_front.clear();
-       connect_left.clear();
-       connect_back.clear();
-       connect_right.clear();
-       disconnected_top.clear();
-       disconnected_bottom.clear();
-       disconnected_front.clear();
-       disconnected_left.clear();
-       disconnected_back.clear();
-       disconnected_right.clear();
-       disconnected.clear();
-       disconnected_sides.clear();
+       connected.reset();
 }
 
 void NodeBox::serialize(std::ostream &os, u16 protocol_version) const
@@ -99,7 +86,7 @@ void NodeBox::serialize(std::ostream &os, u16 protocol_version) const
                writeV3F32(os, wall_side.MinEdge);
                writeV3F32(os, wall_side.MaxEdge);
                break;
-       case NODEBOX_CONNECTED:
+       case NODEBOX_CONNECTED: {
                writeU8(os, type);
 
 #define WRITEBOX(box) \
@@ -109,22 +96,25 @@ void NodeBox::serialize(std::ostream &os, u16 protocol_version) const
                        writeV3F32(os, i.MaxEdge); \
                };
 
+               const auto &c = getConnected();
+
                WRITEBOX(fixed);
-               WRITEBOX(connect_top);
-               WRITEBOX(connect_bottom);
-               WRITEBOX(connect_front);
-               WRITEBOX(connect_left);
-               WRITEBOX(connect_back);
-               WRITEBOX(connect_right);
-               WRITEBOX(disconnected_top);
-               WRITEBOX(disconnected_bottom);
-               WRITEBOX(disconnected_front);
-               WRITEBOX(disconnected_left);
-               WRITEBOX(disconnected_back);
-               WRITEBOX(disconnected_right);
-               WRITEBOX(disconnected);
-               WRITEBOX(disconnected_sides);
+               WRITEBOX(c.connect_top);
+               WRITEBOX(c.connect_bottom);
+               WRITEBOX(c.connect_front);
+               WRITEBOX(c.connect_left);
+               WRITEBOX(c.connect_back);
+               WRITEBOX(c.connect_right);
+               WRITEBOX(c.disconnected_top);
+               WRITEBOX(c.disconnected_bottom);
+               WRITEBOX(c.disconnected_front);
+               WRITEBOX(c.disconnected_left);
+               WRITEBOX(c.disconnected_back);
+               WRITEBOX(c.disconnected_right);
+               WRITEBOX(c.disconnected);
+               WRITEBOX(c.disconnected_sides);
                break;
+       }
        default:
                writeU8(os, type);
                break;
@@ -173,21 +163,23 @@ void NodeBox::deSerialize(std::istream &is)
 
                u16 count;
 
+               auto &c = getConnected();
+
                READBOXES(fixed);
-               READBOXES(connect_top);
-               READBOXES(connect_bottom);
-               READBOXES(connect_front);
-               READBOXES(connect_left);
-               READBOXES(connect_back);
-               READBOXES(connect_right);
-               READBOXES(disconnected_top);
-               READBOXES(disconnected_bottom);
-               READBOXES(disconnected_front);
-               READBOXES(disconnected_left);
-               READBOXES(disconnected_back);
-               READBOXES(disconnected_right);
-               READBOXES(disconnected);
-               READBOXES(disconnected_sides);
+               READBOXES(c.connect_top);
+               READBOXES(c.connect_bottom);
+               READBOXES(c.connect_front);
+               READBOXES(c.connect_left);
+               READBOXES(c.connect_back);
+               READBOXES(c.connect_right);
+               READBOXES(c.disconnected_top);
+               READBOXES(c.disconnected_bottom);
+               READBOXES(c.disconnected_front);
+               READBOXES(c.disconnected_left);
+               READBOXES(c.disconnected_back);
+               READBOXES(c.disconnected_right);
+               READBOXES(c.disconnected);
+               READBOXES(c.disconnected_sides);
        }
 }
 
@@ -409,9 +401,9 @@ void ContentFeatures::reset()
        drowning = 0;
        light_source = 0;
        damage_per_second = 0;
-       node_box = NodeBox();
-       selection_box = NodeBox();
-       collision_box = NodeBox();
+       node_box.reset();
+       selection_box.reset();
+       collision_box.reset();
        waving = 0;
        legacy_facedir_simple = false;
        legacy_wallmounted = false;
@@ -909,8 +901,15 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
                        solidness = 0;
                        visual_solidness = 1;
                } else {
-                       drawtype = NDT_NORMAL;
-                       solidness = 2;
+                       if (waving >= 1) {
+                               // waving nodes must make faces so there are no gaps
+                               drawtype = NDT_ALLFACES;
+                               solidness = 0;
+                               visual_solidness = 1;
+                       } else {
+                               drawtype = NDT_NORMAL;
+                               solidness = 2;
+                       }
                        for (TileDef &td : tdef)
                                td.name += std::string("^[noalpha");
                }
@@ -1091,10 +1090,8 @@ void NodeDefManager::clear()
        {
                ContentFeatures f;
                f.name = "unknown";
-               TileDef unknownTile;
-               unknownTile.name = "unknown_node.png";
                for (int t = 0; t < 6; t++)
-                       f.tiledef[t] = unknownTile;
+                       f.tiledef[t].name = "unknown_node.png";
                // Insert directly into containers
                content_t c = CONTENT_UNKNOWN;
                m_content_features[c] = f;
@@ -1296,22 +1293,23 @@ void getNodeBoxUnion(const NodeBox &nodebox, const ContentFeatures &features,
                        break;
                }
                case NODEBOX_CONNECTED: {
+                       const auto &c = nodebox.getConnected();
                        // Add all possible connected boxes
-                       boxVectorUnion(nodebox.fixed,               box_union);
-                       boxVectorUnion(nodebox.connect_top,         box_union);
-                       boxVectorUnion(nodebox.connect_bottom,      box_union);
-                       boxVectorUnion(nodebox.connect_front,       box_union);
-                       boxVectorUnion(nodebox.connect_left,        box_union);
-                       boxVectorUnion(nodebox.connect_back,        box_union);
-                       boxVectorUnion(nodebox.connect_right,       box_union);
-                       boxVectorUnion(nodebox.disconnected_top,    box_union);
-                       boxVectorUnion(nodebox.disconnected_bottom, box_union);
-                       boxVectorUnion(nodebox.disconnected_front,  box_union);
-                       boxVectorUnion(nodebox.disconnected_left,   box_union);
-                       boxVectorUnion(nodebox.disconnected_back,   box_union);
-                       boxVectorUnion(nodebox.disconnected_right,  box_union);
-                       boxVectorUnion(nodebox.disconnected,        box_union);
-                       boxVectorUnion(nodebox.disconnected_sides,  box_union);
+                       boxVectorUnion(nodebox.fixed,         box_union);
+                       boxVectorUnion(c.connect_top,         box_union);
+                       boxVectorUnion(c.connect_bottom,      box_union);
+                       boxVectorUnion(c.connect_front,       box_union);
+                       boxVectorUnion(c.connect_left,        box_union);
+                       boxVectorUnion(c.connect_back,        box_union);
+                       boxVectorUnion(c.connect_right,       box_union);
+                       boxVectorUnion(c.disconnected_top,    box_union);
+                       boxVectorUnion(c.disconnected_bottom, box_union);
+                       boxVectorUnion(c.disconnected_front,  box_union);
+                       boxVectorUnion(c.disconnected_left,   box_union);
+                       boxVectorUnion(c.disconnected_back,   box_union);
+                       boxVectorUnion(c.disconnected_right,  box_union);
+                       boxVectorUnion(c.disconnected,        box_union);
+                       boxVectorUnion(c.disconnected_sides,  box_union);
                        break;
                }
                default: {
index f90caff8aea500daa4f59271cf1edb0b3d3881dc..edd8d00a7ac5edeaa4e65f24fcd08d743e69a7c7 100644 (file)
@@ -99,17 +99,8 @@ enum NodeBoxType
        NODEBOX_CONNECTED, // optionally draws nodeboxes if a neighbor node attaches
 };
 
-struct NodeBox
+struct NodeBoxConnected
 {
-       enum NodeBoxType type;
-       // NODEBOX_REGULAR (no parameters)
-       // NODEBOX_FIXED
-       std::vector<aabb3f> fixed;
-       // NODEBOX_WALLMOUNTED
-       aabb3f wall_top;
-       aabb3f wall_bottom;
-       aabb3f wall_side; // being at the -X side
-       // NODEBOX_CONNECTED
        std::vector<aabb3f> connect_top;
        std::vector<aabb3f> connect_bottom;
        std::vector<aabb3f> connect_front;
@@ -124,9 +115,35 @@ struct NodeBox
        std::vector<aabb3f> disconnected_right;
        std::vector<aabb3f> disconnected;
        std::vector<aabb3f> disconnected_sides;
+};
+
+struct NodeBox
+{
+       enum NodeBoxType type;
+       // NODEBOX_REGULAR (no parameters)
+       // NODEBOX_FIXED
+       std::vector<aabb3f> fixed;
+       // NODEBOX_WALLMOUNTED
+       aabb3f wall_top;
+       aabb3f wall_bottom;
+       aabb3f wall_side; // being at the -X side
+       // NODEBOX_CONNECTED
+       // (kept externally to not bloat the structure)
+       std::shared_ptr<NodeBoxConnected> connected;
 
        NodeBox()
        { reset(); }
+       ~NodeBox() = default;
+
+       inline NodeBoxConnected &getConnected() {
+               if (!connected)
+                       connected = std::make_shared<NodeBoxConnected>();
+               return *connected;
+       }
+       inline const NodeBoxConnected &getConnected() const {
+               assert(connected);
+               return *connected;
+       }
 
        void reset();
        void serialize(std::ostream &os, u16 protocol_version) const;
@@ -290,7 +307,6 @@ struct ContentFeatures
        // up    down  right left  back  front
        TileSpec tiles[6];
        // Special tiles
-       // - Currently used for flowing liquids
        TileSpec special_tiles[CF_SPECIAL_COUNT];
        u8 solidness; // Used when choosing which face is drawn
        u8 visual_solidness; // When solidness=0, this tells how it looks like
@@ -539,7 +555,7 @@ class NodeDefManager {
         */
        inline const ContentFeatures& get(content_t c) const {
                return
-                       c < m_content_features.size() ?
+                       (c < m_content_features.size() && !m_content_features[c].name.empty()) ?
                                m_content_features[c] : m_content_features[CONTENT_UNKNOWN];
        }
 
index 0bdeaab9ee276a518bb926b01cd516f65cd0faa0..b954197c270942bfe40753c9e4c5b2646e89b5ac 100644 (file)
@@ -1055,22 +1055,25 @@ void push_nodebox(lua_State *L, const NodeBox &box)
                        push_aabb3f(L, box.wall_side);
                        lua_setfield(L, -2, "wall_side");
                        break;
-               case NODEBOX_CONNECTED:
+               case NODEBOX_CONNECTED: {
                        lua_pushstring(L, "connected");
                        lua_setfield(L, -2, "type");
-                       push_box(L, box.connect_top);
+                       const auto &c = box.getConnected();
+                       push_box(L, c.connect_top);
                        lua_setfield(L, -2, "connect_top");
-                       push_box(L, box.connect_bottom);
+                       push_box(L, c.connect_bottom);
                        lua_setfield(L, -2, "connect_bottom");
-                       push_box(L, box.connect_front);
+                       push_box(L, c.connect_front);
                        lua_setfield(L, -2, "connect_front");
-                       push_box(L, box.connect_back);
+                       push_box(L, c.connect_back);
                        lua_setfield(L, -2, "connect_back");
-                       push_box(L, box.connect_left);
+                       push_box(L, c.connect_left);
                        lua_setfield(L, -2, "connect_left");
-                       push_box(L, box.connect_right);
+                       push_box(L, c.connect_right);
                        lua_setfield(L, -2, "connect_right");
+                       // half the boxes are missing here?
                        break;
+               }
                default:
                        FATAL_ERROR("Invalid box.type");
                        break;
@@ -1198,20 +1201,24 @@ NodeBox read_nodebox(lua_State *L, int index)
        NODEBOXREAD(nodebox.wall_top, "wall_top");
        NODEBOXREAD(nodebox.wall_bottom, "wall_bottom");
        NODEBOXREAD(nodebox.wall_side, "wall_side");
-       NODEBOXREADVEC(nodebox.connect_top, "connect_top");
-       NODEBOXREADVEC(nodebox.connect_bottom, "connect_bottom");
-       NODEBOXREADVEC(nodebox.connect_front, "connect_front");
-       NODEBOXREADVEC(nodebox.connect_left, "connect_left");
-       NODEBOXREADVEC(nodebox.connect_back, "connect_back");
-       NODEBOXREADVEC(nodebox.connect_right, "connect_right");
-       NODEBOXREADVEC(nodebox.disconnected_top, "disconnected_top");
-       NODEBOXREADVEC(nodebox.disconnected_bottom, "disconnected_bottom");
-       NODEBOXREADVEC(nodebox.disconnected_front, "disconnected_front");
-       NODEBOXREADVEC(nodebox.disconnected_left, "disconnected_left");
-       NODEBOXREADVEC(nodebox.disconnected_back, "disconnected_back");
-       NODEBOXREADVEC(nodebox.disconnected_right, "disconnected_right");
-       NODEBOXREADVEC(nodebox.disconnected, "disconnected");
-       NODEBOXREADVEC(nodebox.disconnected_sides, "disconnected_sides");
+
+       if (nodebox.type == NODEBOX_CONNECTED) {
+               auto &c = nodebox.getConnected();
+               NODEBOXREADVEC(c.connect_top, "connect_top");
+               NODEBOXREADVEC(c.connect_bottom, "connect_bottom");
+               NODEBOXREADVEC(c.connect_front, "connect_front");
+               NODEBOXREADVEC(c.connect_left, "connect_left");
+               NODEBOXREADVEC(c.connect_back, "connect_back");
+               NODEBOXREADVEC(c.connect_right, "connect_right");
+               NODEBOXREADVEC(c.disconnected_top, "disconnected_top");
+               NODEBOXREADVEC(c.disconnected_bottom, "disconnected_bottom");
+               NODEBOXREADVEC(c.disconnected_front, "disconnected_front");
+               NODEBOXREADVEC(c.disconnected_left, "disconnected_left");
+               NODEBOXREADVEC(c.disconnected_back, "disconnected_back");
+               NODEBOXREADVEC(c.disconnected_right, "disconnected_right");
+               NODEBOXREADVEC(c.disconnected, "disconnected");
+               NODEBOXREADVEC(c.disconnected_sides, "disconnected_sides");
+       }
 
        return nodebox;
 }
index 76509038f3ca303f8e6287f46b14fde4ed08f4db..037bd8cf9416a5e3b5f93ee66ead606f3de16d41 100644 (file)
@@ -129,8 +129,6 @@ void ScriptApiSecurity::initializeSecurity()
                "gethook",
                "traceback",
                "getinfo",
-               "getmetatable",
-               "setmetatable",
                "upvalueid",
                "sethook",
                "debug",
index 9220259ff1d6bd59394a9daab87b4755d54115cd..bae6701a095eb803a50ad704a7853a5c06ed2b97 100644 (file)
@@ -599,6 +599,9 @@ int ModApiItemMod::l_register_item_raw(lua_State *L)
                // be done
                if (f.name == "ignore")
                        return 0;
+               // This would break everything
+               if (f.name.empty())
+                       throw LuaError("Cannot register node with empty name");
 
                content_t id = ndef->set(f.name, f);
 
index 1d052685e4fbc5dbae4ef632933448d5cd0bf117..bdc4844c0c282ba7906e7bae28ee8825d665cb01 100644 (file)
@@ -40,7 +40,7 @@ NodeMetaRef* NodeMetaRef::checkobject(lua_State *L, int narg)
 Metadata* NodeMetaRef::getmeta(bool auto_create)
 {
        if (m_is_local)
-               return m_meta;
+               return m_local_meta;
 
        NodeMetadata *meta = m_env->getMap().getNodeMetadata(m_p);
        if (meta == NULL && auto_create) {
@@ -62,9 +62,14 @@ void NodeMetaRef::clearMeta()
 void NodeMetaRef::reportMetadataChange(const std::string *name)
 {
        SANITY_CHECK(!m_is_local);
-       // NOTE: This same code is in rollback_interface.cpp
        // Inform other things that the metadata has changed
-       NodeMetadata *meta = dynamic_cast<NodeMetadata*>(m_meta);
+       NodeMetadata *meta = dynamic_cast<NodeMetadata*>(getmeta(false));
+
+       // If the metadata is now empty, get rid of it
+       if (meta && meta->empty()) {
+               clearMeta();
+               meta = nullptr;
+       }
 
        MapEditEvent event;
        event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
@@ -174,8 +179,8 @@ NodeMetaRef::NodeMetaRef(v3s16 p, ServerEnvironment *env):
 }
 
 NodeMetaRef::NodeMetaRef(Metadata *meta):
-       m_meta(meta),
-       m_is_local(true)
+       m_is_local(true),
+       m_local_meta(meta)
 {
 }
 
index fdc1766ed2073063fbf48ef8bf3f280186b30543..265ece3d056c47e405a6410a29198d12a6110a67 100644 (file)
@@ -33,10 +33,12 @@ class NodeMetadata;
 
 class NodeMetaRef : public MetaDataRef {
 private:
+       bool m_is_local = false;
+       // Set for server metadata
        v3s16 m_p;
        ServerEnvironment *m_env = nullptr;
-       Metadata *m_meta = nullptr;
-       bool m_is_local = false;
+       // Set for client metadata
+       Metadata *m_local_meta = nullptr;
 
        static const char className[];
        static const luaL_Reg methodsServer[];
index 39b19364eb91157d0fa9f7d90f2b90bce923b9ff..37ba1521a1d56026019b0b6406bac6d8e56dcbf4 100644 (file)
@@ -2323,6 +2323,21 @@ int ObjectRef::l_get_lighting(lua_State *L)
        return 1;
 }
 
+// respawn(self)
+int ObjectRef::l_respawn(lua_State *L)
+{
+       NO_MAP_LOCK_REQUIRED;
+       ObjectRef *ref = checkobject(L, 1);
+       RemotePlayer *player = getplayer(ref);
+       if (player == nullptr)
+               return 0;
+
+       getServer(L)->RespawnPlayer(player->getPeerId());
+       lua_pushboolean(L, true);
+       return 1;
+}
+
+
 ObjectRef::ObjectRef(ServerActiveObject *object):
        m_object(object)
 {}
@@ -2478,5 +2493,7 @@ luaL_Reg ObjectRef::methods[] = {
        luamethod(ObjectRef, set_minimap_modes),
        luamethod(ObjectRef, set_lighting),
        luamethod(ObjectRef, get_lighting),
+       luamethod(ObjectRef, respawn),
+
        {0,0}
 };
index 3e4e6681a122b4b9ec80b7756b452dd133cfa971..b36bab49265767f18112667c060516e0c65ca626 100644 (file)
@@ -382,4 +382,7 @@ class ObjectRef : public ModApiBase {
        
        // get_lighting(self)
        static int l_get_lighting(lua_State *L);
+
+       // respawn(self)
+       static int l_respawn(lua_State *L);
 };
index 14398dda218485fc5e67097458fa9d8385a3fe0e..3f3fda56e6b8ab6e70705601de9274018937cb0a 100644 (file)
@@ -27,9 +27,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "log.h"
 
 
-/* This protects:
- * 'secure.*' settings from being set
- * some mapgen settings from being set
+/* This protects the following from being set:
+ * 'secure.*' settings
+ * some security-relevant settings
+ *   (better solution pending)
+ * some mapgen settings
  *   (not security-criticial, just to avoid messing up user configs)
  */
 #define CHECK_SETTING_SECURITY(L, name) \
@@ -41,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 static inline int checkSettingSecurity(lua_State* L, const std::string &name)
 {
        if (ScriptApiSecurity::isSecure(L) && name.compare(0, 7, "secure.") == 0)
-               throw LuaError("Attempt to set secure setting.");
+               throw LuaError("Attempted to set secure setting.");
 
        bool is_mainmenu = false;
 #ifndef SERVER
@@ -54,6 +56,17 @@ static inline int checkSettingSecurity(lua_State* L, const std::string &name)
                return -1;
        }
 
+       const char *disallowed[] = {
+               "main_menu_script", "shader_path", "texture_path", "screenshot_path",
+               "serverlist_file", "serverlist_url", "map-dir", "contentdb_url",
+       };
+       if (!is_mainmenu) {
+               for (const char *name2 : disallowed) {
+                       if (name == name2)
+                               throw LuaError("Attempted to set disallowed setting.");
+               }
+       }
+
        return 0;
 }
 
index b6330c96a839705d0ce7b02dc8ecd413b8411aaa..013c039c3c13ef1ac342240cdb5e37c05b7942e5 100644 (file)
@@ -891,7 +891,7 @@ void Server::AsyncRunStep(bool initial_step)
                // We'll log the amount of each
                Profiler prof;
 
-               std::list<v3s16> node_meta_updates;
+               std::unordered_set<v3s16> node_meta_updates;
 
                while (!m_unsent_map_edit_queue.empty()) {
                        MapEditEvent* event = m_unsent_map_edit_queue.front();
@@ -918,9 +918,7 @@ void Server::AsyncRunStep(bool initial_step)
                        case MEET_BLOCK_NODE_METADATA_CHANGED: {
                                prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1);
                                if (!event->is_private_change) {
-                                       // Don't send the change yet. Collect them to eliminate dupes.
-                                       node_meta_updates.remove(event->p);
-                                       node_meta_updates.push_back(event->p);
+                                       node_meta_updates.emplace(event->p);
                                }
 
                                if (MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(
@@ -973,7 +971,7 @@ void Server::AsyncRunStep(bool initial_step)
                }
 
                // Send all metadata updates
-               if (node_meta_updates.size())
+               if (!node_meta_updates.empty())
                        sendMetadataChanged(node_meta_updates);
        }
 
@@ -2290,12 +2288,12 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_player
        }
 }
 
-void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far_d_nodes)
+void Server::sendMetadataChanged(const std::unordered_set<v3s16> &positions, float far_d_nodes)
 {
-       float maxd = far_d_nodes * BS;
        NodeMetadataList meta_updates_list(false);
-       std::vector<session_t> clients = m_clients.getClientIDs();
+       std::ostringstream os(std::ios::binary);
 
+       std::vector<session_t> clients = m_clients.getClientIDs();
        ClientInterface::AutoLock clientlock(m_clients);
 
        for (session_t i : clients) {
@@ -2303,18 +2301,20 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far
                if (!client)
                        continue;
 
-               ServerActiveObject *player = m_env->getActiveObject(i);
-               v3f player_pos = player ? player->getBasePosition() : v3f();
+               ServerActiveObject *player = getPlayerSAO(i);
+               v3s16 player_pos;
+               if (player)
+                       player_pos = floatToInt(player->getBasePosition(), BS);
 
-               for (const v3s16 &pos : meta_updates) {
+               for (const v3s16 pos : positions) {
                        NodeMetadata *meta = m_env->getMap().getNodeMetadata(pos);
 
                        if (!meta)
                                continue;
 
                        v3s16 block_pos = getNodeBlockPos(pos);
-                       if (!client->isBlockSent(block_pos) || (player &&
-                                       player_pos.getDistanceFrom(intToFloat(pos, BS)) > maxd)) {
+                       if (!client->isBlockSent(block_pos) ||
+                                       player_pos.getDistanceFrom(pos) > far_d_nodes) {
                                client->SetBlockNotSent(block_pos);
                                continue;
                        }
@@ -2326,14 +2326,15 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far
                        continue;
 
                // Send the meta changes
-               std::ostringstream os(std::ios::binary);
+               os.str("");
                meta_updates_list.serialize(os, client->serialization_version, false, true, true);
-               std::ostringstream oss(std::ios::binary);
-               compressZlib(os.str(), oss);
+               std::string raw = os.str();
+               os.str("");
+               compressZlib(raw, os);
 
-               NetworkPacket pkt(TOCLIENT_NODEMETA_CHANGED, 0);
-               pkt.putLongString(oss.str());
-               m_clients.send(i, 0, &pkt, true);
+               NetworkPacket pkt(TOCLIENT_NODEMETA_CHANGED, 0, i);
+               pkt.putLongString(os.str());
+               Send(&pkt);
 
                meta_updates_list.clear();
        }
@@ -2784,9 +2785,10 @@ void Server::RespawnPlayer(session_t peer_id)
                        << playersao->getPlayer()->getName()
                        << " respawns" << std::endl;
 
-       playersao->setHP(playersao->accessObjectProperties()->hp_max,
+       const auto *prop = playersao->accessObjectProperties();
+       playersao->setHP(prop->hp_max,
                        PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN));
-       playersao->setBreath(playersao->accessObjectProperties()->breath_max);
+       playersao->setBreath(prop->breath_max);
 
        bool repositioned = m_script->on_respawnplayer(playersao);
        if (!repositioned) {
index 2c21f5dfc899c5a812b39a1a9f28f96ea4e06773..f1958701f4355e7e50a94bdd4e28ee56da9151c7 100644 (file)
@@ -336,6 +336,8 @@ class Server : public con::PeerHandler, public MapEventReceiver,
 
        void setLighting(RemotePlayer *player, const Lighting &lighting);
 
+       void RespawnPlayer(session_t peer_id);
+
        /* con::PeerHandler implementation. */
        void peerAdded(con::Peer *peer);
        void deletingPeer(con::Peer *peer, bool timeout);
@@ -425,11 +427,10 @@ class Server : public con::PeerHandler, public MapEventReceiver,
                std::unordered_set<session_t> waiting_players;
        };
 
-       // the standard library does not implement std::hash for pairs so we have this:
+       // The standard library does not implement std::hash for pairs so we have this:
        struct SBCHash {
                size_t operator() (const std::pair<v3s16, u16> &p) const {
-                       return (((size_t) p.first.X) << 48) | (((size_t) p.first.Y) << 32) |
-                               (((size_t) p.first.Z) << 16) | ((size_t) p.second);
+                       return std::hash<v3s16>()(p.first) ^ p.second;
                }
        };
 
@@ -491,7 +492,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
                        std::unordered_set<u16> *far_players = nullptr,
                        float far_d_nodes = 100, bool remove_metadata = true);
 
-       void sendMetadataChanged(const std::list<v3s16> &meta_updates,
+       void sendMetadataChanged(const std::unordered_set<v3s16> &positions,
                        float far_d_nodes = 100);
 
        // Environment and Connection must be locked when called
@@ -530,7 +531,6 @@ class Server : public con::PeerHandler, public MapEventReceiver,
        */
 
        void HandlePlayerDeath(PlayerSAO* sao, const PlayerHPChangeReason &reason);
-       void RespawnPlayer(session_t peer_id);
        void DeleteClient(session_t peer_id, ClientDeletionReason reason);
        void UpdateCrafting(RemotePlayer *player);
        bool checkInteractDistance(RemotePlayer *player, const f32 d, const std::string &what);
index 82f6da2314f8392d66b924844f53d3d6a3a385c2..a4b37ee09401a60280ce1eabca0e2f94ae5432e2 100644 (file)
@@ -337,19 +337,9 @@ u32 LuaEntitySAO::punch(v3f dir,
                if (result.did_punch) {
                        setHP((s32)getHP() - result.damage,
                                PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
-
-                       // create message and add to list
-                       sendPunchCommand();
                }
        }
 
-       if (getHP() == 0 && !isGone()) {
-               clearParentAttachment();
-               clearChildAttachments();
-               m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
-               markForRemoval();
-       }
-
        actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
                        ", hp=" << puncher->getHP() << ") punched " <<
                        getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
@@ -402,6 +392,20 @@ std::string LuaEntitySAO::getDescription()
 void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
 {
        m_hp = rangelim(hp, 0, U16_MAX);
+
+       sendPunchCommand();
+
+       if (m_hp == 0 && !isGone()) {
+               clearParentAttachment();
+               clearChildAttachments();
+               if (m_registered) {
+                       ServerActiveObject *killer = nullptr;
+                       if (reason.type == PlayerHPChangeReason::PLAYER_PUNCH)
+                               killer = reason.object;
+                       m_env->getScriptIface()->luaentity_on_death(m_id, killer);
+               }
+               markForRemoval();
+       }       
 }
 
 u16 LuaEntitySAO::getHP() const
index 87b664a8b282708acaaf717581f26df2d8b758e6..5a5aea7a9c4b3ed2174c7e9b090647f1c2844094 100644 (file)
@@ -36,23 +36,30 @@ class LuaEntitySAO : public UnitSAO
        {
        }
        ~LuaEntitySAO();
+
        ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_LUAENTITY; }
        ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; }
        virtual void addedToEnvironment(u32 dtime_s);
        void step(float dtime, bool send_recommended);
        std::string getClientInitializationData(u16 protocol_version);
+
        bool isStaticAllowed() const { return m_prop.static_save; }
        bool shouldUnload() const { return true; }
        void getStaticData(std::string *result) const;
+
        u32 punch(v3f dir, const ToolCapabilities *toolcap = nullptr,
                        ServerActiveObject *puncher = nullptr,
                        float time_from_last_punch = 1000000.0f,
                        u16 initial_wear = 0);
+
        void rightClick(ServerActiveObject *clicker);
+
        void setPos(const v3f &pos);
        void moveTo(v3f pos, bool continuous);
        float getMinimumSavedMovement();
+
        std::string getDescription();
+
        void setHP(s32 hp, const PlayerHPChangeReason &reason);
        u16 getHP() const;
 
index 6a9001052e2008c3817b324f1b7d93c20cd2e67e..2855f04b84c2f03e1460d9b8bba26fc7f05fe548 100644 (file)
@@ -378,10 +378,7 @@ void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
        /*
                Update m_list
        */
-       m_list.clear();
-       for (v3s16 p : newlist) {
-               m_list.insert(p);
-       }
+       m_list = std::move(newlist);
 }
 
 /*
@@ -626,6 +623,9 @@ PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
        /* Add object to environment */
        addActiveObject(playersao);
 
+       // Update active blocks asap so objects in those blocks appear on the client
+       m_force_update_active_blocks = true;
+
        return playersao;
 }
 
@@ -1332,13 +1332,16 @@ void ServerEnvironment::step(float dtime)
        /*
                Manage active block list
        */
-       if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
+       if (m_active_blocks_mgmt_interval.step(dtime, m_cache_active_block_mgmt_interval) ||
+               m_force_update_active_blocks) {
                ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
+
                /*
                        Get player block positions
                */
                std::vector<PlayerSAO*> players;
-               for (RemotePlayer *player: m_players) {
+               players.reserve(m_players.size());
+               for (RemotePlayer *player : m_players) {
                        // Ignore disconnected players
                        if (player->getPeerId() == PEER_ID_INEXISTENT)
                                continue;
@@ -1363,8 +1366,6 @@ void ServerEnvironment::step(float dtime)
                m_active_blocks.update(players, active_block_range, active_object_range,
                        blocks_removed, blocks_added);
 
-               m_active_block_gauge->set(m_active_blocks.size());
-
                /*
                        Handle removed blocks
                */
@@ -1388,14 +1389,21 @@ void ServerEnvironment::step(float dtime)
                for (const v3s16 &p: blocks_added) {
                        MapBlock *block = m_map->getBlockOrEmerge(p);
                        if (!block) {
-                               m_active_blocks.m_list.erase(p);
-                               m_active_blocks.m_abm_list.erase(p);
+                               // TODO: The blocks removed here will only be picked up again
+                               // on the next cycle. To minimize the latency of objects being
+                               // activated we could remember the blocks pending activating
+                               // and activate them instantly as soon as they're loaded.
+                               m_active_blocks.remove(p);
                                continue;
                        }
 
                        activateBlock(block);
                }
+
+               // Some blocks may be removed again by the code above so do this here
+               m_active_block_gauge->set(m_active_blocks.size());
        }
+       m_force_update_active_blocks = false;
 
        /*
                Mess around in active blocks
index 5dc329a60e8f531a686ac4020cea6607a0bc3f1a..00184421eb1bbd481b541f2927599fc03ff5256d 100644 (file)
@@ -180,8 +180,14 @@ class ActiveBlockList
                m_list.clear();
        }
 
+       void remove(v3s16 p) {
+               m_list.erase(p);
+               m_abm_list.erase(p);
+       }
+
        std::set<v3s16> m_list;
        std::set<v3s16> m_abm_list;
+       // list of blocks that are always active, not modified by this class
        std::set<v3s16> m_forceloaded_list;
 };
 
@@ -454,7 +460,8 @@ class ServerEnvironment : public Environment
        IntervalLimiter m_object_management_interval;
        // List of active blocks
        ActiveBlockList m_active_blocks;
-       IntervalLimiter m_active_blocks_management_interval;
+       bool m_force_update_active_blocks = false;
+       IntervalLimiter m_active_blocks_mgmt_interval;
        IntervalLimiter m_active_block_modifier_interval;
        IntervalLimiter m_active_blocks_nodemetadata_interval;
        // Whether the variables below have been read from file yet
index 16327ec308fd75b1b4dfcf1a230611240bbc6ba3..27034b4fbcac196933305b5f6023aafcf169eb8c 100644 (file)
@@ -9,9 +9,6 @@ install_linux_deps() {
 
        if [[ "$1" == "--no-irr" ]]; then
                shift
-       elif [[ "$1" == "--old-irr" ]]; then
-               shift
-               pkgs+=(libirrlicht-dev)
        else
                wget "https://github.com/minetest/irrlicht/releases/download/1.9.0mt6/ubuntu-bionic.tar.gz"
                sudo tar -xaf ubuntu-bionic.tar.gz -C /usr/local