]> git.lizzy.rs Git - dragonfireclient.git/commitdiff
Merge branch 'master' of https://github.com/minetest/minetest
authorElias Fleckenstein <eliasfleckenstein@web.de>
Tue, 17 May 2022 20:12:00 +0000 (22:12 +0200)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Tue, 17 May 2022 20:12:00 +0000 (22:12 +0200)
71 files changed:
1  2 
.gitignore
CMakeLists.txt
README.md
builtin/async/game.lua
builtin/client/register.lua
builtin/common/chatcommands.lua
builtin/common/misc_helpers.lua
builtin/game/init.lua
builtin/init.lua
builtin/mainmenu/dlg_contentstore.lua
builtin/mainmenu/dlg_settings_advanced.lua
builtin/mainmenu/init.lua
builtin/mainmenu/pkgmgr.lua
builtin/mainmenu/tab_about.lua
builtin/mainmenu/tab_content.lua
builtin/settingtypes.txt
doc/client_lua_api.txt
doc/lua_api.txt
src/client/camera.cpp
src/client/camera.h
src/client/client.cpp
src/client/client.h
src/client/clientenvironment.cpp
src/client/clientmap.cpp
src/client/content_cao.cpp
src/client/content_cao.h
src/client/game.cpp
src/client/game.h
src/client/gameui.cpp
src/client/gameui.h
src/client/hud.cpp
src/client/inputhandler.h
src/client/localplayer.cpp
src/client/localplayer.h
src/client/mapblock_mesh.cpp
src/client/mapblock_mesh.h
src/client/mesh_generator_thread.h
src/client/minimap.cpp
src/client/render/core.cpp
src/client/renderingengine.cpp
src/collision.cpp
src/defaultsettings.cpp
src/environment.cpp
src/gamedef.h
src/gui/CMakeLists.txt
src/gui/cheatMenu.cpp
src/map.cpp
src/map.h
src/network/clientpackethandler.cpp
src/nodedef.cpp
src/player.cpp
src/player.h
src/script/common/c_content.cpp
src/script/common/c_content.h
src/script/cpp_api/s_base.cpp
src/script/cpp_api/s_base.h
src/script/cpp_api/s_client.cpp
src/script/cpp_api/s_security.cpp
src/script/lua_api/l_client.cpp
src/script/lua_api/l_env.cpp
src/script/lua_api/l_env.h
src/script/lua_api/l_item.cpp
src/script/lua_api/l_localplayer.cpp
src/script/lua_api/l_localplayer.h
src/script/lua_api/l_mainmenu.cpp
src/script/lua_api/l_server.cpp
src/script/lua_api/l_util.cpp
src/serverenvironment.cpp
src/unittest/test.cpp
util/buildbot/buildwin32.sh
util/buildbot/buildwin64.sh

diff --cc .gitignore
Simple merge
diff --cc CMakeLists.txt
index deb327c5bc17df45e6c2f4b7d1ce58a80a4e34b9,d8dd85af6e02c18ac9ef92965eea114c3bcd6828..018e233da2b19b23b5ab740a1bae4e46bfa8f0aa
@@@ -9,20 -9,21 +9,21 @@@ 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 11)
- set(GCC_MINIMUM_VERSION "4.8")
- set(CLANG_MINIMUM_VERSION "3.4")
+ set(CMAKE_CXX_STANDARD 14)
+ set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
+ set(GCC_MINIMUM_VERSION "5.1")
+ set(CLANG_MINIMUM_VERSION "3.5")
  
  # Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing
  set(VERSION_MAJOR 5)
- set(VERSION_MINOR 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)
diff --cc README.md
Simple merge
index 0000000000000000000000000000000000000000,8cb9720b66d113aaac82cd4fd96e5ee520665fb0..e3119d8cb0cdbe725579fb173dcb3843d39f0fef
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,45 +1,46 @@@
 -dofile(gamepath .. "voxelarea.lua")
