]> 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)
14 files changed:
1  2 
CMakeLists.txt
README.md
builtin/common/misc_helpers.lua
doc/lua_api.txt
src/client/client.cpp
src/client/mapblock_mesh.cpp
src/client/renderingengine.cpp
src/map.cpp
src/map.h
src/nodedef.cpp
src/script/common/c_content.cpp
src/script/cpp_api/s_security.cpp
src/script/lua_api/l_item.cpp
src/serverenvironment.cpp

diff --combined CMakeLists.txt
index 018e233da2b19b23b5ab740a1bae4e46bfa8f0aa,09e3dcccd1f1481a1b54fd9c7e67adb36f1c3d96..fe1cb47eb38751d491da02983c198f5ecad45fc1
@@@ -9,7 -9,7 +9,7 @@@ endif(
  
  # This can be read from ${PROJECT_NAME} after project() is called
  project(minetest)
 -set(PROJECT_NAME_CAPITALIZED "Minetest")
 +set(PROJECT_NAME_CAPITALIZED "Dragonfire")
  
  set(CMAKE_CXX_STANDARD 14)
  set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
@@@ -20,10 -20,10 +20,10 @@@ set(CLANG_MINIMUM_VERSION "3.5"
  set(VERSION_MAJOR 5)
  set(VERSION_MINOR 6)
  set(VERSION_PATCH 0)
 -set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
 +set(VERSION_EXTRA "dragonfire" CACHE STRING "Stuff to append to version string")
  
  # Change to false for releases
 -set(DEVELOPMENT_BUILD TRUE)
 +set(DEVELOPMENT_BUILD FALSE)
  
  set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
  if(VERSION_EXTRA)
@@@ -78,7 -78,6 +78,6 @@@ if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQU
                # 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 +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
@@@ -248,14 -247,14 +247,14 @@@ if(UNIX AND NOT APPLE
        install(FILES "doc/minetest.6" "doc/minetestserver.6" DESTINATION "${MANDIR}/man6")
        install(FILES "misc/net.minetest.minetest.desktop" DESTINATION "${XDG_APPS_DIR}")
        install(FILES "misc/net.minetest.minetest.appdata.xml" DESTINATION "${APPDATADIR}")
 -      install(FILES "misc/minetest.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps")
 -      install(FILES "misc/minetest-xorg-icon-128.png"
 +      install(FILES "misc/dragonfire.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps")
 +      install(FILES "misc/dragonfire-xorg-icon-128.png"
                DESTINATION "${ICONDIR}/hicolor/128x128/apps"
 -              RENAME "minetest.png")
 +              RENAME "dragonfire.png")
  endif()
  
  if(APPLE)
 -      install(FILES "misc/minetest-icon.icns" DESTINATION "${SHAREDIR}")
 +      install(FILES "misc/dragonfire-icon.icns" DESTINATION "${SHAREDIR}")
        install(FILES "misc/Info.plist" DESTINATION "${BUNDLE_PATH}/Contents")
  endif()
  
@@@ -344,7 -343,7 +343,7 @@@ if(WIN32
                set(CPACK_CREATE_DESKTOP_LINKS ${PROJECT_NAME})
                set(CPACK_PACKAGING_INSTALL_PREFIX "/${PROJECT_NAME_CAPITALIZED}")
  
 -              set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/misc/minetest-icon.ico")
 +              set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/misc/dragonfire-icon.ico")
                # Supported languages can be found at
                # http://wixtoolset.org/documentation/manual/v3/wixui/wixui_localization.html
                #set(CPACK_WIX_CULTURES "ar-SA,bg-BG,ca-ES,hr-HR,cs-CZ,da-DK,nl-NL,en-US,et-EE,fi-FI,fr-FR,de-DE")
diff --combined README.md
index 410ca91a01d4eef2b318dc45c60c6c6f8c7ce98f,8d0e68e0cd7fa2fb297609db662dc98eaff9da7f..4fef8f0c04bd3000f106a10ba2e273647625c48c
+++ b/README.md
@@@ -12,7 -12,7 +12,7 @@@ and contributors (see source file comme
  
  In case you downloaded the source code
  --------------------------------------
 -If you downloaded the Minetest Engine source code in which this file is
 +If you downloaded the Dragonfire Client source code in which this file is
  contained, you probably want to download the [Minetest Game](https://github.com/minetest/minetest_game/)
  project too. See its README.txt for more information.
  
@@@ -172,8 -172,8 +172,8 @@@ For Fedora users
  
  Download source (this is the URL to the latest of source repository, which might not work at all times) using Git:
  
 -    git clone --depth 1 https://github.com/minetest/minetest.git
 -    cd minetest
 +    git clone --depth 1 https://github.com/dragonfireclient/dragonfireclient
 +    cd dragonfireclient
  
  Download minetest_game (otherwise only the "Development Test" game is available) using Git:
  
@@@ -223,8 -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 542b2040d6579df334fe444489b3c0ebb41a566b,d2356b50572cedd04e6923ec3b796ea67acba9cf..f8a905c7bbf6d819e9009022fa9bcb4d2c687245
@@@ -204,7 -204,7 +204,7 @@@ en
  
  --------------------------------------------------------------------------------
  function string:trim()
-       return (self:gsub("^%s*(.-)%s*$", "%1"))
+       return self:match("^%s*(.-)%s*$")
  end
  
  --------------------------------------------------------------------------------
@@@ -245,16 -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 +265,21 @@@ function core.wrap_text(text, max_lengt
                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 +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 = {}
@@@ -517,17 -516,6 +516,17 @@@ function table.shuffle(t, from, to, ran
  end
  
  
 +function table.combine(t, other)
 +      other = other or {}
 +      for k, v in pairs(other) do
 +              if type(v) == "table" and type(t[k]) == "table" then
 +                      table.combine(t[k], v)
 +              else
 +                      t[k] = v
 +              end
 +      end
 +end
 +
  --------------------------------------------------------------------------------
  -- mainmenu only functions
  --------------------------------------------------------------------------------
@@@ -595,66 -583,6 +594,66 @@@ function core.colorize(color, message
        return table.concat(lines, "\n") .. core.get_color_escape_sequence("#ffffff")
  end
  
 +local function rgb_to_hex(rgb)
 +      local hexadecimal = "#"
 +
 +      for key, value in pairs(rgb) do
 +              local hex = ""
 +
 +              while(value > 0)do
 +                      local index = math.fmod(value, 16) + 1
 +                      value = math.floor(value / 16)
 +                      hex = string.sub("0123456789ABCDEF", index, index) .. hex
 +              end
 +
 +              if(string.len(hex) == 0)then
 +                      hex = "00"
 +              elseif(string.len(hex) == 1)then
 +                      hex = "0" .. hex
 +              end
 +
 +              hexadecimal = hexadecimal .. hex
 +      end
 +
 +      return hexadecimal
 +end
 +
 +local function color_from_hue(hue)
 +      local h = hue / 60
 +      local c = 255
 +      local x = (1 - math.abs(h % 2 - 1)) * 255
 +
 +      local i = math.floor(h)
 +      if i == 0 then
 +              return rgb_to_hex({c, x, 0})
 +      elseif i == 1 then
 +              return rgb_to_hex({x, c, 0})
 +      elseif i == 2 then
 +              return rgb_to_hex({0, c, x})
 +      elseif i == 3 then
 +              return rgb_to_hex({0, x, c})
 +      elseif i == 4 then
 +              return rgb_to_hex({x, 0, c})
 +      else
 +              return rgb_to_hex({c, 0, x})
 +      end
 +end
 +
 +function core.rainbow(input)
 +      local step = 360 / input:len()
 +      local hue = 0
 +      local output = ""
 +      for i = 1, input:len() do
 +              local char = input:sub(i, i)
 +              if char:match("%s") then
 +                      output = output .. char
 +              else
 +                      output = output  .. core.get_color_escape_sequence(color_from_hue(hue)) .. char
 +              end
 +              hue = hue + step
 +      end
 +      return output
 +end
  
  function core.strip_foreground_colors(str)
        return (str:gsub(ESCAPE_CHAR .. "%(c@[^)]+%)", ""))
@@@ -708,19 -636,6 +707,19 @@@ function core.get_translator(textdomain
        return function(str, ...) return core.translate(textdomain or "", str, ...) end
  end
  
 +function core.get_pointed_thing_position(pointed_thing, above)
 +      if pointed_thing.type == "node" then
 +              if above then
 +                      -- The position where a node would be placed
 +                      return pointed_thing.above
 +              end
 +              -- The position where a node would be dug
 +              return pointed_thing.under
 +      elseif pointed_thing.type == "object" then
 +              return pointed_thing.ref and pointed_thing.ref:get_pos()
 +      end
 +end
 +
  --------------------------------------------------------------------------------
  -- Returns the exact coordinate of a pointed surface
  --------------------------------------------------------------------------------
@@@ -786,11 -701,70 +785,79 @@@ 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
 +      return "[inventorycube"
 +                      .. "{" .. img1:gsub("%^", "&")
 +                      .. "{" .. img2:gsub("%^", "&")
 +                      .. "{" .. img3:gsub("%^", "&")
 +end
diff --combined doc/lua_api.txt
index df49bd766ac0432afa6389e737b476171c8423ff,15a067a5ed82ea037fda7638d5fdeaef00ae4371..d277429b3f32998b85b5e4b67372b247f9e63712
@@@ -2012,7 -2012,7 +2012,7 @@@ Example definition of the capabilities 
          max_drop_level=1,
          groupcaps={
              crumbly={maxlevel=2, uses=20, times={[1]=1.60, [2]=1.20, [3]=0.80}}
-         }
+         },
          damage_groups = {fleshy=2},
      }
  
@@@ -3284,8 -3284,6 +3284,8 @@@ The following functions provide escape 
        `minetest.get_color_escape_sequence(color) ..
        message ..
        minetest.get_color_escape_sequence("#ffffff")`
 +* `minetest.rainbow(message)`:
 +    * Rainbow colorizes the message.
  * `minetest.get_background_escape_sequence(color)`
      * `color` is a ColorString
      * The escape sequence sets the background of the whole text element to
@@@ -3552,8 -3550,16 +3552,16 @@@ Helper function
  * `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 -7099,8 +7101,8 @@@ object you are working with still exist
          * `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 -8562,8 +8564,8 @@@ See [Decoration types]. Used by `minete
  
          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
diff --combined src/client/client.cpp
index 2d9d226e47e6797e10215428d5331b85205a77f3,93a403e81716259213e55f68a9e4ed303695b76d..5e31387ab02fc0be00b3d3fdebed552a106b94a3
@@@ -41,7 -41,6 +41,7 @@@ with this program; if not, write to th
  #include "filesys.h"
  #include "mapblock_mesh.h"
  #include "mapblock.h"
 +#include "mapsector.h"
  #include "minimap.h"
  #include "modchannels.h"
  #include "content/mods.h"
@@@ -103,7 -102,6 +103,7 @@@ Client::Client
                bool ipv6,
                GameUI *game_ui
  ):
 +      m_mesh_update_thread(this),
        m_tsrc(tsrc),
        m_shsrc(shsrc),
        m_itemdef(itemdef),
        m_sound(sound),
        m_event(event),
        m_rendering_engine(rendering_engine),
 -      m_mesh_update_thread(this),
        m_env(
                new ClientMap(this, rendering_engine, control, 666),
                tsrc, this
@@@ -225,8 -224,6 +225,8 @@@ void Client::loadMods(
        // Run a callback when mods are loaded
        m_script->on_mods_loaded();
  
 +      m_script->init_cheats();
 +
        // Create objects if they're ready
        if (m_state == LC_Ready)
                m_script->on_client_ready(m_env.getLocalPlayer());
@@@ -491,7 -488,7 +491,7 @@@ void Client::step(float dtime
                if (envEvent.type == CEE_PLAYER_DAMAGE) {
                        u16 damage = envEvent.player_damage.amount;
  
 -                      if (envEvent.player_damage.send_to_server)
 +                      if (envEvent.player_damage.send_to_server && ! g_settings->getBool("prevent_natural_damage"))
                                sendDamage(damage);
  
                        // Add to ClientEvent queue
        {
                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++;
  
                                        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;
  
                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 -803,7 +806,7 @@@ void Client::deletingPeer(con::Peer *pe
        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?).");
  }
  
@@@ -962,8 -966,8 +969,8 @@@ void Client::Send(NetworkPacket* pkt
  // Will fill up 12 + 12 + 4 + 4 + 4 bytes
  void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt)
  {
 -      v3f pf           = myplayer->getPosition() * 100;
 -      v3f sf           = myplayer->getSpeed() * 100;
 +      v3f pf           = myplayer->getLegitPosition() * 100;
 +      v3f sf           = myplayer->getSendSpeed() * 100;
        s32 pitch        = myplayer->getPitch() * 100;
        s32 yaw          = myplayer->getYaw() * 100;
        u32 keyPressed   = myplayer->control.getKeysPressed();
@@@ -1316,7 -1320,7 +1323,7 @@@ void Client::sendReady(
        Send(&pkt);
  }
  
 -void Client::sendPlayerPos()
 +void Client::sendPlayerPos(v3f pos)
  {
        LocalPlayer *player = m_env.getLocalPlayer();
        if (!player)
        u32 keyPressed = player->control.getKeysPressed();
  
        if (
 -                      player->last_position     == player->getPosition() &&
 -                      player->last_speed        == player->getSpeed()    &&
 +                      player->last_position     == pos &&
 +                      player->last_speed        == player->getSendSpeed()    &&
                        player->last_pitch        == player->getPitch()    &&
                        player->last_yaw          == player->getYaw()      &&
                        player->last_keyPressed   == keyPressed            &&
                        player->last_wanted_range == wanted_range)
                return;
  
 -      player->last_position     = player->getPosition();
 -      player->last_speed        = player->getSpeed();
 +      player->last_position     = pos;
 +      player->last_speed        = player->getSendSpeed();
        player->last_pitch        = player->getPitch();
        player->last_yaw          = player->getYaw();
        player->last_keyPressed   = keyPressed;
        Send(&pkt);
  }
  
 +void Client::sendPlayerPos()
 +{
 +      LocalPlayer *player = m_env.getLocalPlayer();
 +      if (!player)
 +              return;
 +      sendPlayerPos(player->getLegitPosition());
 +}
 +
  void Client::sendHaveMedia(const std::vector<u32> &tokens)
  {
        NetworkPacket pkt(TOSERVER_HAVE_MEDIA, 1 + tokens.size() * 4);
@@@ -1504,7 -1500,6 +1511,7 @@@ Inventory* Client::getInventory(const I
        case InventoryLocation::UNDEFINED:
        {}
        break;
 +      case InventoryLocation::PLAYER:
        case InventoryLocation::CURRENT_PLAYER:
        {
                LocalPlayer *player = m_env.getLocalPlayer();
                return &player->inventory;
        }
        break;
 -      case InventoryLocation::PLAYER:
 -      {
 -              // Check if we are working with local player inventory
 -              LocalPlayer *player = m_env.getLocalPlayer();
 -              if (!player || strcmp(player->getName(), loc.name.c_str()) != 0)
 -                      return NULL;
 -              return &player->inventory;
 -      }
 -      break;
        case InventoryLocation::NODEMETA:
        {
                NodeMetadata *meta = m_env.getMap().getNodeMetadata(loc.p);
@@@ -1675,25 -1679,6 +1682,25 @@@ void Client::addUpdateMeshTaskForNode(v
                addUpdateMeshTask(blockpos + v3s16(0, 0, -1), false, urgent);
  }
  
 +void Client::updateAllMapBlocks()
 +{
 +      v3s16 currentBlock = getNodeBlockPos(floatToInt(m_env.getLocalPlayer()->getPosition(), BS));
 +
 +      for (s16 X = currentBlock.X - 2; X <= currentBlock.X + 2; X++)
 +      for (s16 Y = currentBlock.Y - 2; Y <= currentBlock.Y + 2; Y++)
 +      for (s16 Z = currentBlock.Z - 2; Z <= currentBlock.Z + 2; Z++)
 +              addUpdateMeshTask(v3s16(X, Y, Z), false, true);
 +
 +      Map &map = m_env.getMap();
 +
 +      std::vector<v3s16> positions;
 +      map.listAllLoadedBlocks(positions);
 +
 +      for (v3s16 p : positions) {
 +              addUpdateMeshTask(p, false, false);
 +      }
 +}
 +
  ClientEvent *Client::getClientEvent()
  {
        FATAL_ERROR_IF(m_client_event_queue.empty(),
@@@ -1919,18 -1904,10 +1926,18 @@@ IItemDefManager* Client::getItemDefMana
  {
        return m_itemdef;
  }
 +IWritableItemDefManager* Client::getWritableItemDefManager()
 +{
 +      return m_itemdef;
 +}
  const NodeDefManager* Client::getNodeDefManager()
  {
        return m_nodedef;
  }
 +NodeDefManager* Client::getWritableNodeDefManager()
 +{
 +      return m_nodedef;
 +}
  ICraftDefManager* Client::getCraftDefManager()
  {
        return NULL;
index 9e82fc3e453ad69d5bb8a451749de79a5bf324d3,3be9e13b89558b0beb51ce3e760855e554b74e32..263601121d723d114b803b3efaca4d57be40d236
@@@ -88,7 -88,7 +88,7 @@@ void MeshMakeData::setCrack(int crack_l
  
  void MeshMakeData::setSmoothLighting(bool smooth_lighting)
  {
 -      m_smooth_lighting = smooth_lighting;
 +      m_smooth_lighting = smooth_lighting && ! g_settings->getBool("fullbright");
  }
  
  /*
@@@ -105,8 -105,6 +105,8 @@@ static u8 getInteriorLight(enum LightBa
        u8 light = n.getLight(bank, ndef);
        if (light > 0)
                light = rangelim(light + increment, 0, LIGHT_SUN);
 +      if(g_settings->getBool("fullbright"))
 +              return 255;
        return decode_light(light);
  }
  
@@@ -141,8 -139,7 +141,8 @@@ static u8 getFaceLight(enum LightBank b
                        ndef->get(n2).light_source);
        if(light_source > light)
                light = light_source;
 -
 +      if(g_settings->getBool("fullbright"))
 +              return 255;
        return decode_light(light);
  }
  
@@@ -660,7 -657,6 +660,7 @@@ static u8 face_contents(content_t m1, c
        u8 c1 = f1.solidness;
        u8 c2 = f2.solidness;
  
 +
        if (c1 == c2)
                return 0;
  
        else if (c2 == 0)
                c2 = f2.visual_solidness;
  
 +
        if (c1 == c2) {
                *equivalent = true;
                // If same solidness, liquid takes precense
@@@ -769,24 -764,6 +769,24 @@@ void getNodeTile(MapNode mn, const v3s1
        tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1];
  }
  
 +std::set<content_t> splitToContentT(std::string str, const NodeDefManager *ndef)
 +{
 +      str += "\n";
 +      std::set<content_t> dat;
 +      std::string buf;
 +      for (char c : str) {
 +              if (c == ',' || c == '\n') {
 +                      if (! buf.empty()) {
 +                              dat.insert(ndef->getId(buf));
 +                      }
 +                      buf.clear();
 +              } else if (c != ' ') {
 +                      buf += c;
 +              }
 +      }
 +      return dat;
 +}
 +
  static void getTileInfo(
                // Input:
                MeshMakeData *data,
                v3s16 &face_dir_corrected,
                u16 *lights,
                u8 &waving,
 -              TileSpec &tile
 -      )
 +              TileSpec &tile,
 +              // lol more Input
 +              bool xray,
 +              std::set<content_t> xraySet)
  {
        VoxelManipulator &vmanip = data->m_vmanip;
        const NodeDefManager *ndef = data->m_client->ndef();
  
        const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p);
  
 +      content_t c0 = n0.getContent();
 +      if (xray && xraySet.find(c0) != xraySet.end())
 +              c0 = CONTENT_AIR;
        // Don't even try to get n1 if n0 is already CONTENT_IGNORE
 -      if (n0.getContent() == CONTENT_IGNORE) {
 +      if (c0 == CONTENT_IGNORE) {
                makes_face = false;
                return;
        }
  
        const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir);
  
 -      if (n1.getContent() == CONTENT_IGNORE) {
 +      content_t c1 = n1.getContent();
 +      if (xray && xraySet.find(c1) != xraySet.end())
 +              c1 = CONTENT_AIR;
 +
 +      if (c1 == CONTENT_IGNORE) {
                makes_face = false;
                return;
        }
  
        // This is hackish
        bool equivalent = false;
 -      u8 mf = face_contents(n0.getContent(), n1.getContent(),
 +      u8 mf = face_contents(c0, c1,
                        &equivalent, ndef);
  
        if (mf == 0) {
@@@ -887,9 -855,7 +887,9 @@@ static void updateFastFaceRow
                v3s16 translate_dir,
                const v3f &&translate_dir_f,
                const v3s16 &&face_dir,
 -              std::vector<FastFace> &dest)
 +              std::vector<FastFace> &dest,
 +              bool xray,
 +              std::set<content_t> xraySet)
  {
        static thread_local const bool waving_liquids =
                g_settings->getBool("enable_shaders") &&
        // Get info of first tile
        getTileInfo(data, p, face_dir,
                        makes_face, p_corrected, face_dir_corrected,
 -                      lights, waving, tile);
 +                      lights, waving, tile, xray, xraySet);
  
        // Unroll this variable which has a significant build cost
        TileSpec next_tile;
                                        next_makes_face, next_p_corrected,
                                        next_face_dir_corrected, next_lights,
                                        waving,
 -                                      next_tile);
 +                                      next_tile,
 +                                      xray,
 +                                      xraySet);
  
                        if (!force_not_tiling
                                        && next_makes_face == makes_face
  }
  
  static void updateAllFastFaceRows(MeshMakeData *data,
 -              std::vector<FastFace> &dest)
 +              std::vector<FastFace> &dest, bool xray, std::set<content_t> xraySet)
  {
        /*
                Go through every y,z and get top(y+) faces in rows of x+
                                v3s16(1, 0, 0), //dir
                                v3f  (1, 0, 0),
                                v3s16(0, 1, 0), //face dir
 -                              dest);
 +                              dest,
 +                              xray,
 +                              xraySet);
  
        /*
                Go through every x,y and get right(x+) faces in rows of z+
                                v3s16(0, 0, 1), //dir
                                v3f  (0, 0, 1),
                                v3s16(1, 0, 0), //face dir
 -                              dest);
 +                              dest,
 +                              xray,
 +                              xraySet);
  
        /*
                Go through every y,z and get back(z+) faces in rows of x+
                                v3s16(1, 0, 0), //dir
                                v3f  (1, 0, 0),
                                v3s16(0, 0, 1), //face dir
 -                              dest);
 +                              dest,
 +                              xray,
 +                              xraySet);
  }
  
  static void applyTileColor(PreMeshBuffer &pmb)
@@@ -1243,15 -1201,6 +1243,15 @@@ MapBlockMesh::MapBlockMesh(MeshMakeDat
  
        std::vector<FastFace> fastfaces_new;
        fastfaces_new.reserve(512);
 +      /*
 +              X-Ray
 +      */
 +      bool xray = g_settings->getBool("xray");
 +      std::set<content_t> xraySet, nodeESPSet;
 +      if (xray)
 +              xraySet = splitToContentT(g_settings->get("xray_nodes"), data->m_client->ndef());
 +
 +      nodeESPSet = splitToContentT(g_settings->get("node_esp_nodes"), data->m_client->ndef());
  
        /*
                We are including the faces of the trailing edges of the block.
        {
                // 4-23ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
                //TimeTaker timer2("updateAllFastFaceRows()");
 -              updateAllFastFaceRows(data, fastfaces_new);
 +              updateAllFastFaceRows(data, fastfaces_new, xray, xraySet);
        }
        // End of slow part
  
 +      /*
 +              NodeESP
 +      */
 +      {
 +              v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
 +              for (s16 x = 0; x < MAP_BLOCKSIZE; x++) {
 +                      for (s16 y = 0; y < MAP_BLOCKSIZE; y++) {
 +                              for (s16 z = 0; z < MAP_BLOCKSIZE; z++) {
 +                                      v3s16 pos = v3s16(x, y, z) + blockpos_nodes;
 +                                      const MapNode &node = data->m_vmanip.getNodeRefUnsafeCheckFlags(pos);
 +                                      if (nodeESPSet.find(node.getContent()) != nodeESPSet.end())
 +                                              esp_nodes.insert(pos);
 +                              }
 +                      }
 +              }
 +      }
 +
        /*
                Convert FastFaces to MeshCollector
        */
  
                        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,7afca45004882363b86e7404c334760dc154c67d..224efce3ec4a9879890f62eccc555417dfbfa1a6
@@@ -303,14 -303,15 +303,14 @@@ bool RenderingEngine::setWindowIcon(
  #if defined(XORG_USED)
  #if RUN_IN_PLACE
        return setXorgWindowIconFromPath(
 -                      porting::path_share + "/misc/" PROJECT_NAME "-xorg-icon-128.png");
 +                      porting::path_share + "/misc/dragonfire-xorg-icon-128.png");
  #else
        // We have semi-support for reading in-place data if we are
        // compiled with RUN_IN_PLACE. Don't break with this and
        // also try the path_share location.
        return setXorgWindowIconFromPath(
 -                             ICON_DIR "/hicolor/128x128/apps/" PROJECT_NAME ".png") ||
 -             setXorgWindowIconFromPath(porting::path_share + "/misc/" PROJECT_NAME
 -                                                             "-xorg-icon-128.png");
 +                             ICON_DIR "/hicolor/128x128/apps/dragonfire.png") ||
 +             setXorgWindowIconFromPath(porting::path_share + "/misc/dragonfire-xorg-icon-128.png");
  #endif
  #elif defined(_WIN32)
        HWND hWnd; // Window handle
@@@ -637,25 -638,10 +637,10 @@@ float RenderingEngine::getDisplayDensit
  
  #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__
diff --combined src/map.cpp
index 7bc1334b0fbf9aa2238e7abfe29e2006e5c74628,213844d5759e3a4e731553ad0ffe411bd8247ef7..cb63d258339423c1283547ae854709cee10cad27
@@@ -102,7 -102,7 +102,7 @@@ MapSector * Map::getSectorNoGenerateNoL
                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;
@@@ -139,21 -139,6 +139,21 @@@ MapBlock * Map::getBlockNoCreate(v3s16 
        return block;
  }
  
 +void Map::listAllLoadedBlocks(std::vector<v3s16> &dst)
 +{
 +      for (auto &sector_it : m_sectors) {
 +              MapSector *sector = sector_it.second;
 +
 +              MapBlockVect blocks;
 +              sector->getBlocks(blocks);
 +
 +              for (MapBlock *block : blocks) {
 +                      v3s16 p = block->getPos();
 +                      dst.push_back(p);
 +              }
 +      }
 +}
 +
  bool Map::isValidPosition(v3s16 p)
  {
        v3s16 blockpos = getNodeBlockPos(p);
@@@ -1708,6 -1693,21 +1708,6 @@@ void ServerMap::listAllLoadableBlocks(s
                dbase_ro->listAllLoadableBlocks(dst);
  }
  
 -void ServerMap::listAllLoadedBlocks(std::vector<v3s16> &dst)
 -{
 -      for (auto &sector_it : m_sectors) {
 -              MapSector *sector = sector_it.second;
 -
 -              MapBlockVect blocks;
 -              sector->getBlocks(blocks);
 -
 -              for (MapBlock *block : blocks) {
 -                      v3s16 p = block->getPos();
 -                      dst.push_back(p);
 -              }
 -      }
 -}
 -
  MapDatabase *ServerMap::createDatabase(
        const std::string &name,
        const std::string &savedir,
diff --combined src/map.h
index 248312ebe5436d3f6539fe949ed9931b808583b9,9317642150e2fcf3c0a712baf2569ef821e21b8d..499609534db20d2a9b8578b9e79cbf8678506319
+++ b/src/map.h
@@@ -151,9 -151,7 +151,9 @@@ public
        MapBlock * getBlockNoCreate(v3s16 p);
        // Returns NULL if not found
        MapBlock * getBlockNoCreateNoEx(v3s16 p);
 -
 +      
 +      void listAllLoadedBlocks(std::vector<v3s16> &dst);
 +      
        /* Server overrides */
        virtual MapBlock * emergeBlock(v3s16 p, bool create_blank=true)
        { return getBlockNoCreateNoEx(p); }
@@@ -268,7 -266,7 +268,7 @@@ protected
  
        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;
@@@ -359,6 -357,7 +359,6 @@@ public
  
        void save(ModifiedState save_level) override;
        void listAllLoadableBlocks(std::vector<v3s16> &dst);
 -      void listAllLoadedBlocks(std::vector<v3s16> &dst);
  
        MapgenParams *getMapgenParams();
  
diff --combined src/nodedef.cpp
index 2b8ebd77393c8ace40479fc9afb65d8ff5f70e1f,9c85826c440412d57492618877ef574001390c1b..5954dac1e86200ad7d68e642f9c86542320d1e0c
@@@ -56,20 -56,7 +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 +86,7 @@@
                writeV3F32(os, wall_side.MinEdge);
                writeV3F32(os, wall_side.MaxEdge);
                break;
-       case NODEBOX_CONNECTED:
+       case NODEBOX_CONNECTED: {
                writeU8(os, type);
  
  #define WRITEBOX(box) \
                        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 +163,23 @@@ void NodeBox::deSerialize(std::istream 
  
                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);
        }
  }
  
@@@ -352,7 -344,7 +344,7 @@@ void ContentFeatures::reset(
                Cached stuff
        */
  #ifndef SERVER
 -      solidness = 2;
 +      solidness = 0;
        visual_solidness = 0;
        backface_culling = true;
  
        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 +901,15 @@@ void ContentFeatures::updateTextures(IT
                        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 +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 +1293,23 @@@ void getNodeBoxUnion(const NodeBox &nod
                        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: {
@@@ -1361,31 -1359,15 +1359,31 @@@ void NodeDefManager::eraseIdFromGroups(
  
  
  // IWritableNodeDefManager
 -content_t NodeDefManager::set(const std::string &name, const ContentFeatures &def)
 +content_t NodeDefManager::set(const std::string &name, const ContentFeatures &d)
  {
 +      ContentFeatures def = d;
 +      
        // Pre-conditions
        assert(name != "");
        assert(name != "ignore");
        assert(name == def.name);
  
        content_t id = CONTENT_IGNORE;
 -      if (!m_name_id_mapping.getId(name, id)) { // ignore aliases
 +      
 +      if (m_name_id_mapping.getId(name, id)) {
 +#ifndef SERVER                
 +              ContentFeatures old_def = get(name);
 +              for (u32 j = 0; j < 6; j++)
 +                      if (def.tiledef[j].name.empty())
 +                              def.tiledef[j] = old_def.tiledef[j];
 +              for (u32 j = 0; j < 6; j++)
 +                      if (def.tiledef_overlay[j].name.empty())
 +                              def.tiledef_overlay[j] = old_def.tiledef_overlay[j];
 +              for (u32 j = 0; j < CF_SPECIAL_COUNT; j++)
 +                      if (def.tiledef_special[j].name.empty())
 +                              def.tiledef_special[j] = old_def.tiledef_special[j];
 +#endif
 +      } else {
                // Get new id
                id = allocateId();
                if (id == CONTENT_IGNORE) {
index 0bdeaab9ee276a518bb926b01cd516f65cd0faa0,f232e9e5de6e81a72abe3fe53808c591ae0c8c62..b954197c270942bfe40753c9e4c5b2646e89b5ac
@@@ -23,7 -23,6 +23,7 @@@ with this program; if not, write to th
  #include "object_properties.h"
  #include "collision.h"
  #include "cpp_api/s_node.h"
 +#include "lua_api/l_clientobject.h"
  #include "lua_api/l_object.h"
  #include "lua_api/l_item.h"
  #include "common/c_internal.h"
@@@ -175,12 -174,10 +175,12 @@@ void push_item_definition_full(lua_Stat
        }
        push_groups(L, i.groups);
        lua_setfield(L, -2, "groups");
 +      lua_newtable(L);
        push_soundspec(L, i.sound_place);
 -      lua_setfield(L, -2, "sound_place");
 +      lua_setfield(L, -2, "place");
        push_soundspec(L, i.sound_place_failed);
 -      lua_setfield(L, -2, "sound_place_failed");
 +      lua_setfield(L, -2, "place_failed");
 +      lua_setfield(L, -2, "sounds");
        lua_pushstring(L, i.node_placement_prediction.c_str());
        lua_setfield(L, -2, "node_placement_prediction");
  }
@@@ -200,14 -197,14 +200,14 @@@ void read_object_properties(lua_State *
        if (getintfield(L, -1, "hp_max", hp_max)) {
                prop->hp_max = (u16)rangelim(hp_max, 0, U16_MAX);
  
 -              if (prop->hp_max < sao->getHP()) {
 +              if (sao && prop->hp_max < sao->getHP()) {
                        PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP);
                        sao->setHP(prop->hp_max, reason);
                }
        }
  
        if (getintfield(L, -1, "breath_max", prop->breath_max)) {
 -              if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
 +              if (sao && sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
                        PlayerSAO *player = (PlayerSAO *)sao;
                        if (prop->breath_max < player->getBreath())
                                player->setBreath(prop->breath_max);
@@@ -513,35 -510,6 +513,35 @@@ TileDef read_tiledef(lua_State *L, int 
        return tiledef;
  }
  
 +/******************************************************************************/
 +void push_tiledef(lua_State *L, TileDef tiledef)
 +{
 +      lua_newtable(L);
 +      setstringfield(L, -1, "name", tiledef.name);
 +      setboolfield(L, -1, "backface_culling", tiledef.backface_culling);
 +      setboolfield(L, -1, "tileable_horizontal", tiledef.tileable_horizontal);
 +      setboolfield(L, -1, "tileable_vertical", tiledef.tileable_vertical);
 +      std::string align_style;
 +      switch (tiledef.align_style) {
 +      case ALIGN_STYLE_USER_DEFINED:
 +                      align_style = "user";
 +                      break;
 +      case ALIGN_STYLE_WORLD:
 +                      align_style = "world";
 +                      break;
 +      default:
 +                      align_style = "node";
 +      }
 +      setstringfield(L, -1, "align_style", align_style);
 +      setintfield(L, -1, "scale", tiledef.scale);
 +      if (tiledef.has_color) {
 +              push_ARGB8(L, tiledef.color);
 +              lua_setfield(L, -2, "color");
 +      }
 +      push_animation_definition(L, tiledef.animation);
 +      lua_setfield(L, -2, "animation");
 +}
 +
  /******************************************************************************/
  void read_content_features(lua_State *L, ContentFeatures &f, int index)
  {
@@@ -881,32 -849,9 +881,32 @@@ void push_content_features(lua_State *L
        std::string drawtype(ScriptApiNode::es_DrawType[(int)c.drawtype].str);
        std::string liquid_type(ScriptApiNode::es_LiquidType[(int)c.liquid_type].str);
  
 -      /* Missing "tiles" because I don't see a usecase (at least not yet). */
 +      lua_newtable(L);
 +
 +      // tiles
 +      lua_newtable(L);
 +      for (int i = 0; i < 6; i++) {
 +              push_tiledef(L, c.tiledef[i]);
 +              lua_rawseti(L, -2, i + 1);
 +      }
 +      lua_setfield(L, -2, "tiles");
  
 +      // overlay_tiles
        lua_newtable(L);
 +      for (int i = 0; i < 6; i++) {
 +              push_tiledef(L, c.tiledef_overlay[i]);
 +              lua_rawseti(L, -2, i + 1);
 +      }
 +      lua_setfield(L, -2, "overlay_tiles");
 +
 +      // special_tiles
 +      lua_newtable(L);
 +      for (int i = 0; i < CF_SPECIAL_COUNT; i++) {
 +              push_tiledef(L, c.tiledef_special[i]);
 +              lua_rawseti(L, -2, i + 1);
 +      }
 +      lua_setfield(L, -2, "special_tiles");
 +
        lua_pushboolean(L, c.has_on_construct);
        lua_setfield(L, -2, "has_on_construct");
        lua_pushboolean(L, c.has_on_destruct);
        lua_setfield(L, -2, "collision_box");
        lua_newtable(L);
        push_soundspec(L, c.sound_footstep);
 -      lua_setfield(L, -2, "sound_footstep");
 +      lua_setfield(L, -2, "footstep");
        push_soundspec(L, c.sound_dig);
 -      lua_setfield(L, -2, "sound_dig");
 +      lua_setfield(L, -2, "dig");
        push_soundspec(L, c.sound_dug);
 -      lua_setfield(L, -2, "sound_dug");
 +      lua_setfield(L, -2, "dug");
        lua_setfield(L, -2, "sounds");
        lua_pushboolean(L, c.legacy_facedir_simple);
        lua_setfield(L, -2, "legacy_facedir_simple");
@@@ -1055,22 -1000,25 +1055,25 @@@ void push_nodebox(lua_State *L, const N
                        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 -1146,24 +1201,24 @@@ NodeBox read_nodebox(lua_State *L, int 
        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;
  }
@@@ -1490,29 -1442,6 +1497,29 @@@ struct TileAnimationParams read_animati
        return anim;
  }
  
 +void push_animation_definition(lua_State *L, struct TileAnimationParams anim)
 +{
 +      switch (anim.type) {
 +      case TAT_NONE:
 +              lua_pushnil(L);
 +              break;
 +      case TAT_VERTICAL_FRAMES:
 +              lua_newtable(L);
 +              setstringfield(L, -1, "type", "vertical_frames");
 +              setfloatfield(L, -1, "aspect_w", anim.vertical_frames.aspect_w);
 +              setfloatfield(L, -1, "aspect_h", anim.vertical_frames.aspect_h);
 +              setfloatfield(L, -1, "length", anim.vertical_frames.length);
 +              break;
 +      case TAT_SHEET_2D:
 +              lua_newtable(L);
 +              setstringfield(L, -1, "type", "sheet_2d");
 +              setintfield(L, -1, "frames_w", anim.sheet_2d.frames_w);
 +              setintfield(L, -1, "frames_h", anim.sheet_2d.frames_h);
 +              setintfield(L, -1, "frame_length", anim.sheet_2d.frame_length);
 +              break;
 +      }
 +}
 +
  /******************************************************************************/
  ToolCapabilities read_tool_capabilities(
                lua_State *L, int table)
@@@ -1946,8 -1875,14 +1953,8 @@@ void push_pointed_thing(lua_State *L, c
        } else if (pointed.type == POINTEDTHING_OBJECT) {
                lua_pushstring(L, "object");
                lua_setfield(L, -2, "type");
 -
 -              if (csm) {
 -                      lua_pushinteger(L, pointed.object_id);
 -                      lua_setfield(L, -2, "id");
 -              } else {
 -                      push_objectRef(L, pointed.object_id);
 -                      lua_setfield(L, -2, "ref");
 -              }
 +              push_objectRef(L, pointed.object_id);
 +              lua_setfield(L, -2, "ref");
        } else {
                lua_pushstring(L, "nothing");
                lua_setfield(L, -2, "type");
@@@ -2219,27 -2154,3 +2226,27 @@@ void push_collision_move_result(lua_Sta
        lua_setfield(L, -2, "collisions");
        /**/
  }
 +
 +/******************************************************************************/
 +void push_physics_override(lua_State *L, float speed, float jump, float gravity, bool sneak, bool sneak_glitch, bool new_move)
 +{
 +      lua_createtable(L, 0, 6);
 +
 +      lua_pushnumber(L, speed);
 +      lua_setfield(L, -2, "speed");
 +
 +      lua_pushnumber(L, jump);
 +      lua_setfield(L, -2, "jump");
 +
 +      lua_pushnumber(L, gravity);
 +      lua_setfield(L, -2, "gravity");
 +
 +      lua_pushboolean(L, sneak);
 +      lua_setfield(L, -2, "sneak");
 +
 +      lua_pushboolean(L, sneak_glitch);
 +      lua_setfield(L, -2, "sneak_glitch");
 +
 +      lua_pushboolean(L, new_move);
 +      lua_setfield(L, -2, "new_move");
 +}
index 76509038f3ca303f8e6287f46b14fde4ed08f4db,88e22f16f1781273a09d35e765c8fd8c034358e5..037bd8cf9416a5e3b5f93ee66ead606f3de16d41
@@@ -111,7 -111,6 +111,7 @@@ void ScriptApiSecurity::initializeSecur
                "bit"
        };
        static const char *io_whitelist[] = {
 +              "open",
                "close",
                "flush",
                "read",
                "gethook",
                "traceback",
                "getinfo",
-               "getmetatable",
-               "setmetatable",
                "upvalueid",
                "sethook",
                "debug",
        copy_safe(L, io_whitelist, sizeof(io_whitelist));
  
        // And replace unsafe ones
 -      SECURE_API(io, open);
 +      //SECURE_API(io, open);
        SECURE_API(io, input);
        SECURE_API(io, output);
        SECURE_API(io, lines);
@@@ -321,6 -318,7 +319,6 @@@ void ScriptApiSecurity::initializeSecur
                "getinfo", // used by builtin and unset before mods load
                "traceback"
        };
 -
  #if USE_LUAJIT
        static const char *jit_whitelist[] = {
                "arch",
        lua_State *L = getStack();
        int thread = getThread(L);
  
 +      // Backup globals to the registry
 +      lua_getglobal(L, "_G");
 +      lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
 +
        // create an empty environment
        createEmptyEnv(L);
  
        SECURE_API(g, require);
        lua_pop(L, 2);
  
 -
 -
        // Copy safe OS functions
        lua_getglobal(L, "os");
        lua_newtable(L);
        copy_safe(L, debug_whitelist, sizeof(debug_whitelist));
        lua_setfield(L, -3, "debug");
        lua_pop(L, 1);  // Pop old debug
 +      
  
  #if USE_LUAJIT
        // Copy safe jit functions, if they exist
index 9220259ff1d6bd59394a9daab87b4755d54115cd,27c1b887592c48cb02ebf11b5ddabc064c5d83cd..bae6701a095eb803a50ad704a7853a5c06ed2b97
@@@ -28,12 -28,7 +28,12 @@@ with this program; if not, write to th
  #include "server.h"
  #include "inventory.h"
  #include "log.h"
 -
 +#include "script/cpp_api/s_base.h"
 +#ifndef SERVER
 +#include "client/client.h"
 +#include "client/renderingengine.h"
 +#include "client/shader.h"
 +#endif
  
  // garbage collector
  int LuaItemStack::gc_object(lua_State *L)
@@@ -557,9 -552,9 +557,9 @@@ int ModApiItemMod::l_register_item_raw(
  
        // Get the writable item and node definition managers from the server
        IWritableItemDefManager *idef =
 -                      getServer(L)->getWritableItemDefManager();
 +                      getGameDef(L)->getWritableItemDefManager();
        NodeDefManager *ndef =
 -                      getServer(L)->getWritableNodeDefManager();
 +                      getGameDef(L)->getWritableNodeDefManager();
  
        // Check if name is defined
        std::string name;
                // 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);
  
                                        + itos(MAX_REGISTERED_CONTENT+1)
                                        + ") exceeded (" + name + ")");
                }
 +              
        }
 -
 +      
        return 0; /* number of results */
  }
  
@@@ -620,12 -617,12 +623,12 @@@ int ModApiItemMod::l_unregister_item_ra
        std::string name = luaL_checkstring(L, 1);
  
        IWritableItemDefManager *idef =
 -                      getServer(L)->getWritableItemDefManager();
 +                      getGameDef(L)->getWritableItemDefManager();
  
        // Unregister the node
        if (idef->get(name).type == ITEM_NODE) {
                NodeDefManager *ndef =
 -                      getServer(L)->getWritableNodeDefManager();
 +                      getGameDef(L)->getWritableNodeDefManager();
                ndef->removeNode(name);
        }
  
@@@ -643,7 -640,7 +646,7 @@@ int ModApiItemMod::l_register_alias_raw
  
        // Get the writable item definition manager from the server
        IWritableItemDefManager *idef =
 -                      getServer(L)->getWritableItemDefManager();
 +                      getGameDef(L)->getWritableItemDefManager();
  
        idef->registerAlias(name, convert_to);
  
index 6a9001052e2008c3817b324f1b7d93c20cd2e67e,06bfc7b98bd7021b940ac092ad3fb8fba83c95c2..2855f04b84c2f03e1460d9b8bba26fc7f05fe548
@@@ -378,10 -378,7 +378,7 @@@ void ActiveBlockList::update(std::vecto
        /*
                Update m_list
        */
-       m_list.clear();
-       for (v3s16 p : newlist) {
-               m_list.insert(p);
-       }
+       m_list = std::move(newlist);
  }
  
  /*
@@@ -626,6 -623,9 +623,9 @@@ PlayerSAO *ServerEnvironment::loadPlaye
        /* 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;
  }
  
@@@ -1185,7 -1185,7 +1185,7 @@@ void ServerEnvironment::clearObjects(Cl
                // Tell the object about removal
                obj->removingFromEnvironment();
                // Deregister in scripting api
 -              m_script->removeObjectReference(obj);
 +              m_script->removeObjectReference(dynamic_cast<ActiveObject *>(obj));
  
                // Delete active object
                if (obj->environmentDeletes())
@@@ -1332,13 -1332,16 +1332,16 @@@ void ServerEnvironment::step(float dtim
        /*
                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;
                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
                */
                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
@@@ -1780,7 -1788,7 +1788,7 @@@ u16 ServerEnvironment::addActiveObjectR
        }
  
        // Register reference in scripting api (must be done before post-init)
 -      m_script->addObjectReference(object);
 +      m_script->addObjectReference(dynamic_cast<ActiveObject *>(object));
        // Post-initialize object
        object->addedToEnvironment(dtime_s);
  
@@@ -1870,7 -1878,7 +1878,7 @@@ void ServerEnvironment::removeRemovedOb
                // Tell the object about removal
                obj->removingFromEnvironment();
                // Deregister in scripting api
 -              m_script->removeObjectReference(obj);
 +              m_script->removeObjectReference(dynamic_cast<ActiveObject *>(obj));
  
                // Delete
                if (obj->environmentDeletes())
@@@ -2135,7 -2143,7 +2143,7 @@@ void ServerEnvironment::deactivateFarOb
                // Tell the object about removal
                obj->removingFromEnvironment();
                // Deregister in scripting api
 -              m_script->removeObjectReference(obj);
 +              m_script->removeObjectReference(dynamic_cast<ActiveObject *>(obj));
  
                // Delete active object
                if (obj->environmentDeletes())