+ core.log("info", "Initializing asynchronous environment (game)")
+ local function pack2(...)
+       return {n=select('#', ...), ...}
+ end
+ -- Entrypoint to run async jobs, called by C++
+ function core.job_processor(func, params)
+       local retval = pack2(func(unpack(params, 1, params.n)))
+       return retval
+ end
+ -- Import a bunch of individual files from builtin/game/
+ local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM
++local commonpath = core.get_builtin_path() .. "common" .. DIR_DELIM
+ dofile(gamepath .. "constants.lua")
+ dofile(gamepath .. "item_s.lua")
+ dofile(gamepath .. "misc_s.lua")
+ dofile(gamepath .. "features.lua")
++dofile(commonpath .. "voxelarea.lua")
+ -- Transfer of globals
+ do
+       local all = assert(core.transferred_globals)
+       core.transferred_globals = nil
+       -- reassemble other tables
+       all.registered_nodes = {}
+       all.registered_craftitems = {}
+       all.registered_tools = {}
+       for k, v in pairs(all.registered_items) do
+               if v.type == "node" then
+                       all.registered_nodes[k] = v
+               elseif v.type == "craftitem" then
+                       all.registered_craftitems[k] = v
+               elseif v.type == "tool" then
+                       all.registered_tools[k] = v
+               end
+       end
+       for k, v in pairs(all) do
+               core[k] = v
+       end
+ end
Simple merge
index 62f9eacd06c93525fbdeeb377a1357ec1219d54c,7c3da0601c44546f5b82d84379506920183af7d5..817f1f526186d2657ba9084731d12adf844ec8d5
@@@ -33,67 -69,30 +69,75 @@@ function core.override_chatcommand(name
        core.registered_chatcommands[name] = chatcommand
  end
  
 +if INIT == "client" then
 +      function core.register_list_command(command, desc, setting)
 +              local def = {}
 +              def.description = desc
 +              def.params = "del <item> | add <item> | list"
 +              function def.func(param)
 +                      local list = (minetest.settings:get(setting) or ""):split(",")
 +                      if param == "list" then
 +                              return true, table.concat(list, ", ")
 +                      else
 +                              local sparam = param:split(" ")
 +                              local cmd = sparam[1]
 +                              local item = sparam[2]
 +                              if cmd == "del" then
 +                                      if not item then
 +                                              return false, "Missing item."
 +                                      end
 +                                      local i = table.indexof(list, item)
 +                                      if i == -1 then
 +                                              return false, item .. " is not on the list."
 +                                      else
 +                                              table.remove(list, i)
 +                                              core.settings:set(setting, table.concat(list, ","))
 +                                              return true, "Removed " .. item .. " from the list."
 +                                      end
 +                              elseif cmd == "add" then
 +                                      if not item then
 +                                              return false, "Missing item."
 +                                      end
 +                                      local i = table.indexof(list, item)
 +                                      if i ~= -1 then
 +                                              return false, item .. " is already on the list."
 +                                      else
 +                                              table.insert(list, item)
 +                                              core.settings:set(setting, table.concat(list, ","))
 +                                              return true, "Added " .. item .. " to the list."
 +                                      end
 +                              end
 +                      end
 +                      return false, "Invalid usage. (See .help " .. command .. ")"
 +              end
 +              core.register_chatcommand(command, def)
 +      end
 +end
 +
+ local function format_help_line(cmd, def)
+       local cmd_marker = INIT == "client" and "." or "/"
+       local msg = core.colorize("#00ffff", cmd_marker .. cmd)
+       if def.params and def.params ~= "" then
+               msg = msg .. " " .. def.params
+       end
+       if def.description and def.description ~= "" then
+               msg = msg .. ": " .. def.description
+       end
+       return msg
+ end
  local function do_help_cmd(name, param)
-       local function format_help_line(cmd, def)
-               local cmd_marker = "/"
-               if INIT == "client" then
-                       cmd_marker = "."
-               end
-               local msg = core.colorize("#00ffff", cmd_marker .. cmd)
-               if def.params and def.params ~= "" then
-                       msg = msg .. " " .. def.params
-               end
-               if def.description and def.description ~= "" then
-                       msg = msg .. ": " .. def.description
-               end
-               return msg
+       local opts, args = getopts("help", param)
+       if not opts then
+               return false, args
+       end
+       if #args > 1 then
+               return false, S("Too many arguments, try using just /help <command>")
        end
-       if param == "" then
+       local use_gui = INIT ~= "client" and core.get_player_by_name(name)
+       use_gui = use_gui and not opts:find("t")
+       if #args == 0 and not use_gui then
                local cmds = {}
                for cmd, def in pairs(core.registered_chatcommands) do
                        if INIT == "client" or core.check_player_privs(name, def.privs) then
Simple merge
index 9a9966d7e57ab8f2f999c2f6f74c0b54e9f41fe4,68d6a10f8d164585fa65e7ed6942f7645f57c332..b9ab97b58251078a3f97523866db2204f8727baa
@@@ -16,9 -17,9 +17,10 @@@ if core.settings:get_bool("profiler.loa
  end
  
  dofile(commonpath .. "after.lua")
 +dofile(commonpath .. "voxelarea.lua")
  dofile(gamepath .. "item_entity.lua")
  dofile(gamepath .. "deprecated.lua")
+ dofile(gamepath .. "misc_s.lua")
  dofile(gamepath .. "misc.lua")
  dofile(gamepath .. "privileges.lua")
  dofile(gamepath .. "auth.lua")
Simple merge
Simple merge
index 38a658969955bcbcae9c50e37bda19db83ebcfe4,320db7e40cb28eae5a5fe8416b3903343cc80a7f..d6f485cf73d29c2176b1c16a51bf8b2e94e9b2d4
@@@ -404,36 -405,6 +405,36 @@@ local function parse_config_file(read_a
                                file:close()
                        end
                end
-               get_mods(core.get_clientmodpath(), clientmods)
 +
 +              -- Parse clientmods
 +              local clientmods_category_initialized = false
 +              local clientmods = {}
++              get_mods(core.get_clientmodpath(), "clientmods", clientmods)
 +              for _, clientmod in ipairs(clientmods) do
 +                      local path = clientmod.path .. DIR_DELIM .. FILENAME
 +                      local file = io.open(path, "r")
 +                      if file then
 +                              if not clientmods_category_initialized then
 +                                      fgettext_ne("Clientmods") -- not used, but needed for xgettext
 +                                      table.insert(settings, {
 +                                              name = "Clientmods",
 +                                              level = 0,
 +                                              type = "category",
 +                                      })
 +                                      clientmods_category_initialized = true
 +                              end
 +
 +                              table.insert(settings, {
 +                                      name = clientmod.name,
 +                                      level = 1,
 +                                      type = "category",
 +                              })
 +
 +                              parse_single_file(file, path, read_all, settings, 2, false)
 +
 +                              file:close()
 +                      end
 +              end
        end
  
        return settings
Simple merge
index 58a4ed8c1e8b68ecf663cdd1e1049391d62fb9f7,334fcf5f828fe70e325abdf0229a849be6bca9be..072c41f0cd17ad27a6744a0d06cd5c42343e51f7
@@@ -657,72 -663,7 +663,54 @@@ function pkgmgr.install_dir(type, path
        return targetpath, nil
  end
  
- --------------------------------------------------------------------------------
- function pkgmgr.install(type, modfilename, basename, dest)
-       local archive_info = pkgmgr.identify_filetype(modfilename)
-       local path = pkgmgr.extract(archive_info)
-       if path == nil then
-               return nil,
-                       fgettext("Install: file: \"$1\"", archive_info.name) .. "\n" ..
-                       fgettext("Install: Unsupported file type \"$1\" or broken archive",
-                               archive_info.type)
-       end
-       local targetpath, msg = pkgmgr.install_dir(type, path, basename, dest)
-       core.delete_dir(path)
-       return targetpath, msg
- end
  --------------------------------------------------------------------------------
-       if modpath ~= nil and
-               modpath ~= "" then
-               get_mods(modpath,clientmods)
 +function pkgmgr.prepareclientmodlist(data)
 +      local retval = {}
 +
 +      local clientmods = {}
 +
 +      --read clientmods
 +      local modpath = core.get_clientmodpath()
 +
++      if modpath ~= nil and modpath ~= "" then
++              get_mods(modpath, "clientmods", clientmods)
 +      end
 +
 +      for i=1,#clientmods,1 do
 +              clientmods[i].type = "mod"
 +              clientmods[i].loc = "global"
 +              clientmods[i].is_clientside = true
 +              retval[#retval + 1] = clientmods[i]
 +      end
 +
 +      --read mods configuration
 +      local filename = modpath ..
 +                              DIR_DELIM .. "mods.conf"
 +
 +      local conffile = Settings(filename)
 +
 +      for key,value in pairs(conffile:to_table()) do
 +              if key:sub(1, 9) == "load_mod_" then
 +                      key = key:sub(10)
 +                      local element = nil
 +                      for i=1,#retval,1 do
 +                              if retval[i].name == key and
 +                                      not retval[i].is_modpack then
 +                                      element = retval[i]
 +                                      break
 +                              end
 +                      end
 +                      if element ~= nil then
 +                              element.enabled = value ~= "false" and value ~= "nil" and value
 +                      else
 +                              core.log("info", "Clientmod: " .. key .. " " .. dump(value) .. " but not found")
 +                      end
 +              end
 +      end
 +
 +      return retval
 +end
 +
  function pkgmgr.preparemodlist(data)
        local retval = {}
  
@@@ -865,51 -820,8 +867,12 @@@ function pkgmgr.refresh_globals(
                        pkgmgr.comparemod, is_equal, nil, {})
        pkgmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
        pkgmgr.global_mods:set_sortmode("alphabetic")
 +      pkgmgr.clientmods = filterlist.create(pkgmgr.prepareclientmodlist,
 +                      pkgmgr.comparemod, is_equal, nil, {})
 +      pkgmgr.clientmods:add_sort_mechanism("alphabetic", sort_mod_list)
 +      pkgmgr.clientmods:set_sortmode("alphabetic")
  end
  
- --------------------------------------------------------------------------------
- function pkgmgr.identify_filetype(name)
-       if name:sub(-3):lower() == "zip" then
-               return {
-                               name = name,
-                               type = "zip"
-                               }
-       end
-       if name:sub(-6):lower() == "tar.gz" or
-               name:sub(-3):lower() == "tgz"then
-               return {
-                               name = name,
-                               type = "tgz"
-                               }
-       end
-       if name:sub(-6):lower() == "tar.bz2" then
-               return {
-                               name = name,
-                               type = "tbz"
-                               }
-       end
-       if name:sub(-2):lower() == "7z" then
-               return {
-                               name = name,
-                               type = "7z"
-                               }
-       end
-       return {
-               name = name,
-               type = "ukn"
-       }
- end
  --------------------------------------------------------------------------------
  function pkgmgr.find_by_gameid(gameid)
        for i=1,#pkgmgr.games,1 do
Simple merge
index 1bffeeb223234353a51fd65f301255254e1625a4,5e14d1902efcc506bed6ea9d3ad6638012f76102..a366d4ab4a11e3907c928ebab755903408e384cf
@@@ -214,9 -164,9 +224,12 @@@ local function handle_doubleclick(pkg, 
                        core.settings:set("texture_path", pkg.path)
                end
                packages = nil
+               mm_game_theme.init()
+               mm_game_theme.reset()
 +      elseif pkg.is_clientside then
 +              pkgmgr.enable_mod({data = {list = packages, selected_mod = pkg_name}})
 +              packages = nil
        end
  end
  
index e023aeab7416d442ee0acb996597104c6646428a,ff69d97412768cbc4d8c4e457edb989a4dd5203a..2e0bb560a7739e406de88063cfdc224bc57d54a1
@@@ -2266,114 -2275,3 +2287,114 @@@ contentdb_flag_blacklist (ContentDB Fla
  #    Maximum number of concurrent downloads. Downloads exceeding this limit will be queued.
  #    This should be lower than curl_parallel_limit.
  contentdb_max_concurrent_downloads (ContentDB Max Concurrent Downloads) int 3
- cheat_menu_font (MenuFont) enum FM_Mono FM_Standard,FM_Mono,FM_Fallback,FM_Simple,FM_SimpleMono,FM_MaxMode,FM_Unspecified
 +
 +[Cheat Menu]
 +
 +#   Font to use for cheat menu
++cheat_menu_font (MenuFont) enum FM_Mono FM_Standard,FM_Mono,FM_Fallback,FM_MaxMode,FM_Unspecified
 +
 +#   (RGB value)
 +cheat_menu_bg_color (Cell background color) v3f 255, 145, 88
 +
 +cheat_menu_bg_color_alpha (Cell background color alpha) int 192
 +
 +#   (RGB value)
 +cheat_menu_active_bg_color (Active cell background color) v3f 255, 87, 53
 +
 +cheat_menu_active_bg_color_alpha (Active cell background color alpha) int 192
 +
 +#   (RGB value)
 +cheat_menu_font_color (Font color) v3f 0, 0, 0
 +
 +cheat_menu_font_color_alpha (Font color alpha) int 255 
 +
 +#   (RGB value)
 +cheat_menu_selected_font_color (Selected font color) v3f 255, 252, 88
 +
 +cheat_menu_selected_font_color_alpha (Selected font color alpha) int 255
 +
 +cheat_menu_head_height (Head height) int 50
 +
 +cheat_menu_entry_height (Entry height) int 40
 +
 +cheat_menu_entry_width (Entry width) int 200
 +
 +[Cheats]
 +
 +fullbright (Fullbright) bool false
 +
 +xray (XRay) bool false
 +
 +xray_nodes (XRay Nodes) string default:stone,mcl_core:stone
 +
 +priv_bypass (PrivBypass) bool true
 +
 +fastdig (FastDig) bool false
 +
 +fastplace (FastPlace) bool false
 +
 +autodig (AutoDig) bool false
 +
 +autoplace (AutoPlace) bool false
 +
 +prevent_natural_damage (NoFallDamage) bool true
 +
 +freecam (Freecam) bool false
 +
 +no_hurt_cam (NoHurtCam) bool false
 +
 +hud_flags_bypass (HUDBypass) bool true
 +
 +antiknockback (AntiKnockback) bool false
 +
 +entity_speed (EntitySpeed) bool false
 +
 +jesus (Jesus) bool false
 +
 +instant_break (InstantBreak) bool false
 +
 +no_night (BrightNight) bool false
 +
 +coords (Coords) bool false
 +
 +point_liquids (PointLiquids) bool false
 +
 +spamclick (FastHit) bool false
 +
 +no_force_rotate (NoForceRotate) bool false
 +
 +no_slow (NoSlow) bool false
 +
 +cheat_hud (CheatHUD) bool true
 +
 +node_esp_nodes (NodeESP Nodes) string
 +
 +jetpack (JetPack) bool false
 +
 +autohit (AutoHit) bool false
 +
 +antislip (AntiSlip) bool false
 +
 +enable_entity_tracers (EntityTracers) bool false
 +
 +enable_entity_esp (EntityESP) bool false
 +
 +enable_player_tracers (PlayerTracers) bool false
 +
 +enable_player_esp (PlayerESP) bool false
 +
 +enable_node_esp (NodeESP) bool false
 +
 +enable_node_tracers (NodeTracers) bool false
 +
 +entity_esp_color (EntityESP Color) v3f 255, 255, 255
 +
 +player_esp_color (PlayerESP Color) v3f 0, 255, 0
 +
 +tool_range (Additional Tool Range) int 2
 +
 +reach (Reach) bool false
 +
 +airjump (AirJump) bool false
 +
 +spider (Spider) bool false
Simple merge
diff --cc doc/lua_api.txt
Simple merge
index 52bedcba93979291c00adf43eafaafb7a80cfccf,d1f19adb3ff08f71488cbd2fe4feccae438fa4dd..0c387262ebe039df92473875de58b4136a196e54
@@@ -35,8 -36,8 +36,9 @@@ with this program; if not, write to th
  #include "util/numeric.h"
  #include "constants.h"
  #include "fontengine.h"
 +#include "guiscalingfilter.h"
  #include "script/scripting_client.h"
+ #include "gettext.h"
  
  #define CAMERA_OFFSET_STEP 200
  #define WIELDMESH_OFFSET_X 55.0f
@@@ -342,15 -319,18 +320,18 @@@ void Camera::update(LocalPlayer* player
        // mods expect the player head to be at the parent's position
        // plus eye height.
        if (player->getParent())
 -              player_position = player->getParent()->getPosition();
 +              player_position = player->getParent()->getPosition() + v3f(0,  g_settings->getBool("float_above_parent") ? BS : 0, 0);
  
-       // Smooth the camera movement when the player instantly moves upward due to stepheight.
-       // To smooth the 'not touching_ground' stepheight, smoothing is necessary when jumping
-       // or swimming (for when moving from liquid to land).
-       // Disable smoothing if climbing or flying, to avoid upwards offset of player model
-       // when seen in 3rd person view.
-       bool flying = (g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly")) || g_settings->getBool("freecam");
-       if (player_position.Y > old_player_position.Y && !player->is_climbing && !flying) {
+       // Smooth the camera movement after the player instantly moves upward due to stepheight.
+       // The smoothing usually continues until the camera position reaches the player position.
+       float player_stepheight = player->getCAO() ? player->getCAO()->getStepHeight() : HUGE_VALF;
+       float upward_movement = player_position.Y - old_player_position.Y;
+       if (upward_movement < 0.01f || upward_movement > player_stepheight) {
+               m_stepheight_smooth_active = false;
+       } else if (player->touching_ground) {
+               m_stepheight_smooth_active = true;
+       }
+       if (m_stepheight_smooth_active) {
                f32 oldy = old_player_position.Y;
                f32 newy = player_position.Y;
                f32 t = std::exp(-23 * frametime);
Simple merge
index 3c4ea5f95f6a2be6d2407f581db91d6fa54458ab,cb556c1ce5cf56b5fd3250b66a8f319bed4cafd7..4e4bb8a97ea8afbe6ecf4ac7a18bba51887b4cc3
@@@ -927,11 -957,11 +960,11 @@@ 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->keyPressed;
+       u32 keyPressed   = myplayer->control.getKeysPressed();
        // scaled by 80, so that pi can fit into a u8
        u8 fov           = clientMap->getCameraFov() * 80;
        u8 wanted_range  = MYMIN(255,
@@@ -1297,21 -1323,27 +1326,27 @@@ void Client::sendPlayerPos(v3f pos
        if (m_activeobjects_received && player->isDead())
                return;
  
+       ClientMap &map = m_env.getClientMap();
+       u8 camera_fov   = map.getCameraFov();
+       u8 wanted_range = map.getControl().wanted_range;
+       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   == player->keyPressed    &&
+                       player->last_keyPressed   == keyPressed            &&
                        player->last_camera_fov   == camera_fov            &&
                        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   = player->keyPressed;
+       player->last_keyPressed   = keyPressed;
        player->last_camera_fov   = camera_fov;
        player->last_wanted_range = wanted_range;
  
@@@ -1639,59 -1658,18 +1661,37 @@@ void Client::addUpdateMeshTaskForNode(v
                                <<std::endl;
        }
  
-       v3s16 blockpos          = getNodeBlockPos(nodepos);
+       v3s16 blockpos = getNodeBlockPos(nodepos);
        v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE;
-       try{
-               addUpdateMeshTask(blockpos, ack_to_server, urgent);
-       }
-       catch(InvalidPositionException &e) {}
+       m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, false);
        // Leading edge
-       if(nodepos.X == blockpos_relative.X){
-               try{
-                       v3s16 p = blockpos + v3s16(-1,0,0);
-                       addUpdateMeshTask(p, false, urgent);
-               }
-               catch(InvalidPositionException &e){}
-       }
-       if(nodepos.Y == blockpos_relative.Y){
-               try{
-                       v3s16 p = blockpos + v3s16(0,-1,0);
-                       addUpdateMeshTask(p, false, urgent);
-               }
-               catch(InvalidPositionException &e){}
-       }
-       if(nodepos.Z == blockpos_relative.Z){
-               try{
-                       v3s16 p = blockpos + v3s16(0,0,-1);
-                       addUpdateMeshTask(p, false, urgent);
-               }
-               catch(InvalidPositionException &e){}
-       }
+       if (nodepos.X == blockpos_relative.X)
+               addUpdateMeshTask(blockpos + v3s16(-1, 0, 0), false, urgent);
+       if (nodepos.Y == blockpos_relative.Y)
+               addUpdateMeshTask(blockpos + v3s16(0, -1, 0), false, urgent);
+       if (nodepos.Z == blockpos_relative.Z)
+               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(),
index 1493d3ce3eecd959ce7a5696ca85bdad7fea7af7,cb1227768f9d8bc6f82e674bfad38066017bf648..d49f2f9ada7392fe9426da50f89c05ba6c838d87
@@@ -379,11 -377,12 +380,11 @@@ public
        virtual ISoundManager* getSoundManager();
        MtEventManager* getEventManager();
        virtual ParticleManager* getParticleManager();
 -      bool checkLocalPrivilege(const std::string &priv)
 -      { return checkPrivilege(priv); }
 +      bool checkLocalPrivilege(const std::string &priv){ return checkPrivilege(priv); }
        virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false);
        const std::string* getModFile(std::string filename);
+       ModMetadataDatabase *getModStorageDatabase() override { return m_mod_storage_database; }
  
-       std::string getModStoragePath() const override;
        bool registerModStorage(ModMetadata *meta) override;
        void unregisterModStorage(const std::string &name) override;
  
index f9c20b2dffad5f6226f870116a898ddfb565290e,448af36c6596e982a144d16958db7eed0ed41a65..01aaa0408c4767307d426e82bbedb2c8d06d3f21
@@@ -193,11 -193,11 +193,10 @@@ void ClientEnvironment::step(float dtim
                // Control local player
                lplayer->applyControl(dtime_part, this);
  
--              // Apply physics
-               if (!free_move && !is_climbing && !g_settings->getBool("freecam")) {
 -              if (!free_move) {
++              if (!free_move && !g_settings->getBool("freecam")) {
                        // Gravity
                        v3f speed = lplayer->getSpeed();
-                       if (!lplayer->in_liquid)
+                       if (!is_climbing && !lplayer->in_liquid)
                                speed.Y -= lplayer->movement_gravity *
                                        lplayer->physics_override_gravity * dtime_part * 2.0f;
  
Simple merge
index 5d8a597a2c41618c4a8743edd58b5a444fc2e075,d89bb53b31881a7123bd0328268f275dd50557b2..ec1fd1c2affadd2c718ab8207a1728d9b3889bd4
@@@ -827,14 -833,27 +837,35 @@@ void GenericCAO::addToScene(ITextureSou
        setNodeLight(m_last_light);
        updateMeshCulling();
  
+       if (m_animated_meshnode) {
+               u32 mat_count = m_animated_meshnode->getMaterialCount();
+               if (mat_count == 0 || m_prop.textures.empty()) {
+                       // nothing
+               } else if (mat_count > m_prop.textures.size()) {
+                       std::ostringstream oss;
+                       oss << "GenericCAO::addToScene(): Model "
+                               << m_prop.mesh << " loaded with " << mat_count
+                               << " mesh buffers but only " << m_prop.textures.size()
+                               << " texture(s) specifed, this is deprecated.";
+                       logOnce(oss, warningstream);
+                       video::ITexture *last = m_animated_meshnode->getMaterial(0).TextureLayer[0].Texture;
+                       for (u32 i = 1; i < mat_count; i++) {
+                               auto &layer = m_animated_meshnode->getMaterial(i).TextureLayer[0];
+                               if (!layer.Texture)
+                                       layer.Texture = last;
+                               last = layer.Texture;
+                       }
+               }
+       }
++
 +      if (m_client->modsLoaded() && m_client->getScript()->on_object_add(m_id)) {
 +              removeFromScene(false);
 +              return;
 +      }
 +
 +      if (m_client->modsLoaded())
 +              m_client->getScript()->on_object_properties_change(m_id);
  }
  
  void GenericCAO::updateLight(u32 day_night_ratio)
                }
        }
        if (!pos_ok)
-               light_at_pos = blend_light(day_night_ratio, LIGHT_SUN, 0);
+               light_at_pos = LIGHT_SUN;
+       video::SColor light = encode_light(light_at_pos, m_glow);
+       if (!m_enable_shaders)
+               final_color_blend(&light, light_at_pos, day_night_ratio);
  
-       u8 light = decode_light(light_at_pos + m_glow);
 +      if (g_settings->getBool("fullbright"))
-               light = 255;
++              light = video::SColor(0xFFFFFFFF);
++
        if (light != m_last_light) {
                m_last_light = light;
                setNodeLight(light);
@@@ -1014,12 -1034,14 +1052,14 @@@ void GenericCAO::step(float dtime, Clie
                        m_velocity = v3f(0,0,0);
                        m_acceleration = v3f(0,0,0);
                        const PlayerControl &controls = player->getPlayerControl();
+                       f32 new_speed = player->local_animation_speed;
  
                        bool walking = false;
-                       if (controls.movement_speed > 0.001f && ! g_settings->getBool("freecam"))
 -                      if (controls.movement_speed > 0.001f) {
++                      if (controls.movement_speed > 0.001f && ! g_settings->getBool("freecam")) {
+                               new_speed *= controls.movement_speed;
                                walking = true;
+                       }
  
-                       f32 new_speed = player->local_animation_speed;
                        v2s32 new_anim = v2s32(0,0);
                        bool allow_update = false;
  
                                        (controls.aux1 ||
                                        (!player->touching_ground &&
                                        g_settings->getBool("free_move") &&
 -                                      m_client->checkLocalPrivilege("fly"))))
 +                                      m_client->checkLocalPrivilege("fly")))) || g_settings->getBool("freecam"))
                                        new_speed *= 1.5;
                        // slowdown speed if sneaking
 -                      if (controls.sneak && walking)
 +                      if (controls.sneak && walking && ! g_settings->getBool("no_slow"))
                                new_speed /= 2;
-                       new_speed *= controls.movement_speed;
  
                        if (walking && (controls.dig || controls.place)) {
                                new_anim = player->local_animations[3];
@@@ -1951,19 -1972,10 +2007,10 @@@ void GenericCAO::updateMeshCulling(
        if (!m_is_local_player)
                return;
  
 -      const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST;
 +      const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST && ! g_settings->getBool("freecam");
  
-       if (m_meshnode && m_prop.visual == "upright_sprite") {
-               u32 buffers = m_meshnode->getMesh()->getMeshBufferCount();
-               for (u32 i = 0; i < buffers; i++) {
-                       video::SMaterial &mat = m_meshnode->getMesh()->getMeshBuffer(i)->getMaterial();
-                       // upright sprite has no backface culling
-                       mat.setFlag(video::EMF_FRONT_FACE_CULLING, hidden);
-               }
-               return;
-       }
        scene::ISceneNode *node = getSceneNode();
        if (!node)
                return;
  
index 7e9bb6671e1262254724db27a53fbceb389d2f27,783aa41999b2c86d608ed42c32000a277eff5d7f..8e5d04bfac2e572857ee0710e0d08a9d43aef44b
@@@ -172,22 -172,7 +172,22 @@@ public
  
        inline const v3f &getRotation() const { return m_rotation; }
  
-       inline const u16 getHp() const
 +      inline const v3f getAcceleration() const
 +      {
 +              return m_acceleration;
 +      }
 +
 +      inline const v3f getVelocity() const
 +      {
 +              return m_velocity;
 +      }
 +
-       const bool isImmortal();
++      inline u16 getHp() const
 +      {
 +              return m_hp;
 +      }
 +
+       bool isImmortal() const;
  
        inline const ObjectProperties &getProperties() const { return m_prop; }
  
index d2a75104085d59555d1765dabecc77bca05be897,f93bd34a3b6ff56fb88fddded059bb525adffd3f..e439d0e32da366d7a056fa93b977c8fd34591a68
@@@ -243,8 -1063,10 +243,8 @@@ bool Game::startup(bool *kill
  void Game::run()
  {
        ProfilerGraph graph;
-       RunStats stats              = { 0 };
-       FpsControl draw_times       = { 0 };
+       RunStats stats = {};
 -      CameraOrientation cam_view_target = {};
 -      CameraOrientation cam_view = {};
+       FpsControl draw_times;
        f32 dtime; // in seconds
  
        /* Clear the profiler */
@@@ -575,10 -1392,10 +573,10 @@@ bool Game::createClient(const GameStart
                str += L" [";
                str += text;
                str += L"]";
-               delete text;
+               delete[] text;
        }
        str += L" [";
 -      str += driver->getName();
 +      str += L"Minetest Hackclient";
        str += L"]";
  
        device->setWindowCaption(str.c_str());
@@@ -1290,29 -2074,24 +1291,36 @@@ void Game::openInventory(
        InventoryLocation inventoryloc;
        inventoryloc.setCurrentPlayer();
  
-       if (!client->modsLoaded()
-                       || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
-               TextDest *txt_dst = new TextDestPlayerInventory(client);
-               auto *&formspec = m_game_ui->updateFormspec("");
-               GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
-                       &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
+       if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
+               delete fs_src;
+               return;
+       }
  
-               formspec->setFormSpec(fs_src->getForm(), inventoryloc);
+       if (fs_src->getForm().empty()) {
+               delete fs_src;
+               return;
        }
+       TextDest *txt_dst = new TextDestPlayerInventory(client);
+       auto *&formspec = m_game_ui->updateFormspec("");
+       GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
+               &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
+       formspec->setFormSpec(fs_src->getForm(), inventoryloc);
  }
  
 +void Game::openEnderchest()
 +{
 +      LocalPlayer *player = client->getEnv().getLocalPlayer();
 +      if (!player || !player->getCAO())
 +              return;
 +
 +      infostream << "Game: Launching special inventory" << std::endl;
 +
 +      if (client->modsLoaded())
 +              client->getScript()->open_enderchest();
 +}
 +
  
  void Game::openConsole(float scale, const wchar_t *line)
  {
@@@ -3155,12 -3860,28 +3145,28 @@@ void Game::updateFrame(ProfilerGraph *g
        }
  
        /*
-               Get chat messages from client
+               Damage camera tilt
        */
+       if (player->hurt_tilt_timer > 0.0f) {
+               player->hurt_tilt_timer -= dtime * 6.0f;
  
-       v2u32 screensize = driver->getScreenSize();
 -              if (player->hurt_tilt_timer < 0.0f)
++              if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
+                       player->hurt_tilt_strength = 0.0f;
+       }
+       /*
+               Update minimap pos and rotation
+       */
+       if (mapper && m_game_ui->m_flags.show_hud) {
+               mapper->setPos(floatToInt(player->getPosition(), BS));
+               mapper->setAngle(player->getYaw());
+       }
+       /*
+               Get chat messages from client
+       */
  
-       updateChat(dtime, screensize);
+       updateChat(dtime);
  
        /*
                Inventory
                runData.damage_flash -= 384.0f * dtime;
        }
  
-       /*
-               Damage camera tilt
-       */
-       if (player->hurt_tilt_timer > 0.0f) {
-               player->hurt_tilt_timer -= dtime * 6.0f;
-               if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
-                       player->hurt_tilt_strength = 0.0f;
-       }
-       /*
-               Update minimap pos and rotation
-       */
-       if (mapper && m_game_ui->m_flags.show_hud) {
-               mapper->setPos(floatToInt(player->getPosition(), BS));
-               mapper->setAngle(player->getYaw());
-       }
        /*
 -              ==================== End scene ====================
 +              End scene
        */
+ #if IRRLICHT_VERSION_MT_REVISION < 5
        if (++m_reset_HW_buffer_counter > 500) {
                /*
                  Periodically remove all mesh HW buffers.
index cb40d48904639613ef8ded00282b39137e02ca3a,d87e747c58a009f2ac550d4ce4a0b79d01a0821b..0e5d0550d296b156acd1652b7bfcf205197b5a95
@@@ -96,824 -43,6 +96,836 @@@ struct CameraOrientation 
        f32 camera_pitch;  // "up/down"
  };
  
- #ifdef __ANDROID__
 +/*
 +      Text input system
 +*/
 +
 +struct TextDestNodeMetadata : public TextDest
 +{
 +      TextDestNodeMetadata(v3s16 p, Client *client)
 +      {
 +              m_p = p;
 +              m_client = client;
 +      }
 +      // This is deprecated I guess? -celeron55
 +      void gotText(const std::wstring &text)
 +      {
 +              std::string ntext = wide_to_utf8(text);
 +              infostream << "Submitting 'text' field of node at (" << m_p.X << ","
 +                         << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
 +              StringMap fields;
 +              fields["text"] = ntext;
 +              m_client->sendNodemetaFields(m_p, "", fields);
 +      }
 +      void gotText(const StringMap &fields)
 +      {
 +              m_client->sendNodemetaFields(m_p, "", fields);
 +      }
 +
 +      v3s16 m_p;
 +      Client *m_client;
 +};
 +
 +struct TextDestPlayerInventory : public TextDest
 +{
 +      TextDestPlayerInventory(Client *client)
 +      {
 +              m_client = client;
 +              m_formname = "";
 +      }
 +      TextDestPlayerInventory(Client *client, const std::string &formname)
 +      {
 +              m_client = client;
 +              m_formname = formname;
 +      }
 +      void gotText(const StringMap &fields)
 +      {
 +              m_client->sendInventoryFields(m_formname, fields);
 +      }
 +
 +      Client *m_client;
 +};
 +
 +struct LocalFormspecHandler : public TextDest
 +{
 +      LocalFormspecHandler(const std::string &formname)
 +      {
 +              m_formname = formname;
 +      }
 +
 +      LocalFormspecHandler(const std::string &formname, Client *client):
 +              m_client(client)
 +      {
 +              m_formname = formname;
 +      }
 +
 +      void gotText(const StringMap &fields)
 +      {
 +              if (m_formname == "MT_PAUSE_MENU") {
 +                      if (fields.find("btn_sound") != fields.end()) {
 +                              g_gamecallback->changeVolume();
 +                              return;
 +                      }
 +
 +                      if (fields.find("btn_key_config") != fields.end()) {
 +                              g_gamecallback->keyConfig();
 +                              return;
 +                      }
 +
 +                      if (fields.find("btn_exit_menu") != fields.end()) {
 +                              g_gamecallback->disconnect();
 +                              return;
 +                      }
 +
 +                      if (fields.find("btn_exit_os") != fields.end()) {
 +                              g_gamecallback->exitToOS();
 +#ifndef __ANDROID__
 +                              RenderingEngine::get_raw_device()->closeDevice();
 +#endif
 +                              return;
 +                      }
 +
 +                      if (fields.find("btn_change_password") != fields.end()) {
 +                              g_gamecallback->changePassword();
 +                              return;
 +                      }
 +
 +                      return;
 +              }
 +
 +              if (m_formname == "MT_DEATH_SCREEN") {
 +                      assert(m_client != 0);
 +                      m_client->sendRespawn();
 +                      return;
 +              }
 +
 +              if (m_client->modsLoaded())
 +                      m_client->getScript()->on_formspec_input(m_formname, fields);
 +      }
 +
 +      Client *m_client = nullptr;
 +};
 +
 +/* Form update callback */
 +
 +class NodeMetadataFormSource: public IFormSource
 +{
 +public:
 +      NodeMetadataFormSource(ClientMap *map, v3s16 p):
 +              m_map(map),
 +              m_p(p)
 +      {
 +      }
 +      const std::string &getForm() const
 +      {
 +              static const std::string empty_string = "";
 +              NodeMetadata *meta = m_map->getNodeMetadata(m_p);
 +
 +              if (!meta)
 +                      return empty_string;
 +
 +              return meta->getString("formspec");
 +      }
 +
 +      virtual std::string resolveText(const std::string &str)
 +      {
 +              NodeMetadata *meta = m_map->getNodeMetadata(m_p);
 +
 +              if (!meta)
 +                      return str;
 +
 +              return meta->resolveString(str);
 +      }
 +
 +      ClientMap *m_map;
 +      v3s16 m_p;
 +};
 +
 +class PlayerInventoryFormSource: public IFormSource
 +{
 +public:
 +      PlayerInventoryFormSource(Client *client):
 +              m_client(client)
 +      {
 +      }
 +
 +      const std::string &getForm() const
 +      {
 +              LocalPlayer *player = m_client->getEnv().getLocalPlayer();
 +              return player->inventory_formspec;
 +      }
 +
 +      Client *m_client;
 +};
 +
 +class NodeDugEvent: public MtEvent
 +{
 +public:
 +      v3s16 p;
 +      MapNode n;
 +
 +      NodeDugEvent(v3s16 p, MapNode n):
 +              p(p),
 +              n(n)
 +      {}
 +      MtEvent::Type getType() const
 +      {
 +              return MtEvent::NODE_DUG;
 +      }
 +};
 +
 +class SoundMaker
 +{
 +      ISoundManager *m_sound;
 +      const NodeDefManager *m_ndef;
 +public:
 +      bool makes_footstep_sound;
 +      float m_player_step_timer;
 +      float m_player_jump_timer;
 +
 +      SimpleSoundSpec m_player_step_sound;
 +      SimpleSoundSpec m_player_leftpunch_sound;
 +      SimpleSoundSpec m_player_rightpunch_sound;
 +
 +      SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
 +              m_sound(sound),
 +              m_ndef(ndef),
 +              makes_footstep_sound(true),
 +              m_player_step_timer(0.0f),
 +              m_player_jump_timer(0.0f)
 +      {
 +      }
 +
 +      void playPlayerStep()
 +      {
 +              if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
 +                      m_player_step_timer = 0.03;
 +                      if (makes_footstep_sound)
 +                              m_sound->playSound(m_player_step_sound, false);
 +              }
 +      }
 +
 +      void playPlayerJump()
 +      {
 +              if (m_player_jump_timer <= 0.0f) {
 +                      m_player_jump_timer = 0.2f;
 +                      m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false);
 +              }
 +      }
 +
 +      static void viewBobbingStep(MtEvent *e, void *data)
 +      {
 +              SoundMaker *sm = (SoundMaker *)data;
 +              sm->playPlayerStep();
 +      }
 +
 +      static void playerRegainGround(MtEvent *e, void *data)
 +      {
 +              SoundMaker *sm = (SoundMaker *)data;
 +              sm->playPlayerStep();
 +      }
 +
 +      static void playerJump(MtEvent *e, void *data)
 +      {
 +              SoundMaker *sm = (SoundMaker *)data;
 +              sm->playPlayerJump();
 +      }
 +
 +      static void cameraPunchLeft(MtEvent *e, void *data)
 +      {
 +              SoundMaker *sm = (SoundMaker *)data;
 +              sm->m_sound->playSound(sm->m_player_leftpunch_sound, false);
 +      }
 +
 +      static void cameraPunchRight(MtEvent *e, void *data)
 +      {
 +              SoundMaker *sm = (SoundMaker *)data;
 +              sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
 +      }
 +
 +      static void nodeDug(MtEvent *e, void *data)
 +      {
 +              SoundMaker *sm = (SoundMaker *)data;
 +              NodeDugEvent *nde = (NodeDugEvent *)e;
 +              sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false);
 +      }
 +
 +      static void playerDamage(MtEvent *e, void *data)
 +      {
 +              SoundMaker *sm = (SoundMaker *)data;
 +              sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
 +      }
 +
 +      static void playerFallingDamage(MtEvent *e, void *data)
 +      {
 +              SoundMaker *sm = (SoundMaker *)data;
 +              sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
 +      }
 +
 +      void registerReceiver(MtEventManager *mgr)
 +      {
 +              mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
 +              mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
 +              mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
 +              mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
 +              mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
 +              mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
 +              mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
 +              mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
 +      }
 +
 +      void step(float dtime)
 +      {
 +              m_player_step_timer -= dtime;
 +              m_player_jump_timer -= dtime;
 +      }
 +};
 +
 +// Locally stored sounds don't need to be preloaded because of this
 +class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
 +{
 +      std::set<std::string> m_fetched;
 +private:
 +      void paths_insert(std::set<std::string> &dst_paths,
 +              const std::string &base,
 +              const std::string &name)
 +      {
 +              dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
 +              dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
 +              dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
 +              dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
 +              dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
 +              dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
 +              dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
 +              dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
 +              dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
 +              dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
 +              dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
 +      }
 +public:
 +      void fetchSounds(const std::string &name,
 +              std::set<std::string> &dst_paths,
 +              std::set<std::string> &dst_datas)
 +      {
 +              if (m_fetched.count(name))
 +                      return;
 +
 +              m_fetched.insert(name);
 +
 +              paths_insert(dst_paths, porting::path_share, name);
 +              paths_insert(dst_paths, porting::path_user,  name);
 +      }
 +};
 +
 +
 +typedef s32 SamplerLayer_t;
 +
 +
 +class GameGlobalShaderConstantSetter : public IShaderConstantSetter
 +{
 +      Sky *m_sky;
 +      bool *m_force_fog_off;
 +      f32 *m_fog_range;
 +      bool m_fog_enabled;
 +      CachedPixelShaderSetting<float, 4> m_sky_bg_color;
 +      CachedPixelShaderSetting<float> m_fog_distance;
 +      CachedVertexShaderSetting<float> m_animation_timer_vertex;
 +      CachedPixelShaderSetting<float> m_animation_timer_pixel;
 +      CachedPixelShaderSetting<float, 3> m_day_light;
 +      CachedPixelShaderSetting<float, 4> m_star_color;
 +      CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
 +      CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
 +      CachedPixelShaderSetting<float, 3> m_minimap_yaw;
 +      CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
 +      CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
 +      CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
 +      CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
 +      Client *m_client;
 +
 +public:
 +      void onSettingsChange(const std::string &name)
 +      {
 +              if (name == "enable_fog")
 +                      m_fog_enabled = g_settings->getBool("enable_fog");
 +      }
 +
 +      static void settingsCallback(const std::string &name, void *userdata)
 +      {
 +              reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
 +      }
 +
 +      void setSky(Sky *sky) { m_sky = sky; }
 +
 +      GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
 +                      f32 *fog_range, Client *client) :
 +              m_sky(sky),
 +              m_force_fog_off(force_fog_off),
 +              m_fog_range(fog_range),
 +              m_sky_bg_color("skyBgColor"),
 +              m_fog_distance("fogDistance"),
 +              m_animation_timer_vertex("animationTimer"),
 +              m_animation_timer_pixel("animationTimer"),
 +              m_day_light("dayLight"),
 +              m_star_color("starColor"),
 +              m_eye_position_pixel("eyePosition"),
 +              m_eye_position_vertex("eyePosition"),
 +              m_minimap_yaw("yawVec"),
 +              m_camera_offset_pixel("cameraOffset"),
 +              m_camera_offset_vertex("cameraOffset"),
 +              m_base_texture("baseTexture"),
 +              m_normal_texture("normalTexture"),
 +              m_client(client)
 +      {
 +              g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
 +              m_fog_enabled = g_settings->getBool("enable_fog");
 +      }
 +
 +      ~GameGlobalShaderConstantSetter()
 +      {
 +              g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
 +      }
 +
 +      void onSetConstants(video::IMaterialRendererServices *services) override
 +      {
 +              // Background color
 +              video::SColor bgcolor = m_sky->getBgColor();
 +              video::SColorf bgcolorf(bgcolor);
 +              float bgcolorfa[4] = {
 +                      bgcolorf.r,
 +                      bgcolorf.g,
 +                      bgcolorf.b,
 +                      bgcolorf.a,
 +              };
 +              m_sky_bg_color.set(bgcolorfa, services);
 +
 +              // Fog distance
 +              float fog_distance = 10000 * BS;
 +
 +              if (m_fog_enabled && !*m_force_fog_off)
 +                      fog_distance = *m_fog_range;
 +
 +              m_fog_distance.set(&fog_distance, services);
 +
 +              u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
 +              video::SColorf sunlight;
 +              get_sunlight_color(&sunlight, daynight_ratio);
 +              float dnc[3] = {
 +                      sunlight.r,
 +                      sunlight.g,
 +                      sunlight.b };
 +              m_day_light.set(dnc, services);
 +
 +              video::SColorf star_color = m_sky->getCurrentStarColor();
 +              float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
 +              m_star_color.set(clr, services);
 +
 +              u32 animation_timer = porting::getTimeMs() % 1000000;
 +              float animation_timer_f = (float)animation_timer / 100000.f;
 +              m_animation_timer_vertex.set(&animation_timer_f, services);
 +              m_animation_timer_pixel.set(&animation_timer_f, services);
 +
 +              float eye_position_array[3];
 +              v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
 +              epos.getAs3Values(eye_position_array);
 +              m_eye_position_pixel.set(eye_position_array, services);
 +              m_eye_position_vertex.set(eye_position_array, services);
 +
 +              if (m_client->getMinimap()) {
 +                      float minimap_yaw_array[3];
 +                      v3f minimap_yaw = m_client->getMinimap()->getYawVec();
 +                      minimap_yaw.getAs3Values(minimap_yaw_array);
 +                      m_minimap_yaw.set(minimap_yaw_array, services);
 +              }
 +
 +              float camera_offset_array[3];
 +              v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
 +              offset.getAs3Values(camera_offset_array);
 +              m_camera_offset_pixel.set(camera_offset_array, services);
 +              m_camera_offset_vertex.set(camera_offset_array, services);
 +
 +              SamplerLayer_t base_tex = 0, normal_tex = 1;
 +              m_base_texture.set(&base_tex, services);
 +              m_normal_texture.set(&normal_tex, services);
 +      }
 +};
 +
 +
 +class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
 +{
 +      Sky *m_sky;
 +      bool *m_force_fog_off;
 +      f32 *m_fog_range;
 +      Client *m_client;
 +      std::vector<GameGlobalShaderConstantSetter *> created_nosky;
 +public:
 +      GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
 +                      f32 *fog_range, Client *client) :
 +              m_sky(NULL),
 +              m_force_fog_off(force_fog_off),
 +              m_fog_range(fog_range),
 +              m_client(client)
 +      {}
 +
 +      void setSky(Sky *sky) {
 +              m_sky = sky;
 +              for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
 +                      ggscs->setSky(m_sky);
 +              }
 +              created_nosky.clear();
 +      }
 +
 +      virtual IShaderConstantSetter* create()
 +      {
 +              auto *scs = new GameGlobalShaderConstantSetter(
 +                              m_sky, m_force_fog_off, m_fog_range, m_client);
 +              if (!m_sky)
 +                      created_nosky.push_back(scs);
 +              return scs;
 +      }
 +};
 +
-       u32 last_time, busy_time, sleep_time;
++#ifdef HAVE_TOUCHSCREENGUI
 +#define SIZE_TAG "size[11,5.5]"
 +#else
 +#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
 +#endif
 +
 +/****************************************************************************
 + ****************************************************************************/
 +
 +const float object_hit_delay = 0.2;
 +
 +struct FpsControl {
-       void updateCamera(u32 busy_time, f32 dtime);
++      FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
++
++      void reset();
++
++      void limit(IrrlichtDevice *device, f32 *dtime);
++
++      u32 getBusyMs() const { return busy_time / 1000; }
++
++      // all values in microseconds (us)
++      u64 last_time, busy_time, sleep_time;
 +};
 +
 +
 +/* The reason the following structs are not anonymous structs within the
 + * class is that they are not used by the majority of member functions and
 + * many functions that do require objects of thse types do not modify them
 + * (so they can be passed as a const qualified parameter)
 + */
 +
 +struct GameRunData {
 +      u16 dig_index;
 +      u16 new_playeritem;
 +      PointedThing pointed_old;
 +      bool digging;
 +      bool punching;
 +      bool btn_down_for_dig;
 +      bool dig_instantly;
 +      bool digging_blocked;
 +      bool reset_jump_timer;
 +      float nodig_delay_timer;
 +      float dig_time;
 +      float dig_time_complete;
 +      float repeat_place_timer;
 +      float object_hit_delay_timer;
 +      float time_from_last_punch;
 +      ClientActiveObject *selected_object;
 +
 +      float jump_timer;
 +      float damage_flash;
 +      float update_draw_list_timer;
 +
 +      f32 fog_range;
 +
 +      v3f update_draw_list_last_cam_dir;
 +
 +      float time_of_day_smooth;
 +};
 +
 +class Game;
 +
 +struct ClientEventHandler
 +{
 +      void (Game::*handler)(ClientEvent *, CameraOrientation *);
 +};
 +
 +using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
 +
 +class Game {
 +public:
 +      Game();
 +      ~Game();
 +
 +      bool startup(bool *kill,
 +                      InputHandler *input,
 +                      RenderingEngine *rendering_engine,
 +                      const GameStartData &game_params,
 +                      std::string &error_message,
 +                      bool *reconnect,
 +                      ChatBackend *chat_backend);
 +
 +
 +      void run();
 +      void shutdown();
 +
 +      // Basic initialisation
 +      bool init(const std::string &map_dir, const std::string &address,
 +                      u16 port, const SubgameSpec &gamespec);
 +      bool initSound();
 +      bool createSingleplayerServer(const std::string &map_dir,
 +                      const SubgameSpec &gamespec, u16 port);
 +
 +      // Client creation
 +      bool createClient(const GameStartData &start_data);
 +      bool initGui();
 +
 +      // Client connection
 +      bool connectToServer(const GameStartData &start_data,
 +                      bool *connect_ok, bool *aborted);
 +      bool getServerContent(bool *aborted);
 +
 +      // Main loop
 +
 +      void updateInteractTimers(f32 dtime);
 +      bool checkConnection();
 +      bool handleCallbacks();
 +      void processQueues();
 +      void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
 +      void updateDebugState();
 +      void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
 +      void updateProfilerGraphs(ProfilerGraph *graph);
 +
 +      // Input related
 +      void processUserInput(f32 dtime);
 +      void processKeyInput();
 +      void processItemSelection(u16 *new_playeritem);
 +
 +      void dropSelectedItem(bool single_item = false);
 +      void openInventory();
 +      void openEnderchest();
 +      void openConsole(float scale, const wchar_t *line=NULL);
 +      void toggleFreeMove();
 +      void toggleFreeMoveAlt();
 +      void togglePitchMove();
 +      void toggleFast();
 +      void toggleNoClip();
 +      void toggleKillaura();
 +      void toggleFreecam();
 +      void toggleScaffold();
 +      void toggleNextItem();
 +      void toggleCinematic();
 +      void toggleBlockBounds();
 +      void toggleAutoforward();
 +
 +      void toggleMinimap(bool shift_pressed);
 +      void toggleFog();
 +      void toggleDebug();
 +      void toggleUpdateCamera();
 +      void updatePlayerCAOVisibility();
 +
 +      void increaseViewRange();
 +      void decreaseViewRange();
 +      void toggleFullViewRange();
 +      void checkZoomEnabled();
 +
 +      void updateCameraDirection(CameraOrientation *cam, float dtime);
 +      void updateCameraOrientation(CameraOrientation *cam, float dtime);
 +      void updatePlayerControl(const CameraOrientation &cam);
 +      void step(f32 *dtime);
 +      void processClientEvents(CameraOrientation *cam);
-       void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
++      void updateCamera(f32 dtime);
 +      void updateSound(f32 dtime);
-       void limitFps(FpsControl *fps_timings, f32 *dtime);
++      void processPlayerInteraction(f32 dtime, bool show_hud);
 +      /*!
 +       * Returns the object or node the player is pointing at.
 +       * Also updates the selected thing in the Hud.
 +       *
 +       * @param[in]  shootline         the shootline, starting from
 +       * the camera position. This also gives the maximal distance
 +       * of the search.
 +       * @param[in]  liquids_pointable if false, liquids are ignored
 +       * @param[in]  look_for_object   if false, objects are ignored
 +       * @param[in]  camera_offset     offset of the camera
 +       * @param[out] selected_object   the selected object or
 +       * NULL if not found
 +       */
 +      PointedThing updatePointedThing(
 +                      const core::line3d<f32> &shootline, bool liquids_pointable,
 +                      bool look_for_object, const v3s16 &camera_offset);
 +      void handlePointingAtNothing(const ItemStack &playerItem);
 +      void handlePointingAtNode(const PointedThing &pointed,
 +                      const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
 +      void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
 +                      const v3f &player_position, bool show_debug);
 +      void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
 +                      const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
 +      void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
 +                      const CameraOrientation &cam);
 +      void updateShadows();
 +
 +      // Misc
-       void updateChat(f32 dtime, const v2u32 &screensize);
 +      void showOverlayMessage(const char *msg, float dtime, int percent,
 +                      bool draw_clouds = true);
 +
 +      static void freecamChangedCallback(const std::string &setting_name, void *data);
 +      static void settingChangedCallback(const std::string &setting_name, void *data);
 +      static void updateAllMapBlocksCallback(const std::string &setting_name, void *data);
 +      void readSettings();
 +
 +      bool isKeyDown(GameKeyType k);
 +      bool wasKeyDown(GameKeyType k);
 +      bool wasKeyPressed(GameKeyType k);
 +      bool wasKeyReleased(GameKeyType k);
 +
 +#ifdef __ANDROID__
 +      void handleAndroidChatInput();
 +#endif
 +
 +      struct Flags {
 +              bool force_fog_off = false;
 +              bool disable_camera_update = false;
 +      };
 +
 +      void showDeathFormspec();
 +      void showPauseMenu();
 +
 +      void pauseAnimation();
 +      void resumeAnimation();
 +
 +      // ClientEvent handlers
 +      void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_HandleParticleEvent(ClientEvent *event,
 +              CameraOrientation *cam);
 +      void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
 +      void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
 +              CameraOrientation *cam);
 +      void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
 +
-       CameraOrientation cam_view_target  = { 0 };
-       CameraOrientation cam_view  = { 0 };
++      void updateChat(f32 dtime);
 +
 +      bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
 +              const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
 +              const NodeMetadata *meta, bool force = false);
 +      static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
 +
 +      f32 getSensitivityScaleFactor() const;
 +
 +      InputHandler *input = nullptr;
 +
 +      Client *client = nullptr;
 +      Server *server = nullptr;
 +
 +      IWritableTextureSource *texture_src = nullptr;
 +      IWritableShaderSource *shader_src = nullptr;
 +
 +      // When created, these will be filled with data received from the server
 +      IWritableItemDefManager *itemdef_manager = nullptr;
 +      NodeDefManager *nodedef_manager = nullptr;
 +
 +      GameOnDemandSoundFetcher soundfetcher; // useful when testing
 +      ISoundManager *sound = nullptr;
 +      bool sound_is_dummy = false;
 +      SoundMaker *soundmaker = nullptr;
 +
 +      ChatBackend *chat_backend = nullptr;
 +      LogOutputBuffer m_chat_log_buf;
 +
 +      EventManager *eventmgr = nullptr;
 +      QuicktuneShortcutter *quicktune = nullptr;
 +      bool registration_confirmation_shown = false;
 +
 +      std::unique_ptr<GameUI> m_game_ui;
 +      GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
 +      CheatMenu *m_cheat_menu = nullptr;
 +      MapDrawControl *draw_control = nullptr;
 +      Camera *camera = nullptr;
 +      Clouds *clouds = nullptr;                         // Free using ->Drop()
 +      Sky *sky = nullptr;                         // Free using ->Drop()
 +      Hud *hud = nullptr;
 +      Minimap *mapper = nullptr;
 +
 +      // Map server hud ids to client hud ids
 +      std::unordered_map<u32, u32> m_hud_server_to_client;
 +
 +      GameRunData runData;
 +      Flags m_flags;
 +
 +      /* 'cache'
 +         This class does take ownership/responsibily for cleaning up etc of any of
 +         these items (e.g. device)
 +      */
 +      IrrlichtDevice *device;
 +      RenderingEngine *m_rendering_engine;
 +      video::IVideoDriver *driver;
 +      scene::ISceneManager *smgr;
 +      bool *kill;
 +      std::string *error_message;
 +      bool *reconnect_requested;
 +      scene::ISceneNode *skybox;
 +      PausedNodesList paused_animated_nodes;
 +
 +      bool simple_singleplayer_mode;
 +      /* End 'cache' */
 +
 +      /* Pre-calculated values
 +       */
 +      int crack_animation_length;
 +
 +      IntervalLimiter profiler_interval;
 +
 +      /*
 +       * TODO: Local caching of settings is not optimal and should at some stage
 +       *       be updated to use a global settings object for getting thse values
 +       *       (as opposed to the this local caching). This can be addressed in
 +       *       a later release.
 +       */
 +      bool m_cache_doubletap_jump;
 +      bool m_cache_enable_clouds;
 +      bool m_cache_enable_joysticks;
 +      bool m_cache_enable_particles;
 +      bool m_cache_enable_fog;
 +      bool m_cache_enable_noclip;
 +      bool m_cache_enable_free_move;
 +      f32  m_cache_mouse_sensitivity;
 +      f32  m_cache_joystick_frustum_sensitivity;
 +      f32  m_repeat_place_time;
 +      f32  m_cache_cam_smoothing;
 +      f32  m_cache_fog_start;
 +
 +      bool m_invert_mouse = false;
 +      bool m_first_loop_after_window_activation = false;
 +      bool m_camera_offset_changed = false;
 +
 +      bool m_does_lost_focus_pause_game = false;
 +
- #ifdef __ANDROID__
++      CameraOrientation cam_view_target = {}; // added by dragonfireclient
++      CameraOrientation cam_view  = {};       // added by dragonfireclient
 +
++#if IRRLICHT_VERSION_MT_REVISION < 5
 +      int m_reset_HW_buffer_counter = 0;
++#endif
++
++#ifdef HAVE_TOUCHSCREENGUI
 +      bool m_cache_hold_aux1;
++#endif
++#ifdef __ANDROID__
 +      bool m_android_chat_open;
 +#endif
 +};
 +extern Game *g_game;
  
  void the_game(bool *kill,
                InputHandler *input,
index 66a006ea196cae71925725bb84105933e0897462,01c733b4f412f678f4aa70153c984d44d5c42a35..54be24ae2ac6fd7fc65fef0a31f6bb8bbb112148
@@@ -103,30 -100,13 +103,30 @@@ void GameUI::update(const RunStats &sta
        const CameraOrientation &cam, const PointedThing &pointed_old,
        const GUIChatConsole *chat_console, float dtime)
  {
 +      LocalPlayer *player = client->getEnv().getLocalPlayer();
 +      v3f player_position = player->getPosition();
 +
        v2u32 screensize = RenderingEngine::getWindowSize();
  
 +      bool show_coords = g_settings->getBool("coords");
 +
 +      if (show_coords) {
 +              std::ostringstream os(std::ios_base::binary);
 +              os << std::setprecision(1) << std::fixed
 +                      << (player_position.X / BS)
 +                      << ", " << (player_position.Y / BS)
 +                      << ", " << (player_position.Z / BS);
 +              setStaticText(m_guitext_coords, utf8_to_wide(os.str()).c_str());
 +              m_guitext_coords->setRelativePosition(core::rect<s32>(5, screensize.Y - 5 - g_fontengine->getTextHeight(), screensize.X, screensize.Y));
 +      }
 +
 +      m_guitext_coords->setVisible(show_coords);
 +
        // Minimal debug text must only contain info that can't give a gameplay advantage
        if (m_flags.show_minimal_debug) {
-               static float drawtime_avg = 0;
-               drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05;
-               u16 fps = 1.0 / stats.dtime_jitter.avg;
+               const u16 fps = 1.0 / stats.dtime_jitter.avg;
+               m_drawtime_avg *= 0.95f;
+               m_drawtime_avg += 0.05f * (stats.drawtime / 1000);
  
                std::ostringstream os(std::ios_base::binary);
                os << std::fixed
@@@ -246,27 -230,32 +249,33 @@@ void GameUI::showTranslatedStatusText(c
  
  void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count)
  {
+       setStaticText(m_guitext_chat, chat_text);
  
+       m_recent_chat_count = recent_chat_count;
+ }
+ void GameUI::updateChatSize()
+ {
        // Update gui element size and position
 -      s32 chat_y = 5;
 +
 +      const v2u32 &window_size = RenderingEngine::getWindowSize();
 +
 +      s32 chat_y = window_size.Y - 150 - m_guitext_chat->getTextHeight();
  
        if (m_flags.show_minimal_debug)
                chat_y += g_fontengine->getLineHeight();
        if (m_flags.show_basic_debug)
                chat_y += g_fontengine->getLineHeight();
  
-       core::rect<s32> chat_size(10, chat_y,
-               window_size.X - 20, 0);
 -      const v2u32 &window_size = RenderingEngine::getWindowSize();
 -
+       core::rect<s32> chat_size(10, chat_y, window_size.X - 20, 0);
        chat_size.LowerRightCorner.Y = std::min((s32)window_size.Y,
-               m_guitext_chat->getTextHeight() + chat_y);
+                       m_guitext_chat->getTextHeight() + chat_y);
  
-       m_guitext_chat->setRelativePosition(chat_size);
-       setStaticText(m_guitext_chat, chat_text);
+       if (chat_size == m_current_chat_size)
+               return;
+       m_current_chat_size = chat_size;
  
-       m_recent_chat_count = recent_chat_count;
+       m_guitext_chat->setRelativePosition(chat_size);
  }
  
  void GameUI::updateProfiler()
index 5404643e2321e65a18f8152eaf87e83612d2491b,cc9377bdca4622ddc695edf0640eb59402b07a53..e22be068b2ced66d30cf27f85878390eef936ae7
@@@ -111,10 -110,11 +112,12 @@@ public
  private:
        Flags m_flags;
  
+       float m_drawtime_avg = 0;
        gui::IGUIStaticText *m_guitext = nullptr;  // First line of debug text
        gui::IGUIStaticText *m_guitext2 = nullptr; // Second line of debug text
 -
 +      gui::IGUIStaticText *m_guitext_coords = nullptr;
 +      
        gui::IGUIStaticText *m_guitext_info = nullptr; // At the middle of the screen
        std::wstring m_infotext;
  
Simple merge
index 951ec88840b414432956c7467a25a2b8d049fe06,3db105c518ecf6d066d1436247b6f558e3f1f21c..47a61d4b85ba86f6b5f454295170ab2ccfcdae6b
@@@ -198,6 -201,9 +202,8 @@@ public
        TouchScreenGUI *m_touchscreengui;
  #endif
  
 -private:
+       s32 mouse_wheel = 0;
        // The current state of keys
        KeyList keyIsDown;
  
Simple merge
index eaac216d3e7187db7dd4424b949c02cbff89f19d,3d0072fc1110f025607b4786db416db1c973fc30..ebc67c4f8c7849844375ca3bacbb60cb3fcb146a
@@@ -187,12 -159,8 +188,14 @@@ public
                added_velocity += vel;
        }
  
+       inline Lighting& getLighting() { return m_lighting; }
 +      void tryReattach(int id);
 +
 +      bool isWaitingForReattach() const;
 +
 +      bool canWalkOn(const ContentFeatures &f);
 +
  private:
        void accelerate(const v3f &target_speed, const f32 max_increase_H,
                const f32 max_increase_V, const bool use_pitch);
Simple merge
index 80075fce242ed07c52fdf525249f96b1ff8aa70f,72d12803825701a4b613a80453999950b6d4c12d..5e2d70b755a204622131c7f389249a42ecb80a0d
@@@ -125,9 -210,23 +210,25 @@@ public
                        m_animation_force_timer--;
        }
  
+       /// update transparent buffers to render towards the camera
+       void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos);
+       void consolidateTransparentBuffers();
+       /// get the list of transparent buffers
+       const std::vector<PartialMeshBuffer> &getTransparentBuffers() const
+       {
+               return this->m_transparent_buffers;
+       }
 +      std::set<v3s16> esp_nodes;
 +
  private:
+       struct AnimationInfo {
+               int frame; // last animation frame
+               int frame_offset;
+               TileLayer tile;
+       };
        scene::IMesh *m_mesh[MAX_TILE_LAYERS];
        MinimapMapblock *m_minimap_mapblock;
        ITextureSource *m_tsrc;
Simple merge
Simple merge
index 1028a96e140a5619b15cda7b0b41e7e03cc2d93d,55cc4e490b023417a09228e0af3baae626a4905b..9927e2589a97208c066ee85d6b621bdcbea15e78
@@@ -79,19 -75,12 +79,22 @@@ void RenderingCore::draw(video::SColor 
        show_minimap = _show_minimap;
        draw_wield_tool = _draw_wield_tool;
        draw_crosshair = _draw_crosshair;
 +      draw_entity_esp = g_settings->getBool("enable_entity_esp");
 +      draw_entity_tracers = g_settings->getBool("enable_entity_tracers");
 +      draw_player_esp = g_settings->getBool("enable_player_esp");
 +      draw_player_tracers = g_settings->getBool("enable_player_tracers");
 +      draw_node_esp = g_settings->getBool("enable_node_esp");
 +      draw_node_tracers = g_settings->getBool("enable_node_tracers");
 +      v3f entity_color = g_settings->getV3F("entity_esp_color");
 +      v3f player_color = g_settings->getV3F("player_esp_color");
 +      entity_esp_color = video::SColor(255, entity_color.X, entity_color.Y, entity_color.Z);
 +      player_esp_color = video::SColor(255, player_color.X, player_color.Y, player_color.Z);
  
-       if (shadow_renderer)
+       if (shadow_renderer) {
+               // This is necessary to render shadows for animations correctly
+               smgr->getRootSceneNode()->OnAnimate(device->getTimer()->getTime());
                shadow_renderer->update();
+       }
  
        beforeDraw();
        drawAll();
Simple merge
index 2e788956de89889c805127a8e1c767ef9ae10e30,be135a2256a1fb686e83eab8e5ebfdf79beb12ee..4f2cba2636d0b839b2ae5331d961b137e933f948
@@@ -301,10 -300,11 +301,11 @@@ collisionMoveResult collisionMoveSimple
                        const NodeDefManager *nodedef = gamedef->getNodeDefManager();
                        const ContentFeatures &f = nodedef->get(n);
  
 -                      if (!f.walkable)
 +                      if (!(f.walkable || (jesus && f.isLiquid())))
                                continue;
  
-                       int n_bouncy_value = itemgroup_get(f.groups, "bouncy");
+                       // Negative bouncy may have a meaning, but we need +value here.
+                       int n_bouncy_value = abs(itemgroup_get(f.groups, "bouncy"));
  
                        int neighbors = 0;
                        if (f.drawtype == NDT_NODEBOX &&
index 0d509752bd61c1f4d1e40119febf9950d1528046,11d52efd3dc0d70b26097e0a8609f7379e0a7f6f..ef2f8724dbc9d134bd2bf4f3a9f4e1ef0acc6823
@@@ -65,69 -65,8 +65,68 @@@ void set_default_settings(
        settings->setDefault("max_out_chat_queue_size", "20");
        settings->setDefault("pause_on_lost_focus", "false");
        settings->setDefault("enable_register_confirmation", "true");
-       settings->setDefault("clickable_chat_weblinks", "false");
        settings->setDefault("chat_weblink_color", "#8888FF");
  
 +      // Cheat Menu
 +      settings->setDefault("cheat_menu_font", "FM_Standard");
 +      settings->setDefault("cheat_menu_bg_color", "(45, 45, 68)");
 +      settings->setDefault("cheat_menu_bg_color_alpha", "173");
 +      settings->setDefault("cheat_menu_active_bg_color", "(0, 0, 0)");
 +      settings->setDefault("cheat_menu_active_bg_color_alpha", "210");
 +      settings->setDefault("cheat_menu_font_color", "(255, 255, 255)");
 +      settings->setDefault("cheat_menu_font_color_alpha", "195");
 +      settings->setDefault("cheat_menu_selected_font_color", "(255, 255, 255)");
 +      settings->setDefault("cheat_menu_selected_font_color_alpha", "235");
 +      settings->setDefault("cheat_menu_head_height", "50");
 +      settings->setDefault("cheat_menu_entry_height", "35");
 +      settings->setDefault("cheat_menu_entry_width", "200");
 +
 +      // Cheats
 +      settings->setDefault("xray", "false");
 +      settings->setDefault("xray_nodes", "default:stone,mcl_core:stone");
 +      settings->setDefault("fullbright", "false");
 +      settings->setDefault("priv_bypass", "true");
 +      settings->setDefault("freecam", "false");
 +      settings->setDefault("prevent_natural_damage", "true");
 +      settings->setDefault("freecam", "false");
 +      settings->setDefault("no_hurt_cam", "false");
 +      settings->setDefault("reach", "true");
 +      settings->setDefault("hud_flags_bypass", "true");
 +      settings->setDefault("antiknockback", "false");
 +      settings->setDefault("entity_speed", "false");
 +      settings->setDefault("autodig", "false");
 +      settings->setDefault("fastdig", "false");
 +      settings->setDefault("jesus", "false");
 +      settings->setDefault("fastplace", "false");
 +      settings->setDefault("autoplace", "false");
 +      settings->setDefault("instant_break", "false");
 +      settings->setDefault("no_night", "false");
 +      settings->setDefault("coords", "false");
 +      settings->setDefault("point_liquids", "false");
 +      settings->setDefault("spamclick", "false");
 +      settings->setDefault("no_force_rotate", "false");
 +      settings->setDefault("no_slow", "false");
 +      settings->setDefault("float_above_parent", "false");
 +      settings->setDefault("dont_point_nodes", "false");
 +      settings->setDefault("cheat_hud", "true");
 +      settings->setDefault("node_esp_nodes", "");
 +      settings->setDefault("jetpack", "false");
 +      settings->setDefault("autohit", "false");
 +      settings->setDefault("antislip", "false");
 +      settings->setDefault("enable_entity_esp", "false");
 +      settings->setDefault("enable_entity_tracers", "false");
 +      settings->setDefault("enable_player_esp", "false");
 +      settings->setDefault("enable_player_tracers", "false");
 +      settings->setDefault("enable_node_esp", "false");
 +      settings->setDefault("enable_node_tracers", "false");
 +      settings->setDefault("entity_esp_color", "(255, 255, 255)");
 +      settings->setDefault("player_esp_color", "(0, 255, 0)");
 +      settings->setDefault("tool_range", "2");
 +      settings->setDefault("scaffold", "false");
 +      settings->setDefault("killaura", "false");
 +      settings->setDefault("airjump", "false");
 +      settings->setDefault("spider", "false");
 +
        // Keymap
        settings->setDefault("remote_port", "30000");
        settings->setDefault("keymap_forward", "KEY_KEY_W");
        settings->setDefault("enable_particles", "true");
        settings->setDefault("arm_inertia", "true");
        settings->setDefault("show_nametag_backgrounds", "true");
+       settings->setDefault("transparency_sorting_distance", "16");
  
        settings->setDefault("enable_minimap", "true");
 -      settings->setDefault("minimap_shape_round", "true");
 +      settings->setDefault("minimap_shape_round", "false");
        settings->setDefault("minimap_double_scan_height", "true");
  
        // Effects
Simple merge
diff --cc src/gamedef.h
Simple merge
index ea6e44ab7b554156c0b0aae304956032d693a896,513b13e8ec64814302f56838239bc30d44ea5d90..a5f25c0f3ec511129af6549ccfbad02bd18d83c2
@@@ -1,5 -1,9 +1,10 @@@
+ set(extra_gui_SRCS "")
+ if(ENABLE_TOUCH)
+       set(extra_gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/touchscreengui.cpp)
+ endif()
  set(gui_SRCS
 +      ${CMAKE_CURRENT_SOURCE_DIR}/cheatMenu.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiAnimatedImage.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiBackgroundImage.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiBox.cpp
index 31acfb78045b8e9782255eb2c70b597f3ecc467f,0000000000000000000000000000000000000000..2be82f148a8997fb25f8d2adbbae69429cab926a
mode 100644,000000..100644
--- /dev/null
@@@ -1,270 -1,0 +1,266 @@@
-       else if (str == "FM_Simple")
-               return FM_Simple;
-       else if (str == "FM_SimpleMono")
-               return FM_SimpleMono;
 +/*
 +Dragonfire
 +Copyright (C) 2020 Elias Fleckenstein <eliasfleckenstein@web.de>
 +
 +This program is free software; you can redistribute it and/or modify
 +it under the terms of the GNU Lesser General Public License as published by
 +the Free Software Foundation; either version 2.1 of the License, or
 +(at your option) any later version.
 +
 +This program is distributed in the hope that it will be useful,
 +but WITHOUT ANY WARRANTY; without even the implied warranty of
 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +GNU Lesser General Public License for more details.
 +
 +You should have received a copy of the GNU Lesser General Public License along
 +with this program; if not, write to the Free Software Foundation, Inc.,
 +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 +*/
 +
 +#include "script/scripting_client.h"
 +#include "client/client.h"
 +#include "client/fontengine.h"
 +#include "cheatMenu.h"
 +#include <cstddef>
 +
 +FontMode CheatMenu::fontStringToEnum(std::string str)
 +{
 +      if (str == "FM_Standard")
 +              return FM_Standard;
 +      else if (str == "FM_Mono")
 +              return FM_Mono;
 +      else if (str == "FM_Fallback")
 +              return _FM_Fallback;
 +      else if (str == "FM_MaxMode")
 +              return FM_MaxMode;
 +      else if (str == "FM_Unspecified")
 +              return FM_Unspecified;
 +      else
 +              return FM_Standard;
 +}
 +
 +CheatMenu::CheatMenu(Client *client) : m_client(client)
 +{
 +      FontMode fontMode = fontStringToEnum(g_settings->get("cheat_menu_font"));
 +      v3f bg_color, active_bg_color, font_color, selected_font_color;
 +
 +      bg_color = g_settings->getV3F("cheat_menu_bg_color");
 +      active_bg_color = g_settings->getV3F("cheat_menu_active_bg_color");
 +      font_color = g_settings->getV3F("cheat_menu_font_color");
 +      selected_font_color = g_settings->getV3F("cheat_menu_selected_font_color");
 +
 +      m_bg_color = video::SColor(g_settings->getU32("cheat_menu_bg_color_alpha"),
 +                      bg_color.X, bg_color.Y, bg_color.Z);
 +
 +      m_active_bg_color = video::SColor(
 +                      g_settings->getU32("cheat_menu_active_bg_color_alpha"),
 +                      active_bg_color.X, active_bg_color.Y, active_bg_color.Z);
 +
 +      m_font_color = video::SColor(g_settings->getU32("cheat_menu_font_color_alpha"),
 +                      font_color.X, font_color.Y, font_color.Z);
 +
 +      m_selected_font_color = video::SColor(
 +                      g_settings->getU32("cheat_menu_selected_font_color_alpha"),
 +                      selected_font_color.X, selected_font_color.Y,
 +                      selected_font_color.Z);
 +
 +      m_head_height = g_settings->getU32("cheat_menu_head_height");
 +      m_entry_height = g_settings->getU32("cheat_menu_entry_height");
 +      m_entry_width = g_settings->getU32("cheat_menu_entry_width");
 +
 +      m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, fontMode);
 +
 +      if (!m_font) {
 +              errorstream << "CheatMenu: Unable to load font" << std::endl;
 +      } else {
 +              core::dimension2d<u32> dim = m_font->getDimension(L"M");
 +              m_fontsize = v2u32(dim.Width, dim.Height);
 +              m_font->grab();
 +      }
 +      m_fontsize.X = MYMAX(m_fontsize.X, 1);
 +      m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
 +}
 +
 +void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, int number,
 +              bool selected, bool active, CheatMenuEntryType entry_type)
 +{
 +      int x = m_gap, y = m_gap, width = m_entry_width, height = m_entry_height;
 +      video::SColor *bgcolor = &m_bg_color, *fontcolor = &m_font_color;
 +      if (entry_type == CHEAT_MENU_ENTRY_TYPE_HEAD) {
 +              bgcolor = &m_active_bg_color;
 +              height = m_head_height;
 +      } else {
 +              bool is_category = entry_type == CHEAT_MENU_ENTRY_TYPE_CATEGORY;
 +              y += m_gap + m_head_height +
 +                   (number + (is_category ? 0 : m_selected_category)) *
 +                                   (m_entry_height + m_gap);
 +              x += (is_category ? 0 : m_gap + m_entry_width);
 +              if (active)
 +                      bgcolor = &m_active_bg_color;
 +              if (selected)
 +                      fontcolor = &m_selected_font_color;
 +      }
 +      driver->draw2DRectangle(*bgcolor, core::rect<s32>(x, y, x + width, y + height));
 +      if (selected)
 +              driver->draw2DRectangleOutline(
 +                              core::rect<s32>(x - 1, y - 1, x + width, y + height),
 +                              *fontcolor);
 +      int fx = x + 5, fy = y + (height - m_fontsize.Y) / 2;
 +      core::rect<s32> fontbounds(
 +                      fx, fy, fx + m_fontsize.X * name.size(), fy + m_fontsize.Y);
 +      m_font->draw(name.c_str(), fontbounds, *fontcolor, false, false);
 +}
 +
 +void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug)
 +{
 +      CHEAT_MENU_GET_SCRIPTPTR
 +
 +      if (!show_debug)
 +              drawEntry(driver, "Dragonfireclient", 0, false, false,
 +                              CHEAT_MENU_ENTRY_TYPE_HEAD);
 +      int category_count = 0;
 +      for (auto category = script->m_cheat_categories.begin();
 +                      category != script->m_cheat_categories.end(); category++) {
 +              bool is_selected = category_count == m_selected_category;
 +              drawEntry(driver, (*category)->m_name, category_count, is_selected, false,
 +                              CHEAT_MENU_ENTRY_TYPE_CATEGORY);
 +              if (is_selected && m_cheat_layer) {
 +                      int cheat_count = 0;
 +                      for (auto cheat = (*category)->m_cheats.begin();
 +                                      cheat != (*category)->m_cheats.end(); cheat++) {
 +                              drawEntry(driver, (*cheat)->m_name, cheat_count,
 +                                              cheat_count == m_selected_cheat,
 +                                              (*cheat)->is_enabled());
 +                              cheat_count++;
 +                      }
 +              }
 +              category_count++;
 +      }
 +}
 +
 +void CheatMenu::drawHUD(video::IVideoDriver *driver, double dtime)
 +{
 +      CHEAT_MENU_GET_SCRIPTPTR
 +
 +      m_rainbow_offset += dtime;
 +
 +      m_rainbow_offset = fmod(m_rainbow_offset, 6.0f);
 +
 +      std::vector<std::string> enabled_cheats;
 +
 +      int cheat_count = 0;
 +
 +      for (auto category = script->m_cheat_categories.begin();
 +                      category != script->m_cheat_categories.end(); category++) {
 +              for (auto cheat = (*category)->m_cheats.begin();
 +                              cheat != (*category)->m_cheats.end(); cheat++) {
 +                      if ((*cheat)->is_enabled()) {
 +                              enabled_cheats.push_back((*cheat)->m_name);
 +                              cheat_count++;
 +                      }
 +              }
 +      }
 +
 +      if (enabled_cheats.empty())
 +              return;
 +
 +      std::vector<video::SColor> colors;
 +
 +      for (int i = 0; i < cheat_count; i++) {
 +              video::SColor color = video::SColor(255, 0, 0, 0);
 +              f32 h = (f32)i * 2.0f / (f32)cheat_count - m_rainbow_offset;
 +              if (h < 0)
 +                      h = 6.0f + h;
 +              f32 x = (1 - fabs(fmod(h, 2.0f) - 1.0f)) * 255.0f;
 +              switch ((int)h) {
 +              case 0:
 +                      color = video::SColor(255, 255, x, 0);
 +                      break;
 +              case 1:
 +                      color = video::SColor(255, x, 255, 0);
 +                      break;
 +              case 2:
 +                      color = video::SColor(255, 0, 255, x);
 +                      break;
 +              case 3:
 +                      color = video::SColor(255, 0, x, 255);
 +                      break;
 +              case 4:
 +                      color = video::SColor(255, x, 0, 255);
 +                      break;
 +              case 5:
 +                      color = video::SColor(255, 255, 0, x);
 +                      break;
 +              }
 +              colors.push_back(color);
 +      }
 +
 +      core::dimension2d<u32> screensize = driver->getScreenSize();
 +
 +      u32 y = 5;
 +
 +      int i = 0;
 +      for (std::string cheat : enabled_cheats) {
 +              core::dimension2d<u32> dim =
 +                              m_font->getDimension(utf8_to_wide(cheat).c_str());
 +              u32 x = screensize.Width - 5 - dim.Width;
 +
 +              core::rect<s32> fontbounds(x, y, x + dim.Width, y + dim.Height);
 +              m_font->draw(cheat.c_str(), fontbounds, colors[i], false, false);
 +
 +              y += dim.Height;
 +              i++;
 +      }
 +}
 +
 +void CheatMenu::selectUp()
 +{
 +      CHEAT_MENU_GET_SCRIPTPTR
 +
 +      int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category]
 +                                                                ->m_cheats.size()
 +                               : script->m_cheat_categories.size()) -
 +                1;
 +      int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category;
 +      --*selected;
 +      if (*selected < 0)
 +              *selected = max;
 +}
 +
 +void CheatMenu::selectDown()
 +{
 +      CHEAT_MENU_GET_SCRIPTPTR
 +
 +      int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category]
 +                                                                ->m_cheats.size()
 +                               : script->m_cheat_categories.size()) -
 +                1;
 +      int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category;
 +      ++*selected;
 +      if (*selected > max)
 +              *selected = 0;
 +}
 +
 +void CheatMenu::selectRight()
 +{
 +      if (m_cheat_layer)
 +              return;
 +      m_cheat_layer = true;
 +      m_selected_cheat = 0;
 +}
 +
 +void CheatMenu::selectLeft()
 +{
 +      if (!m_cheat_layer)
 +              return;
 +      m_cheat_layer = false;
 +}
 +
 +void CheatMenu::selectConfirm()
 +{
 +      CHEAT_MENU_GET_SCRIPTPTR
 +
 +      if (m_cheat_layer)
 +              script->toggle_cheat(script->m_cheat_categories[m_selected_category]
 +                                                   ->m_cheats[m_selected_cheat]);
 +}
diff --cc src/map.cpp
index 1648adec3d4b1c5a7ad83efe49d369ae721f59ba,ce69accb5180445ea284882a0680e8e0b6a3fd77..7bc1334b0fbf9aa2238e7abfe29e2006e5c74628
@@@ -139,28 -139,6 +139,21 @@@ MapBlock * Map::getBlockNoCreate(v3s16 
        return block;
  }
  
- bool Map::isNodeUnderground(v3s16 p)
- {
-       v3s16 blockpos = getNodeBlockPos(p);
-       MapBlock *block = getBlockNoCreateNoEx(blockpos);
-       return block && block->getIsUnderground();
- }
 +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);
diff --cc src/map.h
index 9c5c16368b1e98e39074179ecaba9f40a6751c13,1e5499586162780b8e71a1c0b423e7e15a1ac781..248312ebe5436d3f6539fe949ed9931b808583b9
+++ b/src/map.h
@@@ -375,11 -352,12 +354,11 @@@ public
        static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf);
  
        // Call these before and after saving of blocks
-       void beginSave();
-       void endSave();
+       void beginSave() override;
+       void endSave() override;
  
-       void save(ModifiedState save_level);
+       void save(ModifiedState save_level) override;
        void listAllLoadableBlocks(std::vector<v3s16> &dst);
 -      void listAllLoadedBlocks(std::vector<v3s16> &dst);
  
        MapgenParams *getMapgenParams();
  
Simple merge
diff --cc src/nodedef.cpp
Simple merge
diff --cc src/player.cpp
index 13b79da0451e3a9db3e44df1ac5950b66e4f4399,1e064c1dac557d43c92ab9663c9d552f4c3f8e7b..789d852eab325c12e09083ae77458d387190552a
@@@ -159,16 -160,73 +160,74 @@@ void Player::clearHud(
        }
  }
  
+ #ifndef SERVER
+ u32 PlayerControl::getKeysPressed() const
+ {
+       u32 keypress_bits =
+               ( (u32)(jump  & 1) << 4) |
+               ( (u32)(aux1  & 1) << 5) |
+               ( (u32)(sneak & 1) << 6) |
+               ( (u32)(dig   & 1) << 7) |
+               ( (u32)(place & 1) << 8) |
+               ( (u32)(zoom  & 1) << 9)
+       ;
+       // If any direction keys are pressed pass those through
+       if (direction_keys != 0)
+       {
+               keypress_bits |= direction_keys;
+       }
+       // Otherwise set direction keys based on joystick movement (for mod compatibility)
+       else if (isMoving())
+       {
+               float abs_d;
+               // (absolute value indicates forward / backward)
+               abs_d = abs(movement_direction);
+               if (abs_d < 3.0f / 8.0f * M_PI)
+                       keypress_bits |= (u32)1; // Forward
+               if (abs_d > 5.0f / 8.0f * M_PI)
+                       keypress_bits |= (u32)1 << 1; // Backward
+               // rotate entire coordinate system by 90 degree
+               abs_d = movement_direction + M_PI_2;
+               if (abs_d >= M_PI)
+                       abs_d -= 2 * M_PI;
+               abs_d = abs(abs_d);
+               // (value now indicates left / right)
+               if (abs_d < 3.0f / 8.0f * M_PI)
+                       keypress_bits |= (u32)1 << 2; // Left
+               if (abs_d > 5.0f / 8.0f * M_PI)
+                       keypress_bits |= (u32)1 << 3; // Right
+       }
+       return keypress_bits;
+ }
+ #endif
+ void PlayerControl::unpackKeysPressed(u32 keypress_bits)
+ {
+       direction_keys = keypress_bits & 0xf;
+       jump  = keypress_bits & (1 << 4);
+       aux1  = keypress_bits & (1 << 5);
+       sneak = keypress_bits & (1 << 6);
+       dig   = keypress_bits & (1 << 7);
+       place = keypress_bits & (1 << 8);
+       zoom  = keypress_bits & (1 << 9);
+ }
  void PlayerSettings::readGlobalSettings()
  {
 -      free_move = g_settings->getBool("free_move");
 +      freecam = g_settings->getBool("freecam");
 +      free_move = g_settings->getBool("free_move") || freecam;
        pitch_move = g_settings->getBool("pitch_move");
 -      fast_move = g_settings->getBool("fast_move");
 +      fast_move = g_settings->getBool("fast_move") || freecam;
        continuous_forward = g_settings->getBool("continuous_forward");
 -      always_fly_fast = g_settings->getBool("always_fly_fast");
 +      always_fly_fast = g_settings->getBool("always_fly_fast") || freecam;
        aux1_descends = g_settings->getBool("aux1_descends");
 -      noclip = g_settings->getBool("noclip");
 +      noclip = g_settings->getBool("noclip") || freecam;
        autojump = g_settings->getBool("autojump");
  }
  
diff --cc src/player.h
Simple merge
Simple merge
index 1aed7901e835c3db11afe04d29d397d8509d9289,a7b8709c623ae5b797040024ed2b115f29c6618c..a6b96c0122913cf5787297b9092d20265ab057e7
@@@ -120,15 -120,13 +121,16 @@@ void               read_object_properti
  void               push_object_properties    (lua_State *L,
                                                ObjectProperties *prop);
  
 +void               push_inventory              (lua_State *L,
 +                                              Inventory *inventory);
 +
  void               push_inventory_list       (lua_State *L,
-                                               Inventory *inv,
-                                               const char *name);
+                                               const InventoryList &invlist);
+ void               push_inventory_lists      (lua_State *L,
+                                               const Inventory &inv);
  void               read_inventory_list       (lua_State *L, int tableindex,
                                                Inventory *inv, const char *name,
-                                               Server *srv, int forcesize=-1);
+                                               IGameDef *gdef, int forcesize=-1);
  
  MapNode            readnode                  (lua_State *L, int index,
                                                const NodeDefManager *ndef);
index 27d730b1d89248936db806acf5331ea4ff28007d,595c9e540b3eec64f566adcf9d95d89a5068135d..ae4a1677156b88b6d1021a23cdde300f1b70da3f
@@@ -84,11 -85,16 +86,16 @@@ ScriptApiBase::ScriptApiBase(ScriptingT
  
        lua_atpanic(m_luastack, &luaPanic);
  
 -      if (m_type == ScriptingType::Client)
 +      /*if (m_type == ScriptingType::Client)
                clientOpenLibs(m_luastack);
 -      else
 +      else*/
                luaL_openlibs(m_luastack);
  
+       // Load bit library
+       lua_pushcfunction(m_luastack, luaopen_bit);
+       lua_pushstring(m_luastack, LUA_BITLIBNAME);
+       lua_call(m_luastack, 1, 0);
        // Make the ScriptApiBase* accessible to ModApiBase
  #if INDIRECT_SCRIPTAPI_RIDX
        *(void **)(lua_newuserdata(m_luastack, sizeof(void *))) = this;
Simple merge
Simple merge
index bd9c80e15ce32d32cc4dfe570289ee7c0c6b63c3,f68cd17771690ff7459c5dbb937ae915e5992dfc..76509038f3ca303f8e6287f46b14fde4ed08f4db
@@@ -106,9 -108,9 +108,10 @@@ void ScriptApiSecurity::initializeSecur
                "string",
                "table",
                "math",
+               "bit"
        };
        static const char *io_whitelist[] = {
 +              "open",
                "close",
                "flush",
                "read",
@@@ -307,9 -317,10 +318,9 @@@ void ScriptApiSecurity::initializeSecur
                "time"
        };
        static const char *debug_whitelist[] = {
-               "getinfo",
+               "getinfo", // used by builtin and unset before mods load
                "traceback"
        };
 -
  #if USE_LUAJIT
        static const char *jit_whitelist[] = {
                "arch",
index 1d769c73e9b433b8ad51c02ec5afc7e4a1685158,aaced7cd0a09ab8343683ff5632dbf9573f35318..265c7d3fc6bbe189b497a6ef0432953a1eaf4c0c
@@@ -417,253 -414,6 +417,253 @@@ int ModApiClient::l_get_csm_restriction
        return 1;
  }
  
-               push_inventory(L, inventory);
 +// send_damage(damage)
 +int ModApiClient::l_send_damage(lua_State *L)
 +{
 +      u16 damage = luaL_checknumber(L, 1);
 +      getClient(L)->sendDamage(damage);
 +      return 0;
 +}
 +
 +// place_node(pos)
 +int ModApiClient::l_place_node(lua_State *L)
 +{
 +      Client *client = getClient(L);
 +      ClientMap &map = client->getEnv().getClientMap();
 +      LocalPlayer *player = client->getEnv().getLocalPlayer();
 +      ItemStack selected_item, hand_item;
 +      player->getWieldedItem(&selected_item, &hand_item);
 +      const ItemDefinition &selected_def = selected_item.getDefinition(getGameDef(L)->idef());
 +      v3s16 pos = read_v3s16(L, 1);
 +      PointedThing pointed;
 +      pointed.type = POINTEDTHING_NODE;
 +      pointed.node_abovesurface = pos;
 +      pointed.node_undersurface = pos;
 +      NodeMetadata *meta = map.getNodeMetadata(pos);
 +      g_game->nodePlacement(selected_def, selected_item, pos, pos, pointed, meta, true);
 +      return 0;
 +}
 +
 +// dig_node(pos)
 +int ModApiClient::l_dig_node(lua_State *L)
 +{
 +      Client *client = getClient(L);
 +      v3s16 pos = read_v3s16(L, 1);
 +      PointedThing pointed;
 +      pointed.type = POINTEDTHING_NODE;
 +      pointed.node_abovesurface = pos;
 +      pointed.node_undersurface = pos;
 +      client->interact(INTERACT_START_DIGGING, pointed);
 +      client->interact(INTERACT_DIGGING_COMPLETED, pointed);
 +      client->removeNode(pos);
 +      return 0;
 +}
 +
 +// get_inventory(location)
 +int ModApiClient::l_get_inventory(lua_State *L)
 +{
 +      Client *client = getClient(L);
 +      InventoryLocation inventory_location;
 +      Inventory *inventory;
 +      std::string location;
 +
 +      location = readParam<std::string>(L, 1);
 +
 +      try {
 +              inventory_location.deSerialize(location);
 +              inventory = client->getInventory(inventory_location);
 +              if (! inventory)
 +                      throw SerializationError(std::string("Attempt to access nonexistant inventory (") + location + ")");
++              push_inventory_lists(L, *inventory);
 +      } catch (SerializationError &) {
 +              lua_pushnil(L);
 +      }
 +
 +      return 1;
 +}
 +
 +// set_keypress(key_setting, pressed) -> returns true on success
 +int ModApiClient::l_set_keypress(lua_State *L)
 +{
 +      std::string setting_name = "keymap_" + readParam<std::string>(L, 1);
 +      bool pressed = lua_isboolean(L, 2) && readParam<bool>(L, 2);
 +      try {
 +              KeyPress keyCode = getKeySetting(setting_name.c_str());
 +              if (pressed)
 +                      g_game->input->setKeypress(keyCode);
 +              else
 +                      g_game->input->unsetKeypress(keyCode);
 +              lua_pushboolean(L, true);
 +      } catch (SettingNotFoundException &) {
 +              lua_pushboolean(L, false);
 +      }
 +      return 1;
 +}
 +
 +// drop_selected_item()
 +int ModApiClient::l_drop_selected_item(lua_State *L)
 +{
 +      g_game->dropSelectedItem();
 +      return 0;
 +}
 +
 +// get_objects_inside_radius(pos, radius)
 +int ModApiClient::l_get_objects_inside_radius(lua_State *L)
 +{
 +      ClientEnvironment &env = getClient(L)->getEnv();
 +
 +      v3f pos = checkFloatPos(L, 1);
 +      float radius = readParam<float>(L, 2) * BS;
 +
 +      std::vector<DistanceSortedActiveObject> objs;
 +      env.getActiveObjects(pos, radius, objs);
 +
 +      int i = 0;
 +      lua_createtable(L, objs.size(), 0);
 +      for (const auto obj : objs) {
 +              push_objectRef(L, obj.obj->getId());
 +              lua_rawseti(L, -2, ++i);
 +      }
 +      return 1;
 +}
 +
 +// make_screenshot()
 +int ModApiClient::l_make_screenshot(lua_State *L)
 +{
 +      getClient(L)->makeScreenshot();
 +      return 0;
 +}
 +
 +/*
 +`pointed_thing`
 +---------------
 +
 +* `{type="nothing"}`
 +* `{type="node", under=pos, above=pos}`
 +    * Indicates a pointed node selection box.
 +    * `under` refers to the node position behind the pointed face.
 +    * `above` refers to the node position in front of the pointed face.
 +* `{type="object", ref=ObjectRef}`
 +
 +Exact pointing location (currently only `Raycast` supports these fields):
 +
 +* `pointed_thing.intersection_point`: The absolute world coordinates of the
 +  point on the selection box which is pointed at. May be in the selection box
 +  if the pointer is in the box too.
 +* `pointed_thing.box_id`: The ID of the pointed selection box (counting starts
 +  from 1).
 +* `pointed_thing.intersection_normal`: Unit vector, points outwards of the
 +  selected selection box. This specifies which face is pointed at.
 +  Is a null vector `{x = 0, y = 0, z = 0}` when the pointer is inside the
 +  selection box.
 +*/
 +
 +// interact(action, pointed_thing)
 +int ModApiClient::l_interact(lua_State *L)
 +{
 +      std::string action_str = readParam<std::string>(L, 1);
 +      InteractAction action;
 +
 +      if (action_str == "start_digging")
 +              action = INTERACT_START_DIGGING;
 +      else if (action_str == "stop_digging")
 +              action = INTERACT_STOP_DIGGING;
 +      else if (action_str == "digging_completed")
 +              action = INTERACT_DIGGING_COMPLETED;
 +      else if (action_str == "place")
 +              action = INTERACT_PLACE;
 +      else if (action_str == "use")
 +              action = INTERACT_USE;
 +      else if (action_str == "activate")
 +              action = INTERACT_ACTIVATE;
 +      else
 +              return 0;
 +
 +      lua_getfield(L, 2, "type");
 +      if (! lua_isstring(L, -1))
 +              return 0;
 +      std::string type_str = lua_tostring(L, -1);
 +      lua_pop(L, 1);
 +
 +      PointedThingType type;
 +
 +      if (type_str == "nothing")
 +              type = POINTEDTHING_NOTHING;
 +      else if (type_str == "node")
 +              type = POINTEDTHING_NODE;
 +      else if (type_str == "object")
 +              type = POINTEDTHING_OBJECT;
 +      else
 +              return 0;
 +
 +      PointedThing pointed;
 +      pointed.type = type;
 +      ClientObjectRef *obj;
 +
 +      switch (type) {
 +      case POINTEDTHING_NODE:
 +              lua_getfield(L, 2, "under");
 +              pointed.node_undersurface = check_v3s16(L, -1);
 +
 +              lua_getfield(L, 2, "above");
 +              pointed.node_abovesurface = check_v3s16(L, -1);
 +              break;
 +      case POINTEDTHING_OBJECT:
 +              lua_getfield(L, 2, "ref");
 +              obj = ClientObjectRef::checkobject(L, -1);
 +              pointed.object_id = obj->getClientActiveObject()->getId();
 +              break;
 +      default:
 +              break;
 +      }
 +
 +      getClient(L)->interact(action, pointed);
 +      lua_pushboolean(L, true);
 +      return 1;
 +}
 +
 +StringMap *table_to_stringmap(lua_State *L, int index)
 +{
 +      StringMap *m = new StringMap;
 +
 +      lua_pushvalue(L, index);
 +      lua_pushnil(L);
 +
 +      while (lua_next(L, -2)) {
 +              lua_pushvalue(L, -2);
 +              std::basic_string<char> key = lua_tostring(L, -1);
 +              std::basic_string<char> value = lua_tostring(L, -2);
 +              (*m)[key] = value;
 +              lua_pop(L, 2);
 +      }
 +
 +      lua_pop(L, 1);
 +
 +      return m;
 +}
 +
 +// send_inventory_fields(formname, fields)
 +// Only works if the inventory form was opened beforehand.
 +int ModApiClient::l_send_inventory_fields(lua_State *L)
 +{
 +      std::string formname = luaL_checkstring(L, 1);
 +      StringMap *fields = table_to_stringmap(L, 2);
 +
 +      getClient(L)->sendInventoryFields(formname, *fields);
 +      return 0;
 +}
 +
 +// send_nodemeta_fields(position, formname, fields)
 +int ModApiClient::l_send_nodemeta_fields(lua_State *L)
 +{
 +      v3s16 pos = check_v3s16(L, 1);
 +      std::string formname = luaL_checkstring(L, 2);
 +      StringMap *m = table_to_stringmap(L, 3);
 +
 +      getClient(L)->sendNodemetaFields(pos, formname, *m);
 +      return 0;
 +}
 +
  void ModApiClient::Initialize(lua_State *L, int top)
  {
        API_FCT(get_current_modname);
index 876f84d537f443f50ef760a16e59de25bd84c5db,7640f27825dde091016f9ca0a848676836e11326..a489d245c431e18b0a4f079b3f5a85283f3ff0c3
@@@ -879,165 -880,21 +879,180 @@@ int ModApiEnvMod::l_find_node_near(lua_
        return 0;
  }
  
 +// find_nodes_near(pos, radius, nodenames, [search_center])
 +// nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
 +int ModApiEnvMod::l_find_nodes_near(lua_State *L)
 +{
 +      GET_PLAIN_ENV_PTR;
 +
 +      const NodeDefManager *ndef = env->getGameDef()->ndef();
 +      Map &map = env->getMap();
 +
 +      v3s16 pos = read_v3s16(L, 1);
 +      int radius = luaL_checkinteger(L, 2);
 +      std::vector<content_t> filter;
 +      collectNodeIds(L, 3, ndef, filter);
 +
 +      int start_radius = (lua_isboolean(L, 4) && readParam<bool>(L, 4)) ? 0 : 1;
 +
 +#ifndef SERVER
 +      // Client API limitations
 +      if (Client *client = getClient(L))
 +              radius = client->CSMClampRadius(pos, radius);
 +#endif
 +
 +      std::vector<u32> individual_count;
 +      individual_count.resize(filter.size());
 +
 +      lua_newtable(L);
 +      u32 i = 0;
 +
 +      for (int d = start_radius; d <= radius; d++) {
 +              const std::vector<v3s16> &list = FacePositionCache::getFacePositions(d);
 +              for (const v3s16 &posi : list) {
 +                      v3s16 p = pos + posi;
 +                      content_t c = map.getNode(p).getContent();
 +                      auto it = std::find(filter.begin(), filter.end(), c);
 +                      if (it != filter.end()) {
 +                              push_v3s16(L, p);
 +                              lua_rawseti(L, -2, ++i);
 +
 +                              u32 filt_index = it - filter.begin();
 +                              individual_count[filt_index]++;
 +                      }
 +              }
 +      }
 +      lua_createtable(L, 0, filter.size());
 +      for (u32 i = 0; i < filter.size(); i++) {
 +              lua_pushinteger(L, individual_count[i]);
 +              lua_setfield(L, -2, ndef->get(filter[i]).name.c_str());
 +      }
 +      return 2;
 +}
 +
 +// find_nodes_near_under_air(pos, radius, nodenames, [search_center])
 +// nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
 +int ModApiEnvMod::l_find_nodes_near_under_air(lua_State *L)
 +{
 +      GET_PLAIN_ENV_PTR;
 +
 +      const NodeDefManager *ndef = env->getGameDef()->ndef();
 +      Map &map = env->getMap();
 +
 +      v3s16 pos = read_v3s16(L, 1);
 +      int radius = luaL_checkinteger(L, 2);
 +      std::vector<content_t> filter;
 +      collectNodeIds(L, 3, ndef, filter);
 +      int start_radius = (lua_isboolean(L, 4) && readParam<bool>(L, 4)) ? 0 : 1;
 +
 +#ifndef SERVER
 +      // Client API limitations
 +      if (Client *client = getClient(L))
 +              radius = client->CSMClampRadius(pos, radius);
 +#endif
 +
 +      std::vector<u32> individual_count;
 +      individual_count.resize(filter.size());
 +
 +      lua_newtable(L);
 +      u32 i = 0;
 +
 +      for (int d = start_radius; d <= radius; d++) {
 +              const std::vector<v3s16> &list = FacePositionCache::getFacePositions(d);
 +              for (const v3s16 &posi : list) {
 +                      v3s16 p = pos + posi;
 +                      content_t c = map.getNode(p).getContent();
 +                      v3s16 psurf(p.X, p.Y + 1, p.Z);
 +                      content_t csurf = map.getNode(psurf).getContent();
 +                      if (c == CONTENT_AIR || csurf != CONTENT_AIR)
 +                              continue;
 +                      auto it = std::find(filter.begin(), filter.end(), c);
 +                      if (it != filter.end()) {
 +                              push_v3s16(L, p);
 +                              lua_rawseti(L, -2, ++i);
 +
 +                              u32 filt_index = it - filter.begin();
 +                              individual_count[filt_index]++;
 +                      }
 +              }
 +      }
 +      lua_createtable(L, 0, filter.size());
 +      for (u32 i = 0; i < filter.size(); i++) {
 +              lua_pushinteger(L, individual_count[i]);
 +              lua_setfield(L, -2, ndef->get(filter[i]).name.c_str());
 +      }
 +      return 2;
 +}
 +
 +// find_nodes_near_under_air_except(pos, radius, nodenames, [search_center])
 +// nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
 +int ModApiEnvMod::l_find_nodes_near_under_air_except(lua_State *L)
 +{
 +      GET_PLAIN_ENV_PTR;
 +
 +      const NodeDefManager *ndef = env->getGameDef()->ndef();
 +      Map &map = env->getMap();
 +
 +      v3s16 pos = read_v3s16(L, 1);
 +      int radius = luaL_checkinteger(L, 2);
 +      std::vector<content_t> filter;
 +      collectNodeIds(L, 3, ndef, filter);
 +      int start_radius = (lua_isboolean(L, 4) && readParam<bool>(L, 4)) ? 0 : 1;
 +
 +#ifndef SERVER
 +      // Client API limitations
 +      if (Client *client = getClient(L))
 +              radius = client->CSMClampRadius(pos, radius);
 +#endif
 +
 +      std::vector<u32> individual_count;
 +      individual_count.resize(filter.size());
 +
 +      lua_newtable(L);
 +      u32 i = 0;
 +
 +      for (int d = start_radius; d <= radius; d++) {
 +              const std::vector<v3s16> &list = FacePositionCache::getFacePositions(d);
 +              for (const v3s16 &posi : list) {
 +                      v3s16 p = pos + posi;
 +                      content_t c = map.getNode(p).getContent();
 +                      v3s16 psurf(p.X, p.Y + 1, p.Z);
 +                      content_t csurf = map.getNode(psurf).getContent();
 +                      if (c == CONTENT_AIR || csurf != CONTENT_AIR)
 +                              continue;
 +                      auto it = std::find(filter.begin(), filter.end(), c);
 +                      if (it == filter.end()) {
 +                              push_v3s16(L, p);
 +                              lua_rawseti(L, -2, ++i);
 +
 +                              u32 filt_index = it - filter.begin();
 +                              individual_count[filt_index]++;
 +                      }
 +              }
 +      }
 +      lua_createtable(L, 0, filter.size());
 +      for (u32 i = 0; i < filter.size(); i++) {
 +              lua_pushinteger(L, individual_count[i]);
 +              lua_setfield(L, -2, ndef->get(filter[i]).name.c_str());
 +      }
 +      return 2;
 +}
 +
+ static void checkArea(v3s16 &minp, v3s16 &maxp)
+ {
+       auto volume = VoxelArea(minp, maxp).getVolume();
+       // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000
+       if (volume > 4096000) {
+               throw LuaError("Area volume exceeds allowed value of 4096000");
+       }
+       // Clamp to map range to avoid problems
+ #define CLAMP(arg) core::clamp(arg, (s16)-MAX_MAP_GENERATION_LIMIT, (s16)MAX_MAP_GENERATION_LIMIT)
+       minp = v3s16(CLAMP(minp.X), CLAMP(minp.Y), CLAMP(minp.Z));
+       maxp = v3s16(CLAMP(maxp.X), CLAMP(maxp.Y), CLAMP(maxp.Z));
+ #undef CLAMP
+ }
  // find_nodes_in_area(minp, maxp, nodenames, [grouped])
  int ModApiEnvMod::l_find_nodes_in_area(lua_State *L)
  {
Simple merge
Simple merge
index 769b3ef2b3bdf3adcefdc9a0e0febe537d2b675c,2efb976c7ed0fc78ebf05d0d56aa6b5273429b4f..1da0679d69ea5bfefce9f05fa7d234407615683d
@@@ -599,8 -488,8 +600,10 @@@ const luaL_Reg LuaLocalPlayer::methods[
                luamethod(LuaLocalPlayer, hud_remove),
                luamethod(LuaLocalPlayer, hud_change),
                luamethod(LuaLocalPlayer, hud_get),
 +              luamethod(LuaLocalPlayer, get_object),
 +              luamethod(LuaLocalPlayer, get_hotbar_size),
  
+               luamethod(LuaLocalPlayer, get_move_resistance),
                {0, 0}
  };
index bb5a294ca95d4ccf3540d78822fa33b0546fbcb8,041545a49e7921cd51b680573a0f0416c4f50436..458c824e6c00bf88e2a51f49e8cfef3d3ede41c3
@@@ -121,9 -95,8 +120,11 @@@ private
        // hud_get(self, id)
        static int l_hud_get(lua_State *L);
  
+       static int l_get_move_resistance(lua_State *L);
 +      // get_object(self)
 +      static int l_get_object(lua_State *L);
 +
        LocalPlayer *m_localplayer = nullptr;
  
  public:
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge